Skip to content
Open
Show file tree
Hide file tree
Changes from 12 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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ MANIFEST
pip-log.txt
pip-delete-this-directory.txt

# Test output files
all_sequential_tests.txt
Comment thread
roussel-ryan marked this conversation as resolved.
Outdated
test_results.txt
test_serialization_results.txt

# Unit test / coverage reports
htmlcov/
.tox/
Expand Down
1 change: 1 addition & 0 deletions docs/algorithms.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Scipy Generators
These generators serve as wrappers for algorithms implemented in scipy.

- [`NelderMeadGenerator`](examples/sequential/neldermead.ipynb): implements Nelder-Mead (simplex) optimization.
- [`ScipyGenerator`](examples/sequential/scipy.ipynb): generic wrapper around `scipy.optimize.minimize` methods.
- [`LatinHypercubeGenerator`](examples/scipy/latin_hypercube.ipynb): perform latin hypercube sampling of the evaluation function.
Comment thread
roussel-ryan marked this conversation as resolved.
Outdated

RCDS Generators
Expand Down
31 changes: 31 additions & 0 deletions docs/api/generators/sequential/scipy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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 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 cheap synthetic test functions, this overhead can be a significant part of runtime.
Comment thread
roussel-ryan marked this conversation as resolved.
Outdated

## 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"source": [
"from copy import deepcopy\n",
"from xopt import Xopt, Evaluator\n",
"from xopt.generators.scipy.latin_hypercube import LatinHypercubeGenerator\n",
"from xopt.generators.latin_hypercube import LatinHypercubeGenerator\n",
"from xopt.resources.test_functions.tnk import evaluate_TNK, tnk_vocs\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np"
Expand Down
171 changes: 171 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,171 @@
{
"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 `Powell`, but methods such as `Nelder-Mead`, `L-BFGS-B`, and others can also be used."
Comment thread
roussel-ryan marked this conversation as resolved.
Outdated
]
},
{
"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()"
]
},
{
"cell_type": "markdown",
"id": "f693d7e1",
"metadata": {},
"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

]
}
],
"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
}
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Currenty **Xopt** provides:
- `extremum_seeking` Extremum seeking time-dependent optimization
- `rcds` Robust Conjugate Direction Search (RCDS)
- `neldermead` Nelder-Mead Simplex
- `scipy` Generic scipy.optimize.minimize sequential optimization
- Sampling algorithms:
- `random` Uniform random sampling
- Convenient YAML/JSON based input format
Expand Down
4 changes: 3 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,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 @@ -103,6 +104,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
8 changes: 6 additions & 2 deletions 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 @@ -58,11 +58,13 @@ def get_generator_dynamic(name: str) -> type[Generator]:
)
elif name in all_generator_names["scipy"]:
try:
from xopt.generators.scipy.latin_hypercube import LatinHypercubeGenerator
from xopt.generators.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 @@ -155,6 +157,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
Comment thread
roussel-ryan marked this conversation as resolved.
File renamed without changes.
4 changes: 0 additions & 4 deletions xopt/generators/scipy/__init__.py

This file was deleted.

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