Skip to content

Alternate optimizers - #322

Closed
MTakahashi-KWH wants to merge 43 commits into
mainfrom
alternate_optimizers
Closed

Alternate optimizers#322
MTakahashi-KWH wants to merge 43 commits into
mainfrom
alternate_optimizers

Conversation

@MTakahashi-KWH

@MTakahashi-KWH MTakahashi-KWH commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

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 Scipy agent interface and standard independent ScipyOptimizer (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:

  • Rerigged simple-experiment.md as 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.
  • Introduced a new Scipy class in src/blop/gradient/Scipy.py that 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.
    • Domain native configuration interface (ScipyCFG(...) and Scipy(ScipyCFG)) also provided for deeper customization of scipy optimizers

Enhancements to DOF/parameter handling:

  • Added a to_scipy_bounds method to the RangeDOF class, enabling easy conversion of DOFs to Scipy's Bounds object for compatibility with Scipy optimizers. (may be dropped due to 2 scipy optimizers unable to support bounds??)
  • Imported Scipy's Bounds in src/blop/ax/dof.py to support the new conversion method.

Targets:

#282 Add gradient based methods to default package capabilities

@MTakahashi-KWH MTakahashi-KWH linked an issue Jun 25, 2026 that may be closed by this pull request
@MTakahashi-KWH

Copy link
Copy Markdown
Collaborator Author

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.

@MTakahashi-KWH
MTakahashi-KWH marked this pull request as ready for review July 22, 2026 21:39

@thopkins32 thopkins32 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread src/blop/scipy/scipy.py
Comment thread src/blop/scipy/__init__.py
Comment thread src/blop/gradient/optimizer.py Outdated
Comment thread src/blop/gradient/optimizer.py Outdated
Comment on lines +27 to +46
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"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Enums must be all caps, surprised the ruff linter didn't catch this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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_scalar
  • minimize with any choice of method
  • basinhopping
  • brute
  • differential_evolution
  • shgo
  • dual_annealing
  • direct

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.

Comment thread src/blop/gradient/Scipy.py Outdated
Comment thread src/blop/ax/dof.py Outdated
Comment thread src/blop/tests/gradient/test_integration.py Outdated
Comment on lines +273 to +274
if not self.force_resiliance:
raise ValueError("optimizer did not expect to receive an update")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I am confused by this. Why is it necessary?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Comment thread src/blop/scipy/optimizer.py
Comment thread src/blop/scipy/optimizer.py
@MTakahashi-KWH

Copy link
Copy Markdown
Collaborator Author

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:
@thopkins32
@josephhanrahan

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.

Add gradient based methods to default package capabilities

2 participants