Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
986e86c
initial implementation
roussel-ryan May 27, 2026
68ab06b
generalize bobyqa to any scipy minimize method
roussel-ryan May 27, 2026
caeb5f2
move latin_hypercube
roussel-ryan May 27, 2026
f85cf21
update scipy docs
roussel-ryan May 27, 2026
bbab28e
update tests and docs
roussel-ryan May 27, 2026
38a180a
linting
roussel-ryan May 27, 2026
5b4d53a
testing updates
roussel-ryan May 27, 2026
42f6071
Fix PydanticUndefinedType not JSON serializable in get_generator_defa…
Copilot May 27, 2026
b5b1686
Fix lint failures: remove test artifact txt files, add to gitignore, …
Copilot May 27, 2026
711b718
Add tests for full ScipyGenerator coverage
Copilot May 27, 2026
6cf6af0
move / update latin hypercube example
roussel-ryan May 27, 2026
9eb7a74
linting
roussel-ryan May 27, 2026
406239d
Potential fix for pull request finding
roussel-ryan Jun 2, 2026
cbf70fd
Potential fix for pull request finding
roussel-ryan Jun 2, 2026
8d82e11
Potential fix for pull request finding
roussel-ryan Jun 2, 2026
b1f28b2
Update test_scipy.py
roussel-ryan Jun 2, 2026
4de94b6
linting
roussel-ryan Jun 3, 2026
ca1a4d1
Merge branch 'main' into bobyqua
roussel-ryan Jun 11, 2026
f47a551
update generators to use threading instead of replay
roussel-ryan Jul 15, 2026
95f460a
Merge branch 'main' into bobyqua
roussel-ryan Jul 15, 2026
bca8044
move latin hypercube generator back
roussel-ryan Jul 15, 2026
c0bbd6a
fix import
roussel-ryan Jul 15, 2026
cd8a547
update method validation and tests
roussel-ryan Jul 15, 2026
a28406e
add tests to maximize coverage
roussel-ryan Jul 15, 2026
54530c9
Potential fix for pull request finding
roussel-ryan Jul 15, 2026
1749e41
minor updates
roussel-ryan Jul 15, 2026
929dd09
remove extraneous files from gitignore
roussel-ryan Jul 15, 2026
cf64fa0
Update scipy.ipynb
roussel-ryan Jul 15, 2026
eeea483
linting
roussel-ryan Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/algorithms.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ Scipy Generators
These generators serve as wrappers for algorithms implemented in scipy.

- [`NelderMeadGenerator`](examples/sequential/neldermead.ipynb): implements Nelder-Mead (simplex) optimization.
- [`LatinHypercubeGenerator`](examples/scipy/latin_hypercube.ipynb): perform latin hypercube sampling of the evaluation function.
- [`ScipyGenerator`](examples/sequential/scipy.ipynb): generic wrapper around `scipy.optimize.minimize` methods.
- [`LatinHypercubeGenerator`](examples/other/latin_hypercube.ipynb): performs latin hypercube sampling of the evaluation function.

RCDS Generators
===
Expand Down
64 changes: 64 additions & 0 deletions docs/api/generators/sequential/scipy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Scipy Minimize Generator

`ScipyGenerator` exposes scipy's `optimize.minimize` methods through Xopt's sequential ask/tell interface.

## 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 running one persistent scipy session in a worker thread:

1. A cache is built from prior evaluations (`data`) keyed by rounded variable values.
2. A single `minimize` call starts in a background thread.
3. The objective callback first checks the cache.
4. For an uncached point, that point is pushed to Xopt via a request queue.
5. Xopt evaluates the point externally and calls `add_data`.
6. The objective value is sent back through a response queue, and the same scipy run continues from in-memory state.

## Performance Notes

- Cache reconstruction is O(N) when a session starts or data is reloaded.
- The active scipy run is maintained between `step` calls; `minimize` is not restarted each step.
- Keys are rounded to 12 decimals before cache lookup to reduce floating-point key mismatch issues.

