Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAINT: minimize_cyipopt: add input validation #206

Merged
merged 2 commits into from
May 19, 2023

Conversation

mdhaber
Copy link
Contributor

@mdhaber mdhaber commented May 19, 2023

Although IpoptProblemWrapper contains some input validation, minimize_ipopt was missing input validation of other parameters. For instance, callback was ignored silently, and invalid values of arguments could lead to confusing errors deeper in the code. This PR systematically adds basic input validation and tests to minimize_ipopt.

Copy link
Contributor Author

@mdhaber mdhaber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some self-review to aid the reviewer.

Comment on lines 557 to 562
if jac is not None and jac not in {True, False} and not callable(jac):
raise ValueError('`jac` must be callable or boolean.')
if hess is not None and not callable(hess):
raise ValueError('`hess` must be callable.')
if hessp is not None:
raise NotImplementedError('`hessp` is not yet supported by Ipopt.`')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously there was some input validation of jac and hessp in IpoptProblemWrapper, but this will happen first. If IpoptProblemWrapper is only used by minimize_ipopt, shall I remove the (now redundant) input validation of these things there?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that IpoptProblemWrapper is an independent class, any input validation for it belongs there. So my recommendation is to validate everything you can there and then whatever is left could be validated here.

Copy link
Contributor Author

@mdhaber mdhaber May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually started there, but then I moved everything out because. It looked like it might just be a support function, and I wouldn't want to break up validation of, say, constraints and bounds if users wouldn't see the difference. Although it doesn't have a preceding underscore, it's not part of __all__ here or in ipopt_wrapper.py, and its not documented online. I couldn't tell - is it considered public?

If you confirm, I'll move what I can.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't tell - is it considered public?

Everything is public unless it has been explicitly labeled as not public.

Copy link
Contributor Author

@mdhaber mdhaber May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll move what can be moved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 550 to 551
if not np.iterable(args):
args = (args,)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've discussed how to handle this situation in SciPy (scipy/scipy#15994), but we haven't really reached a conclusion. This implements one of the suggestions given there. Another possibility is to raise an error (instead of wrapping args in a tuple) if args is not iterable or to be even more strict and require that args is a tuple. LMK which you prefer.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should copy the behavior in scipy's minimize() which seems to require a tuple: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if not np.iterable(args):
args = (args,)
if not isinstance(args, tuple):
args = (args,)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

kwargs = dict() if kwargs is None else kwargs
if not isinstance(kwargs, dict):
raise ValueError('`kwargs` must be a dictionary.')
if method is not None: # this will be updated when gh-200 is merged
Copy link
Contributor Author

@mdhaber mdhaber May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and gh-200 can be merged in either order. I'll make sure the other gets updated accordingly.

Comment on lines 563 to 565
# TODO: add input validation for `bounds` and `constraints` when adding
# support for instances of new-style constraints (e.g. `Bounds` and
# `NonlinearConstraint`) and sequences of constraints.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is next on my list.

@mdhaber mdhaber force-pushed the add_input_validation branch from b256d20 to ddec62d Compare May 19, 2023 01:37
@moorepants
Copy link
Collaborator

LGTM! Thanks a bunch. I like your test strings :)

@moorepants moorepants merged commit 619b1d2 into mechmotum:master May 19, 2023
@@ -118,8 +135,7 @@ def __init__(self,
elif jac is True:
fun = MemoizeJac(fun)
jac = fun.derivative
elif not callable(jac):
raise NotImplementedError('jac has to be bool or a function')
Copy link
Contributor Author

@mdhaber mdhaber May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have pointed out that I changed this to a ValueError above because I'm not sure if there is anything specific to be implemented. (hessp is different because there is something specific we have in mind.) Hope that's OK. If not, I can change it back in the next PR (in which I'll add support for Bounds).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be a rare case that someone relied on this as a feature, so we can go with your change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants