-
Notifications
You must be signed in to change notification settings - Fork 39
scipy minimize generator #424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
986e86c
68ab06b
caeb5f2
f85cf21
bbab28e
38a180a
5b4d53a
42f6071
b5b1686
711b718
6cf6af0
9eb7a74
406239d
cbf70fd
8d82e11
b1f28b2
4de94b6
ca1a4d1
f47a551
95f460a
bca8044
c0bbd6a
cd8a547
a28406e
54530c9
1749e41
929dd09
cf64fa0
eeea483
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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." | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| } | ||
| 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"] | ||
There was a problem hiding this comment.
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.