## Supported Methods

`method` is validated against bounded scipy methods supported by this wrapper:

- `Nelder-Mead`
- `Powell`
- `L-BFGS-B`
- `TNC`
- `SLSQP`
- `trust-constr`
- `COBYLA`
- `COBYQA`

Invalid or empty method names fail validation.

## Session Lifecycle and Errors

- `reset()` stops the active worker session and clears transient runtime state.
- `set_data(...)` stops any active session, reloads data, and rebuilds the cache.
- If scipy converges using only cached values, generation falls back to the latest known data row.
- Common scipy `ValueError` messages are remapped to clearer runtime errors (for example unsupported solver availability or bound handling).

## State Restoration

For model-level roundtrips, use pydantic serialization:

- `model_dump()`
- `model_validate(...)`

Then restore the evaluation history with `set_data(...)` so the cache and last outcome are reconstructed before continuing optimization.

The class also defines `__getstate__` and `__setstate__` for explicit state handling of non-picklable runtime thread objects.

## Configuration

Typical fields:

- `method`: scipy minimization method name, e.g. `Powell`, `Nelder-Mead`, `L-BFGS-B`.
- `initial_point`: optional starting point dictionary.
- `tol`, `options`: passed directly to `scipy.optimize.minimize`.
- `scipy_kwargs`: additional keyword arguments forwarded to scipy.

::: xopt.generators.sequential.scipy
161 changes: 161 additions & 0 deletions docs/examples/sequential/scipy.ipynb

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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3841297f",
"metadata": {},
"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.

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.

