better-optimize 0.2.1__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {better_optimize-0.2.1 → better_optimize-0.3.1}/PKG-INFO +52 -1
- {better_optimize-0.2.1 → better_optimize-0.3.1}/README.md +48 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize/__init__.py +2 -1
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize/_version.py +3 -3
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize/basinhopping.py +6 -1
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize/constants.py +1 -0
- better_optimize-0.3.1/better_optimize/multi_optimize.py +516 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize/root.py +4 -1
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize/wrapper.py +51 -26
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize.egg-info/PKG-INFO +52 -1
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize.egg-info/SOURCES.txt +2 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize.egg-info/requires.txt +3 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/pyproject.toml +3 -0
- better_optimize-0.3.1/tests/test_multistart.py +433 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/LICENSE +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize/minimize.py +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize/utilities.py +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize.egg-info/dependency_links.txt +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/better_optimize.egg-info/top_level.txt +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/setup.cfg +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/setup.py +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/tests/test_basinhopping.py +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/tests/test_jax_integration.py +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/tests/test_minimize.py +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/tests/test_root.py +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/tests/test_utilities.py +0 -0
- {better_optimize-0.2.1 → better_optimize-0.3.1}/tests/test_wrapper.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: better_optimize
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: A drop-in replacement for scipy optimize functions with quality of life improvements
|
|
5
5
|
License: MIT License
|
|
6
6
|
Project-URL: Homepage, https://github.com/jessegrabowski/better_optimize
|
|
@@ -21,6 +21,9 @@ License-File: LICENSE
|
|
|
21
21
|
Requires-Dist: numpy
|
|
22
22
|
Requires-Dist: scipy>=1.15
|
|
23
23
|
Requires-Dist: rich
|
|
24
|
+
Requires-Dist: threadpoolctl
|
|
25
|
+
Requires-Dist: joblib
|
|
26
|
+
Requires-Dist: pandas
|
|
24
27
|
Provides-Extra: tests
|
|
25
28
|
Requires-Dist: pytest; extra == "tests"
|
|
26
29
|
Requires-Dist: pytest-cov; extra == "tests"
|
|
@@ -146,6 +149,54 @@ Many sub-computations are repeated between the objective, gradient, and hessian
|
|
|
146
149
|
fused value_and_grad function, but `better_optimize` also lets you pass a triple-fused value_grad_and_hess function.
|
|
147
150
|
This avoids redundant computation and speeds up the optimization process.
|
|
148
151
|
|
|
152
|
+
### Parallel Optimization from Multiple Starting Points
|
|
153
|
+
|
|
154
|
+
Real-world objectives often have multiple local minima. A common workaround is to throw many random starting points at
|
|
155
|
+
the optimizer and keep the best result. `better_optimize` makes this painless with `multi_optimize`:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from better_optimize import minimize, multi_optimize
|
|
159
|
+
|
|
160
|
+
def rosenbrock(x):
|
|
161
|
+
return sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
|
|
162
|
+
|
|
163
|
+
result = multi_optimize(
|
|
164
|
+
solver=minimize,
|
|
165
|
+
solver_kwargs=dict(f=rosenbrock, method="L-BFGS-B", tol=1e-10),
|
|
166
|
+
x0=np.zeros(5),
|
|
167
|
+
n_runs=16,
|
|
168
|
+
init_strategy="uniform",
|
|
169
|
+
bounds=(-5, 5),
|
|
170
|
+
backend="loky",
|
|
171
|
+
n_jobs=-1,
|
|
172
|
+
seed=42,
|
|
173
|
+
progressbar=True,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
print(result.best) # Best OptimizeResult
|
|
177
|
+
print(result.x_best) # Best parameter vector
|
|
178
|
+
print(result.fun_best) # Best objective value
|
|
179
|
+
result.summary() # Rich table of all runs, ranked
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`multi_optimize` works with **any** solver that follows the `(x0, **kwargs) → OptimizeResult` signature — that includes
|
|
183
|
+
`minimize`, `root`, `basinhopping`, or your own custom wrapper. It just calls `solver(x0=x0_i, **solver_kwargs)` for
|
|
184
|
+
each starting point; it never inspects the solver internals.
|
|
185
|
+
|
|
186
|
+
A few highlights:
|
|
187
|
+
|
|
188
|
+
- **Initialization strategies** — `"uniform"`, `"normal"`, `"sobol"`, `"lhs"`, or pass your own callable. Bounded
|
|
189
|
+
strategies (`uniform`, `sobol`, `lhs`) require a `bounds` argument; `"normal"` perturbs around `x0` with a
|
|
190
|
+
configurable `init_scale`. Or just pass an explicit `list[np.ndarray]` as `x0` and skip the generation entirely.
|
|
191
|
+
- **Parallel backends** — `"sequential"` (for debugging), `"loky"` (CPU-bound work, default), or `"threading"`
|
|
192
|
+
(GIL-releasing code). Under the hood this is `joblib`, so the usual `n_jobs=-1` convention works.
|
|
193
|
+
- **BLAS thread control** — When many workers each spawn a full BLAS/OpenMP thread pool, you get noisy-neighbor
|
|
194
|
+
over-subscription. The `blas_cores` argument (default `"auto"`) caps the total thread budget so workers don't
|
|
195
|
+
fight over the same cores.
|
|
196
|
+
|
|
197
|
+
The returned `MultiStartResult` gives you `best`, `top_k(k)`, `ranked()`, `success_rate`, a `summary()` table, and
|
|
198
|
+
`to_dataframe()` for further analysis.
|
|
199
|
+
|
|
149
200
|
|
|
150
201
|
## Contributing
|
|
151
202
|
|
|
@@ -117,6 +117,54 @@ Many sub-computations are repeated between the objective, gradient, and hessian
|
|
|
117
117
|
fused value_and_grad function, but `better_optimize` also lets you pass a triple-fused value_grad_and_hess function.
|
|
118
118
|
This avoids redundant computation and speeds up the optimization process.
|
|
119
119
|
|
|
120
|
+
### Parallel Optimization from Multiple Starting Points
|
|
121
|
+
|
|
122
|
+
Real-world objectives often have multiple local minima. A common workaround is to throw many random starting points at
|
|
123
|
+
the optimizer and keep the best result. `better_optimize` makes this painless with `multi_optimize`:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from better_optimize import minimize, multi_optimize
|
|
127
|
+
|
|
128
|
+
def rosenbrock(x):
|
|
129
|
+
return sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
|
|
130
|
+
|
|
131
|
+
result = multi_optimize(
|
|
132
|
+
solver=minimize,
|
|
133
|
+
solver_kwargs=dict(f=rosenbrock, method="L-BFGS-B", tol=1e-10),
|
|
134
|
+
x0=np.zeros(5),
|
|
135
|
+
n_runs=16,
|
|
136
|
+
init_strategy="uniform",
|
|
137
|
+
bounds=(-5, 5),
|
|
138
|
+
backend="loky",
|
|
139
|
+
n_jobs=-1,
|
|
140
|
+
seed=42,
|
|
141
|
+
progressbar=True,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
print(result.best) # Best OptimizeResult
|
|
145
|
+
print(result.x_best) # Best parameter vector
|
|
146
|
+
print(result.fun_best) # Best objective value
|
|
147
|
+
result.summary() # Rich table of all runs, ranked
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`multi_optimize` works with **any** solver that follows the `(x0, **kwargs) → OptimizeResult` signature — that includes
|
|
151
|
+
`minimize`, `root`, `basinhopping`, or your own custom wrapper. It just calls `solver(x0=x0_i, **solver_kwargs)` for
|
|
152
|
+
each starting point; it never inspects the solver internals.
|
|
153
|
+
|
|
154
|
+
A few highlights:
|
|
155
|
+
|
|
156
|
+
- **Initialization strategies** — `"uniform"`, `"normal"`, `"sobol"`, `"lhs"`, or pass your own callable. Bounded
|
|
157
|
+
strategies (`uniform`, `sobol`, `lhs`) require a `bounds` argument; `"normal"` perturbs around `x0` with a
|
|
158
|
+
configurable `init_scale`. Or just pass an explicit `list[np.ndarray]` as `x0` and skip the generation entirely.
|
|
159
|
+
- **Parallel backends** — `"sequential"` (for debugging), `"loky"` (CPU-bound work, default), or `"threading"`
|
|
160
|
+
(GIL-releasing code). Under the hood this is `joblib`, so the usual `n_jobs=-1` convention works.
|
|
161
|
+
- **BLAS thread control** — When many workers each spawn a full BLAS/OpenMP thread pool, you get noisy-neighbor
|
|
162
|
+
over-subscription. The `blas_cores` argument (default `"auto"`) caps the total thread budget so workers don't
|
|
163
|
+
fight over the same cores.
|
|
164
|
+
|
|
165
|
+
The returned `MultiStartResult` gives you `best`, `top_k(k)`, `ranked()`, `success_rate`, a `summary()` table, and
|
|
166
|
+
`to_dataframe()` for further analysis.
|
|
167
|
+
|
|
120
168
|
|
|
121
169
|
## Contributing
|
|
122
170
|
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
from better_optimize._version import get_versions
|
|
4
4
|
from better_optimize.basinhopping import basinhopping
|
|
5
5
|
from better_optimize.minimize import minimize
|
|
6
|
+
from better_optimize.multi_optimize import multi_optimize
|
|
6
7
|
from better_optimize.root import root
|
|
7
8
|
|
|
8
9
|
_log = logging.getLogger(__name__)
|
|
@@ -15,4 +16,4 @@ if not logging.root.handlers:
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
__version__ = get_versions()["version"]
|
|
18
|
-
__all__ = ["minimize", "root", "basinhopping"]
|
|
19
|
+
__all__ = ["minimize", "root", "basinhopping", "multi_optimize"]
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2026-03-
|
|
11
|
+
"date": "2026-03-20T00:48:11+0100",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.
|
|
14
|
+
"full-revisionid": "0478ed4b401b74272d06dee5e904e77be2916235",
|
|
15
|
+
"version": "0.3.1"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -21,6 +21,7 @@ from scipy.optimize._basinhopping import (
|
|
|
21
21
|
check_random_state,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
+
from better_optimize.constants import CONSOLE_WIDTH
|
|
24
25
|
from better_optimize.minimize import minimize
|
|
25
26
|
from better_optimize.utilities import (
|
|
26
27
|
LRUCache1,
|
|
@@ -60,7 +61,11 @@ def initialize_progress_bar(progressbar, use_jac=True, use_hess=False):
|
|
|
60
61
|
]
|
|
61
62
|
|
|
62
63
|
return ToggleableProgress(
|
|
63
|
-
*columns,
|
|
64
|
+
*columns,
|
|
65
|
+
expand=False,
|
|
66
|
+
disable=not progressbar,
|
|
67
|
+
console=Console(width=CONSOLE_WIDTH),
|
|
68
|
+
transient=True,
|
|
64
69
|
)
|
|
65
70
|
|
|
66
71
|
|