Skip to content

scipy minimize generator#424

Open
roussel-ryan wants to merge 18 commits into
mainfrom
bobyqua
Open

scipy minimize generator#424
roussel-ryan wants to merge 18 commits into
mainfrom
bobyqua

Conversation

@roussel-ryan

Copy link
Copy Markdown
Collaborator

Overview

This pull request introduces a new generic sequential optimization generator, ScipyGenerator, that wraps scipy.optimize.minimize and integrates it with Xopt's ask/tell workflow. It includes comprehensive documentation, an example notebook, and full test coverage. Additionally, it updates the generator registry and navigation to reflect the new functionality, and refactors some imports for consistency.

Integration Model

Xopt evaluates objective functions externally, one point at a time. scipy.optimize.minimize expects an in-process callable objective. ScipyGenerator bridges this mismatch by replaying known evaluations:

  1. A cache is built from X.data.
  2. minimize is called with an objective wrapper that checks the cache first.
  3. If scipy asks for an unseen point, the generator raises an internal signal, exits minimize, and returns that point to Xopt.
  4. Xopt evaluates that point and appends the result.
  5. On the next step, minimize is called again with the larger cache.

Performance Notes

  • Cache reconstruction is O(N) per step, where N is the number of collected evaluations.
  • minimize restarts each step, so there is repeated optimizer bookkeeping overhead.
  • For expensive evaluations, this overhead is usually negligible.
  • For inexpensive evaluations, this overhead can be a significant part of runtime.

Key changes:

New Feature: Generic Scipy Minimize Generator

  • Added ScipyGenerator in xopt.generators.sequential.scipy, providing a sequential ask/tell interface to any scipy.optimize.minimize method. The generator bridges the difference between Xopt's external evaluation and scipy's in-process callable objective, including robust caching and replay logic.
  • Added tests for ScipyGenerator covering point generation, multiple point error handling, restart/serialization, and direct generator usage in test_scipy.py.
  • Included ScipyGenerator in the serialization test suite. [1] [2]

Documentation & Examples

  • Added API documentation for ScipyGenerator describing its integration model, performance considerations, and configuration in docs/api/generators/sequential/scipy.md.
  • Added a new example notebook docs/examples/sequential/scipy.ipynb demonstrating usage with the Rosenbrock function and showing convergence.
  • Updated the algorithms overview and main index to mention the new generic scipy minimize generator. [1] [2]
  • Updated navigation in mkdocs.yml to include the new example and API documentation. [1] [2]

Generator Registry & Import Refactoring

  • Registered scipy as a generator name and added ScipyGenerator to the dynamic generator import logic and __all__ lists. [1] [2] [3]
  • Removed now-unnecessary imports from xopt/generators/scipy/__init__.py and refactored LatinHypercubeGenerator imports for consistency. [1] [2]

@codecov

codecov Bot commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.51244% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
xopt/generators/sequential/scipy.py 94.04% 1 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

@roussel-ryan

Copy link
Copy Markdown
Collaborator Author

@copilot address the coverage, linting and notebook failures

Copilot AI commented May 27, 2026

Copy link
Copy Markdown
Contributor

@copilot address the coverage, linting and notebook failures

Fixed in commits 42f6071 and 57bde15 (approximately):

  • Tests (test_serialization_loading[scipy]): get_generator_defaults now handles Pydantic v2 default_factory fields (which set v.default to PydanticUndefined instead of None), resolving the TypeError: Object of type PydanticUndefinedType is not JSON serializable.
  • Lint (ruff-format): Fixed x0 ** 2x0**2 in the scipy example notebook.
  • Lint (end-of-file-fixer): Removed the test artifact .txt files (all_sequential_tests.txt, test_results.txt, test_serialization_results.txt) from git tracking and added them to .gitignore.

@roussel-ryan

Copy link
Copy Markdown
Collaborator Author

@copilot add tests to achieve needed coverage

Copilot AI commented May 27, 2026

Copy link
Copy Markdown
Contributor

@copilot add tests to achieve needed coverage

Added 8 new tests in the latest commit covering all previously untested branches in scipy.pyscipy.py now has 100% line coverage:

  • Validator rejecting whitespace-only method string
  • Validator rejecting empty initial_point dict
  • _add_data early return on empty DataFrame
  • Unknown solver ValueErrorRuntimeError conversion
  • Non-solver ValueError re-raise
  • Convergence path (minimize finishes without requesting a new point)
  • Convergence path with constants
  • Candidate generation with constants

Also removed a dead-code if v is None: return v branch that was unreachable in Pydantic v2.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds a new sequential optimization generator, ScipyGenerator, that adapts scipy.optimize.minimize to Xopt’s external ask/tell workflow, and updates tests, docs, and generator registration to support it.

Changes:

  • Introduce ScipyGenerator (sequential wrapper around scipy.optimize.minimize) and register it under generator name scipy.
  • Add unit tests (including serialization/restart coverage) for the new generator and update existing sequential serialization tests.
  • Add documentation + example notebook for ScipyGenerator, and update MkDocs navigation and algorithms/index references; refactor LatinHypercubeGenerator imports/location.