removed

]
},
{
"cell_type": "markdown",
"id": "ece077aa",
"metadata": {},
"source": [
"## Imports"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2517dcff",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"\n",
"from xopt import Evaluator, VOCS, Xopt\n",
"from xopt.generators.sequential.scipy import ScipyGenerator"
]
},
{
"cell_type": "markdown",
"id": "ca499090",
"metadata": {},
"source": [
"## Define a simple objective\n",
"\n",
"We will optimize the 2D Rosenbrock function, exposed through an Xopt evaluator function."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7702f19c",
"metadata": {},
"outputs": [],
"source": [
"def rosenbrock_eval(input_dict):\n",
" x0 = input_dict[\"x0\"]\n",
" x1 = input_dict[\"x1\"]\n",
" y = (1 - x0) ** 2 + 100.0 * (x1 - x0**2) ** 2\n",
" return {\"y\": float(y)}"
]
},
{
"cell_type": "markdown",
"id": "29a8f4d5",
"metadata": {},
"source": [
"## Configure `ScipyGenerator`\n",
"\n",
"Choose a scipy method using `method`. Here we use `L-BFGS-B`, but methods such as `Nelder-Mead`, `Powell`, and others can also be used."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b020f9b7",
"metadata": {},
"outputs": [],
"source": [
"vocs = VOCS(\n",
" variables={\"x0\": [-2.0, 2.0], \"x1\": [-1.0, 3.0]},\n",
" objectives={\"y\": \"MINIMIZE\"},\n",
")\n",
"\n",
"generator = ScipyGenerator(\n",
" vocs=vocs,\n",
" method=\"L-BFGS-B\",\n",
" initial_point={\"x0\": -1.2, \"x1\": 1.0},\n",
" options={\"maxiter\": 200},\n",
")\n",
"\n",
"evaluator = Evaluator(function=rosenbrock_eval)\n",
"X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)"
]
},
{
"cell_type": "markdown",
"id": "6b3fe58b",
"metadata": {},
"source": [
"## Run optimization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "775e0fe9",
"metadata": {},
"outputs": [],
"source": [
"for _ in range(200):\n",
" X.step()\n",
"\n",
"best_idx = X.data[\"y\"].argmin()\n",
"best = X.data.iloc[best_idx]\n",
"\n",
"print(\"Evaluations:\", len(X.data))\n",
"print(\"Best point:\", {\"x0\": float(best[\"x0\"]), \"x1\": float(best[\"x1\"])})\n",
"print(\"Best objective:\", float(best[\"y\"]))"
]
},
{
"cell_type": "markdown",
"id": "444a1f87",
"metadata": {},
"source": [
"## Inspect convergence"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a5518499",
"metadata": {},
"outputs": [],
"source": [
"ax = X.data[\"y\"].plot(figsize=(7, 4), logy=True)\n",
"ax.set_xlabel(\"iteration\")\n",
"ax.set_ylabel(\"objective y (log scale)\")\n",
"ax.set_title(\"ScipyGenerator optimization progression\")\n",
"plt.tight_layout()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "xopt-dev",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
4 changes: 3 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@ nav:
- Genetic operators: examples/ga/nsga2/genetic_operators.ipynb

- Sequential:
- Scipy Minimize: examples/sequential/scipy.ipynb
- Nelder-Mead: examples/sequential/neldermead.ipynb
- Extremum seeking: examples/sequential/extremum_seeking.ipynb
- RCDS: examples/sequential/rcds.ipynb
- Other:
- Latin Hypercube: examples/scipy/latin_hypercube.ipynb
- Latin Hypercube: examples/other/latin_hypercube.ipynb
- Developer:
- Benchmarking & Profiling: examples/developer/benchmarking.md

Expand Down Expand Up @@ -104,6 +105,7 @@ nav:
- Genetic Operators: api/generators/ga/operators.md
- Sequential generators:
- Sequential Base Class: api/generators/sequential/sequential_generator.md
- Scipy Minimize: api/generators/sequential/scipy.md
- RCDS: api/generators/sequential/rcds.md
- Extremum Seeking: api/generators/sequential/extremumseeking.md
- Nelder-Mead: api/generators/sequential/neldermead.md
Expand Down
6 changes: 5 additions & 1 deletion xopt/generators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# don't import this directly -- use
all_generator_names = {
"mggpo": {"mggpo"},
"scipy": {"neldermead", "latin_hypercube"},
"scipy": {"neldermead", "latin_hypercube", "scipy"},
"bo": {
"upper_confidence_bound",
"mobo",
Expand Down Expand Up @@ -61,9 +61,11 @@ def get_generator_dynamic(name: str) -> type[Generator]:
try:
from xopt.generators.scipy.latin_hypercube import LatinHypercubeGenerator
from xopt.generators.sequential.neldermead import NelderMeadGenerator
from xopt.generators.sequential.scipy import ScipyGenerator

registered_generators = [
NelderMeadGenerator,
ScipyGenerator,
LatinHypercubeGenerator,
]

Expand Down Expand Up @@ -159,6 +161,8 @@ def get_generator_defaults(

if v.is_required():
defaults[k] = None
elif v.default_factory is not None:
defaults[k] = v.default_factory()
else:
if v.default is None:
defaults[k] = None
Expand Down
4 changes: 0 additions & 4 deletions xopt/generators/scipy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +0,0 @@
from xopt.generators.scipy.latin_hypercube import LatinHypercubeGenerator
from xopt.generators.sequential.neldermead import NelderMeadGenerator

__all__ = ["LatinHypercubeGenerator", "NelderMeadGenerator"]
2 changes: 2 additions & 0 deletions xopt/generators/sequential/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from xopt.generators.sequential.rcds import RCDSGenerator
from xopt.generators.sequential.extremumseeking import ExtremumSeekingGenerator
from xopt.generators.sequential.neldermead import NelderMeadGenerator
from xopt.generators.sequential.scipy import ScipyGenerator

__all__ = [
"SequentialGenerator",
"RCDSGenerator",
"ExtremumSeekingGenerator",
"NelderMeadGenerator",
"ScipyGenerator",
]
Loading
Loading