Alternate optimizers - #322
Conversation
|
This is now a mostly complete implementation. There are some unexposed optimizers and scipy options that I believe should be pushed to a future update of this module. |
thopkins32
left a comment
There was a problem hiding this comment.
Fairly complex implementation so I will have to review once more after discussion. Are there practical ways to simplify a lot of this?
The major complexity stems from the required cooperative thread + callback mechanism and the support for various algorithms from scipy.optimize. That + some state tracking should be all we need to get an optimizer working with Blop's protocols.
If we defer to the user to give us the scipy.optimize method (w/ *args, **kwargs) in the scipy Agent/Optimizer, then all we have to do is state tracking + cooperative thread management.
Our "cost function", in scipy terms, is always to simply wait (possibly forever) until the next call to ingest(), nothing more, nothing less.
| Default = "Default" | ||
|
|
||
| Nelder_Mead = "Nelder-Mead" | ||
| Powell = "Powell" | ||
| CG = "CG" | ||
| BFGS = "BFGS" | ||
| # Newton_CG = "Newton-CG" | ||
| LBFGS = "L-BFGS-B" | ||
| TNC = "TNC" | ||
| COBYLA = "COBYLA" | ||
| COBYQA = "COBYQA" | ||
| SLSQP = "SLSQP" | ||
| Trust_Constr = "trust-constr" | ||
| # Dogleg = "dogleg" | ||
| # Trust_NCG = "trust-ncg" | ||
| # Trust_Exact = "trust-exact" | ||
| # Trust_Krylov = "trust-krylov" | ||
|
|
||
| Dual_Annealing = "dual annealing" | ||
| SHGO = "SHGO" |
There was a problem hiding this comment.
Enums must be all caps, surprised the ruff linter didn't catch this.
There was a problem hiding this comment.
See my comment on API design for this. Can users pass in the scipy.optimize method they want instead of us maintaining a "supported algorithm" list?
There was a problem hiding this comment.
This is a debate I've have with development as Scipy is notorious for having an inconsistent API. I decided to 'flatten' the call so that a standard parameter/call format fits them all, and needed enum as a gatekeeper for as I expand the implementation support. Adding the ability to pass an optimizer would would need a standard enough parameter interface Scipy.opt doesnt have (see some algorithms also requiring a jacobian or an analytical gradient closure too)
In a sense my implementations primary development is to invert the prompting loop of a cost closure calling optimization function and not necessarily scipy specific. I can definitely make a generic loop inversion implementation and regularize scipy's calls in another file to work with this and we'd just pass one of those as something like "loop_invert(scipy_norm.BFGS,*params)". Another optimization system that accepts a cost function closure could run the same way. Though ultimately that's a consequence of the implementation, not the objective. The design started from the assumption that SciPy is the optimization backend to enable gradient optimization, not from a desire to abstract over arbitrary optimizers. I see no significant evidence that another optimization library operates in a similar way to enable this.
There was a problem hiding this comment.
Yeah I realize this is a bit annoying. Most of these methods take a callable function as the first argument though which is something we can standardize on. In fact, all of the methods under the "Optimization" sub-header take func as the first argument.
This would enable the use of:
minimize_scalarminimizewith any choice of methodbasinhoppingbrutedifferential_evolutionshgodual_annealingdirect
This already covers so much that I think it's fine to start here. If there is demand for anything beyond this, then we can revisit.
| if not self.force_resiliance: | ||
| raise ValueError("optimizer did not expect to receive an update") |
There was a problem hiding this comment.
I am confused by this. Why is it necessary?
There was a problem hiding this comment.
This is from the fact that we don't have a stopping condition and agent.optimize() runs for a fixed number of iters. If scipy converges to a solution, it will stop creating sample points. I made the decision that if it converges in the middle of an optimization, a user would be more happy for it to run the iterations to completion without erroring out run engine. But I still give the ability to do so anyways to cut useless operation.
|
added refactoring branch scipy_barebones which separates the parameter normalization of inner/closed optimizer call (InnerOptimizer) from the inversion setup (OuterOptimizer). Really need a better naming convention for this structure of interest to perhaps: |
8a2b887 to
171ca3d
Compare
…times for testing
dropping crossover Co-authored-by: Thomas Hopkins <thopkins1@bnl.gov>
171ca3d to
563f328
Compare
Keeping in draft while the rest of the optimizers provided by scipy optimize are filled in on the SCP enum (should be pretty procedural with no changes needed to testing except regarding global optimizers)
Request introduces a new Scipy-based gradient optimization interface to the Blop library and provides some documentation and usability improvements for gradient-based optimization workflows. The main changes include the addition of a user-friendly
Scipyagent interface and standard independentScipyOptimizer(with/session interface and standard object usage), new documentation for Scipy optimization, and enhancements to DOF support for Scipy optimizers.New Scipy optimization interface and documentation:
simple-experiment.mdas a system_spanning tutorial (gradient-optimization.md) demonstrating how to use Blop with Scipy optimizers, including setup, defining DOFs/objectives, evaluation functions, running optimizations, and visualizing results.Scipyclass insrc/blop/gradient/Scipy.pythat provides a convenient, Ax-like agent interface (Scipy.Agent(...)) for running gradient-based optimizations with Scipy, supporting both single and multipoint sampling, callback management, and integration with Bluesky.ScipyCFG(...)andScipy(ScipyCFG)) also provided for deeper customization of scipy optimizersEnhancements to DOF/parameter handling:
to_scipy_boundsmethod to theRangeDOFclass, enabling easy conversion of DOFs to Scipy'sBoundsobject for compatibility with Scipy optimizers. (may be dropped due to 2 scipy optimizers unable to support bounds??)Boundsinsrc/blop/ax/dof.pyto support the new conversion method.Targets:
#282 Add gradient based methods to default package capabilities