Reviewed changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
xopt/generators/sequential/scipy.py New ScipyGenerator implementation wrapping scipy.optimize.minimize in a sequential ask/tell pattern
xopt/generators/sequential/init.py Export ScipyGenerator from the sequential generators package
xopt/generators/init.py Register ScipyGenerator for dynamic loading; improve defaults handling for default_factory fields
xopt/generators/scipy/init.py Removes old scipy generator re-exports (package content removed)
xopt/generators/latin_hypercube.py Adds/relocates LatinHypercubeGenerator under a new import path
xopt/tests/generators/sequential/test_scipy.py Adds test coverage for ScipyGenerator
xopt/tests/generators/sequential/test_serialization.py Adds ScipyGenerator to sequential generator serialization/restart tests
xopt/tests/generators/test_latin_hypercube.py Updates import path for LatinHypercubeGenerator
docs/api/generators/sequential/scipy.md API documentation page for ScipyGenerator
docs/examples/sequential/scipy.ipynb New example notebook demonstrating ScipyGenerator usage
docs/examples/other/latin_hypercube.ipynb Updates import path for LatinHypercubeGenerator and relocates example
mkdocs.yml Adds new docs + example notebook to navigation; updates Latin Hypercube example path
docs/index.md Mentions the new scipy sequential generator in the feature list
docs/algorithms.md Adds ScipyGenerator to algorithms list (but link maintenance needed)
.gitignore Ignores local test output artifacts

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/algorithms.md Outdated
Comment thread xopt/generators/sequential/scipy.py
Comment thread xopt/generators/sequential/scipy.py
@nikitakuklev

Copy link
Copy Markdown
Collaborator

FYI I'll try catch up this week

roussel-ryan and others added 6 commits June 2, 2026 10:57
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@electronsandstuff electronsandstuff 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.

OK, overall I think it's a cool concept. Obviously it's a bit of a hack to get it to work, but it's kind of cool. Because of that, I would strongly recommend adding the below mentioned test. I can't think of how to do it outside of this without threading. There are also obvious performance issues, but it might be good enough for now.

"source": [
"# ScipyGenerator with scipy.optimize.minimize\n",
"\n",
"This notebook demonstrates how to use Xopt's `ScipyGenerator` to drive any supported scipy `optimize.minimize` method in a sequential ask/tell workflow."

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.

"ask/tell" isn't jargon we use anywhere else. This feels like an LLM'ism making up terms. I would recommend using the names used elsewhere for the generator. Check your PR for other uses of this phrase.

"source": [
"## Notes on performance\n",
"\n",
"`ScipyGenerator` bridges scipy's in-process `minimize` API into Xopt's external evaluation loop by replaying cached points each step. This adds overhead that is small for expensive evaluations but can be noticeable for very fast toy objectives."

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.

Style thing: I don't know if I'd add low level details in the example

Comment thread .gitignore
pip-delete-this-directory.txt

# Test output files
all_sequential_tests.txt

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 searched for these and couldn't find where they are generated. Could you point out what makes them otherwise remove? I'd also suggest using a tempfile.TemporaryDirectory as an alternative to not make extra files even if not commited.

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.

Formatting, make titles title case. Probably don't need a header for imports.

cached points. This is robust and method-agnostic, but adds repeated optimizer
bookkeeping work compared to a persistent in-memory scipy run.
- Point keys are rounded (12 decimals) before cache lookup to avoid fragile
floating-point equality checks. This improves replay stability across methods

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 a little worried about dealing with "numerically close" points like this. My understanding is that the scipy optimizers are deterministic, so the hack to get it to work in Xopt is you feed it back the same points and it will always revisit the same sequence seen before and you replay it the objective value. I am worried about trying to do things where we feed it points "nearby" without carefully thinking about how this affects the algorithm.

If the cache is really just replaying points, I would imagine it can be a list, not a dict right? Ie the list of points in the order they have been seen by the optimizer, then we just replay without hashing the variables in a dict. This should work under the same assumptions as in the current version of the code and also be significantly faster (no computation during replay.

name = "scipy"
supports_single_objective: bool = True

method: str = Field(

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.

Literal["a", "b", "c"] would give better type annotation and validation. I wonder if there is a way that we can get the list of strings names of methods from scipy and fill this list automatically.

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.

Given that we are doing something not supported by scipy, I think this PR needs the following test:

Given a test objective function, perform an optimization once with scipy.minimize and once with Xopt.ScipyGenerator. Collect all of the x points and confirm all of them are the same down to numerical precision. Parameterize the test by method and do for each of the methods we are supporting in scipy (presumably all that support bounds).

This will ensure that the replay method is doing what we think it should even if scipy changes.

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.

This move will break the import from xopt.generators.scipy import LatinHypercubeGenerator. I would recommend keeping this import and a deprecation warning in __init__.py to be removed later. Especially since we showed that import in our example. We may want to note somwhere and make sure our documentation is consistent with only loading from xopt.generators and not the internal things to avoid in the future.

@roussel-ryan

Copy link
Copy Markdown
Collaborator Author

Thanks for the comments @electronsandstuff, I will work on this over the next couple of weeks and ping you again when its ready

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.

5 participants