-
Notifications
You must be signed in to change notification settings - Fork 62
ENH: minimize_cyipopt: support SciPy methods
#200
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
Conversation
There was a problem hiding this 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 review.
| def _wrap_fun(fun, kwargs): | ||
| if callable(fun) and kwargs: | ||
| def new_fun(x, *args): | ||
| return fun(x, *args, **kwargs) | ||
| else: | ||
| new_fun = fun | ||
| return new_fun | ||
|
|
||
| def _wrap_funs(fun, jac, hess, hessp, constraints, kwargs): | ||
| wrapped_fun = _wrap_fun(fun, kwargs) | ||
| wrapped_jac = _wrap_fun(jac, kwargs) | ||
| wrapped_hess = _wrap_fun(hess, kwargs) | ||
| wrapped_hessp = _wrap_fun(hessp, kwargs) | ||
| if isinstance(constraints, dict): | ||
| constraints = (constraints,) | ||
| wrapped_constraints = [] | ||
| for constraint in constraints: | ||
| constraint = constraint.copy() | ||
| ckwargs = constraint.pop('kwargs', {}) | ||
| constraint['fun'] = _wrap_fun(constraint.get('fun', None), ckwargs) | ||
| constraint['jac'] = _wrap_fun(constraint.get('jac', None), ckwargs) | ||
| wrapped_constraints.append(constraint) | ||
| return (wrapped_fun, wrapped_jac, wrapped_hess, wrapped_hessp, | ||
| wrapped_constraints) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minimize does not natively support kwargs like minimize_ipopt, so we have to do some wrapping. This is why the tests heavily emphasize the callable arguments (and I didn't test bounds, options, etc.).
There is one other callable, callback, but that doesn't need to be wrapped because it does not accept additional arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The kwargs haven't ever worked before, I added them in the recent PR #197. If it makes more sense to disable the support for kwargs, we can.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, they work now, so no harm leaving them unless you want to deprecate them and remove the parameter altogether.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They don't need to be deprecated because we haven't made a new release yet. If the goal is to match the scipy api, then maybe we should stick to that. Otherwise, I'm open for whatever people need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it didn't work before, but it looks like it was already present as a positional/keyword argument in a previous release (e.g. 1.0.3). For SciPy, that would mean it needs to be deprecated before it can be removed because working user code may pass it as an argument even though it does nothing. But if you are comfortable with the (probably rare) immediate disruption of removing it (e.g. now be an error if it is passed), I understand.
I'd rather not weigh in on that decision, though. (If I had my way, minimize would have neither args nor kwargs, jac would be grad - see scipy/scipy#18249, etc...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@moorepants since it was already present as an argument in previous releases, do you agree that it would need to be deprecated? In light of that, would you like to just keep it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine keeping it, but we should document how we deviate from scipy's minimize function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a bit at the top of the documentation.
Please don't keep kwargs on my account, though. As I mentioned, I am almost always against accepting args and kwargs just to be passed through to functions. I would prefer that standard Python features be used to accomplish the same thing (e.g. def, lambda, or functools.partial to wrap the callable). I've supported them here because whether they worked or not, they have been part of the signature for at least a few releases.
| # Hard-code rather than importing from scipy.optimize._minimize in a try/except | ||
| MINIMIZE_METHODS = ['nelder-mead', 'powell', 'cg', 'bfgs', 'newton-cg', | ||
| 'l-bfgs-b', 'tnc', 'cobyla', 'slsqp', 'trust-constr', | ||
| 'dogleg', 'trust-ncg', 'trust-exact', 'trust-krylov'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you prefer:
| # Hard-code rather than importing from scipy.optimize._minimize in a try/except | |
| MINIMIZE_METHODS = ['nelder-mead', 'powell', 'cg', 'bfgs', 'newton-cg', | |
| 'l-bfgs-b', 'tnc', 'cobyla', 'slsqp', 'trust-constr', | |
| 'dogleg', 'trust-ncg', 'trust-exact', 'trust-krylov'] | |
| try: | |
| from scipy.optimize._minimize import MINIMIZE_METHODS | |
| except ImportError: | |
| pass |
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll have to maintain this hard coded list if things change in scipy, so I think I'd prefer the try clause. Is there a reason you hardcoded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only to avoid importing from a private module. But I don't think that's much of a concern to me anymore. I'll change it.
| self.obj_hess = hess | ||
| if jac is None: | ||
| jac = lambda x0, *args, **kwargs: approx_fprime( | ||
| x0, fun, eps, *args, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
approx_fprime doesn't accept kwargs
|
OK, I finished the remaining tests and fixed a bug with use of |
| if method in constr_methods: | ||
| np.testing.assert_allclose(res.x[2:], [c0, d0], rtol=1e-3) | ||
| else: | ||
| np.testing.assert_allclose(res.x[2:], 0, atol=1e-3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious why the tolerances in these checks are quite large.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think it was because of COBYLA. I'll see if they can be tighter for other methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah a few solvers have relative or absolute errors up to the order of 1e-5. I tightened the tolerances to 5e-5 across the board and made them variables so they can be adjusted more easily. I don't think it's worth it to have different tolerances for each part of the test / every method, and 5e-5 is about the tightest we can do and have everything pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that's fine.
|
This looks good. One comment and I merged the other PR so there are some conflicts here now. |
|
Thanks! Addressed the comments and merged master. Squash merge is fine with me; I don't think there's much worth preserving about the commits. |
|
Thanks! |
This adds support for the
methodsparameter ofminimize_ipopt.To do:
method=None(ipopt) failshesspboundscallbacktol?options?