better-optimize 0.3.1__tar.gz → 0.3.2__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.3.1 → better_optimize-0.3.2}/PKG-INFO +1 -1
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize/_version.py +3 -3
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize/basinhopping.py +3 -1
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize/multi_optimize.py +18 -3
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize/wrapper.py +36 -15
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize.egg-info/PKG-INFO +1 -1
- {better_optimize-0.3.1 → better_optimize-0.3.2}/tests/test_multistart.py +51 -12
- {better_optimize-0.3.1 → better_optimize-0.3.2}/tests/test_wrapper.py +5 -4
- {better_optimize-0.3.1 → better_optimize-0.3.2}/LICENSE +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/README.md +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize/__init__.py +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize/constants.py +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize/minimize.py +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize/root.py +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize/utilities.py +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize.egg-info/SOURCES.txt +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize.egg-info/dependency_links.txt +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize.egg-info/requires.txt +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize.egg-info/top_level.txt +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/pyproject.toml +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/setup.cfg +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/setup.py +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/tests/test_basinhopping.py +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/tests/test_jax_integration.py +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/tests/test_minimize.py +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/tests/test_root.py +0 -0
- {better_optimize-0.3.1 → better_optimize-0.3.2}/tests/test_utilities.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: better_optimize
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2026-03-
|
|
11
|
+
"date": "2026-03-20T19:57:42+0100",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.3.
|
|
14
|
+
"full-revisionid": "2c8f4f7afbc2b54fa1e8aa8827b4be15e6ea87a9",
|
|
15
|
+
"version": "0.3.2"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
1
3
|
from collections.abc import Callable, Sequence
|
|
2
4
|
|
|
3
5
|
import numpy as np
|
|
@@ -64,7 +66,7 @@ def initialize_progress_bar(progressbar, use_jac=True, use_hess=False):
|
|
|
64
66
|
*columns,
|
|
65
67
|
expand=False,
|
|
66
68
|
disable=not progressbar,
|
|
67
|
-
console=Console(width=CONSOLE_WIDTH),
|
|
69
|
+
console=Console(file=sys.stderr, width=CONSOLE_WIDTH),
|
|
68
70
|
transient=True,
|
|
69
71
|
)
|
|
70
72
|
|
|
@@ -2,6 +2,7 @@ import contextlib
|
|
|
2
2
|
import logging
|
|
3
3
|
import multiprocessing as mp
|
|
4
4
|
import pickle
|
|
5
|
+
import sys
|
|
5
6
|
import threading
|
|
6
7
|
|
|
7
8
|
from collections.abc import Callable
|
|
@@ -176,8 +177,22 @@ def _run_single(
|
|
|
176
177
|
if blas_cores_per_worker is not None
|
|
177
178
|
else contextlib.nullcontext()
|
|
178
179
|
)
|
|
179
|
-
|
|
180
|
-
|
|
180
|
+
try:
|
|
181
|
+
with limiter:
|
|
182
|
+
result = solver(x0=x0, progressbar=progress, progress_task=task_id, **solver_kwargs)
|
|
183
|
+
except Exception as exc:
|
|
184
|
+
_log.warning(
|
|
185
|
+
"Run %d failed with %s: %s",
|
|
186
|
+
run_index,
|
|
187
|
+
type(exc).__name__,
|
|
188
|
+
exc,
|
|
189
|
+
)
|
|
190
|
+
result = OptimizeResult(
|
|
191
|
+
x=x0,
|
|
192
|
+
fun=np.inf,
|
|
193
|
+
success=False,
|
|
194
|
+
message=f"Solver raised {type(exc).__name__}: {exc}",
|
|
195
|
+
)
|
|
181
196
|
|
|
182
197
|
result.run_index = run_index
|
|
183
198
|
result.x0 = x0
|
|
@@ -249,7 +264,7 @@ class MultiStartResult:
|
|
|
249
264
|
|
|
250
265
|
for rank, res in enumerate(display, 1):
|
|
251
266
|
table.add_row(*self._format_result_row(rank, res))
|
|
252
|
-
Console().print(table)
|
|
267
|
+
Console(file=sys.stderr).print(table)
|
|
253
268
|
|
|
254
269
|
def _result_to_dict(self, rank: int, res: OptimizeResult) -> dict:
|
|
255
270
|
"""Convert a single ``OptimizeResult`` into a flat dict for DataFrame export."""
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import sys
|
|
2
3
|
|
|
3
4
|
from collections.abc import Callable
|
|
4
5
|
from functools import partial
|
|
@@ -61,7 +62,7 @@ def build_progress_bar(
|
|
|
61
62
|
*columns,
|
|
62
63
|
expand=False,
|
|
63
64
|
disable=not progressbar,
|
|
64
|
-
console=Console(width=CONSOLE_WIDTH),
|
|
65
|
+
console=Console(file=sys.stderr, width=CONSOLE_WIDTH),
|
|
65
66
|
)
|
|
66
67
|
|
|
67
68
|
|
|
@@ -234,20 +235,40 @@ def optimizer_early_stopping_wrapper(f_optim: partial):
|
|
|
234
235
|
"prematurely.",
|
|
235
236
|
)
|
|
236
237
|
except Exception as e:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
238
|
+
# Catch any runtime errors from the optimizer (e.g. nan/inf) and return a failed
|
|
239
|
+
# result instead of crashing.
|
|
240
|
+
_log.warning("Optimizer raised %s: %s", type(e).__name__, e)
|
|
241
|
+
final_value = objective.previous_x
|
|
242
|
+
x0 = f_optim.keywords.get("x0")
|
|
243
|
+
if final_value is None:
|
|
244
|
+
final_value = x0
|
|
245
|
+
res = OptimizeResult(
|
|
246
|
+
x=final_value,
|
|
247
|
+
fun=np.inf,
|
|
248
|
+
success=False,
|
|
249
|
+
message=f"Optimizer raised {type(e).__name__}: {e}",
|
|
250
|
+
)
|
|
250
251
|
|
|
251
|
-
|
|
252
|
+
# Guard against final_value being None (can happen if no finite
|
|
253
|
+
# iterate was ever recorded and no x0 was available).
|
|
254
|
+
if final_value is not None:
|
|
255
|
+
try:
|
|
256
|
+
outputs = objective.step(final_value)
|
|
257
|
+
|
|
258
|
+
if not objective.use_jac and not objective.use_hess:
|
|
259
|
+
value = outputs
|
|
260
|
+
grad = None
|
|
261
|
+
hess = None
|
|
262
|
+
elif objective.use_jac and not objective.use_hess:
|
|
263
|
+
value, grad = outputs
|
|
264
|
+
hess = None
|
|
265
|
+
else:
|
|
266
|
+
value, grad, hess = outputs
|
|
267
|
+
|
|
268
|
+
objective.update_progressbar(value, grad, hess, completed=True)
|
|
269
|
+
except Exception:
|
|
270
|
+
# If the final evaluation itself fails (e.g. NaN inputs),
|
|
271
|
+
# just mark the progress bar as complete.
|
|
272
|
+
_log.debug("Final evaluation failed; skipping progress-bar update.", exc_info=True)
|
|
252
273
|
|
|
253
274
|
return res
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: better_optimize
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -263,19 +263,27 @@ def test_run_single_attaches_metadata():
|
|
|
263
263
|
assert_allclose(result.x0, x0)
|
|
264
264
|
|
|
265
265
|
|
|
266
|
-
def
|
|
266
|
+
def test_run_single_catches_solver_exception():
|
|
267
|
+
"""Solver exceptions are caught and returned as failed OptimizeResults."""
|
|
268
|
+
|
|
267
269
|
def exploding_solver(x0, **kwargs):
|
|
268
|
-
raise RuntimeError("
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
270
|
+
raise RuntimeError("NaN encountered")
|
|
271
|
+
|
|
272
|
+
x0 = np.array([1.0, 2.0])
|
|
273
|
+
result = _run_single(
|
|
274
|
+
run_index=3,
|
|
275
|
+
x0=x0,
|
|
276
|
+
solver=exploding_solver,
|
|
277
|
+
solver_kwargs={},
|
|
278
|
+
progress=MagicMock(),
|
|
279
|
+
task_id=0,
|
|
280
|
+
)
|
|
281
|
+
assert not result.success
|
|
282
|
+
assert result.fun == np.inf
|
|
283
|
+
assert "RuntimeError" in result.message
|
|
284
|
+
assert result.run_index == 3
|
|
285
|
+
assert_allclose(result.x0, x0)
|
|
286
|
+
assert_allclose(result.x, x0)
|
|
279
287
|
|
|
280
288
|
|
|
281
289
|
class TestMultiStartResult:
|
|
@@ -431,3 +439,34 @@ def test_multistart_pickle_fallback(caplog):
|
|
|
431
439
|
# Fallback may or may not trigger depending on environment,
|
|
432
440
|
# but either way we must get valid results.
|
|
433
441
|
assert len(result.results) == 2
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def test_multistart_survives_crashing_solver():
|
|
445
|
+
"""A solver that raises for some starting points should not kill the campaign."""
|
|
446
|
+
call_count = 0
|
|
447
|
+
|
|
448
|
+
def sometimes_exploding_solver(x0, **kwargs):
|
|
449
|
+
nonlocal call_count
|
|
450
|
+
call_count += 1
|
|
451
|
+
if call_count % 2 == 0:
|
|
452
|
+
raise RuntimeError("NaN in trust region subproblem")
|
|
453
|
+
return OptimizeResult(x=x0, fun=float(np.sum(x0**2)), success=True)
|
|
454
|
+
|
|
455
|
+
x0_list = [np.ones(2) * i for i in range(4)]
|
|
456
|
+
result = multi_optimize(
|
|
457
|
+
solver=sometimes_exploding_solver,
|
|
458
|
+
x0=x0_list,
|
|
459
|
+
backend="sequential",
|
|
460
|
+
progressbar=False,
|
|
461
|
+
blas_cores=None,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
assert len(result.results) == 4
|
|
465
|
+
n_success = sum(r.success for r in result.results)
|
|
466
|
+
n_failed = sum(not r.success for r in result.results)
|
|
467
|
+
assert n_success == 2
|
|
468
|
+
assert n_failed == 2
|
|
469
|
+
for r in result.results:
|
|
470
|
+
if not r.success:
|
|
471
|
+
assert r.fun == np.inf
|
|
472
|
+
assert "RuntimeError" in r.message
|
|
@@ -43,7 +43,8 @@ def test_early_return_from_keyboard_interrupt(root, method):
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
@pytest.mark.parametrize("root", [True, False], ids=["root", "minimize"])
|
|
46
|
-
def
|
|
46
|
+
def test_exception_returns_failed_result(root, monkeypatch):
|
|
47
|
+
"""Exceptions raised inside the optimizer should be caught and returned as a failed result."""
|
|
47
48
|
N_EXEC = 0
|
|
48
49
|
|
|
49
50
|
def f(x: np.ndarray):
|
|
@@ -64,9 +65,9 @@ def test_execption_breaks_optimization(root, monkeypatch):
|
|
|
64
65
|
callback=objective.callback,
|
|
65
66
|
)
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
result = optimizer_early_stopping_wrapper(f_optim)
|
|
69
|
+
assert not result.success
|
|
70
|
+
assert "Simulated error" in result.message
|
|
70
71
|
|
|
71
72
|
|
|
72
73
|
def func2(x, a, b):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{better_optimize-0.3.1 → better_optimize-0.3.2}/better_optimize.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|