better-optimize 0.1.2__tar.gz → 0.1.4__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.1.4/PKG-INFO +146 -0
- better_optimize-0.1.4/README.md +124 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize/_version.py +3 -3
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize/basinhopping.py +70 -15
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize/minimize.py +14 -7
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize/root.py +7 -6
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize/utilities.py +208 -22
- better_optimize-0.1.4/better_optimize.egg-info/PKG-INFO +146 -0
- better_optimize-0.1.2/PKG-INFO +0 -30
- better_optimize-0.1.2/README.md +0 -8
- better_optimize-0.1.2/better_optimize.egg-info/PKG-INFO +0 -30
- {better_optimize-0.1.2 → better_optimize-0.1.4}/LICENSE +0 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize/__init__.py +0 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize/constants.py +0 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize/wrapper.py +0 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize.egg-info/SOURCES.txt +0 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize.egg-info/dependency_links.txt +0 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize.egg-info/requires.txt +0 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/better_optimize.egg-info/top_level.txt +0 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/pyproject.toml +0 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/setup.cfg +0 -0
- {better_optimize-0.1.2 → better_optimize-0.1.4}/setup.py +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: better_optimize
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: A drop-in replacement for scipy optimize functions with quality of life improvements
|
|
5
|
+
License: MIT License
|
|
6
|
+
Project-URL: Homepage, https://github.com/jessegrabowski/better_optimize
|
|
7
|
+
Project-URL: Repository, https://github.com/jessegrabowski/better_optimize.git
|
|
8
|
+
Project-URL: Issues, https://github.com/jessegrabowski/better_optimize/issues
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
18
|
+
Requires-Python: <3.14,>=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Provides-Extra: tests
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
|
|
23
|
+
# Better Optimization!
|
|
24
|
+
|
|
25
|
+
`better_optimize` is a friendlier front-end to scipy's `optimize.minimize` and `optimize.root` functions. Features
|
|
26
|
+
include:
|
|
27
|
+
|
|
28
|
+
- Progress bar!
|
|
29
|
+
- Early stopping!
|
|
30
|
+
- Better propagation of common arguments (`maxiters`, `tol`)!
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
To install `better_optimize`, simply use conda:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
conda install -c conda-forge better_optimize
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or, if you prefer pip:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install better_optimize
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## What does `better_optimize` provide over basic scipy?
|
|
47
|
+
|
|
48
|
+
### 1. Progress Bars
|
|
49
|
+
|
|
50
|
+
All optimization routines in `better_optimize` can display a rich, informative progress bar using the [rich](https://github.com/Textualize/rich) library. This includes:
|
|
51
|
+
|
|
52
|
+
- Iteration counts, elapsed time, and objective values.
|
|
53
|
+
- Gradient and Hessian norms (when available).
|
|
54
|
+
- Separate progress bars for global (basinhopping) and local (minimizer) steps.
|
|
55
|
+
- Toggleable display for headless or script environments.
|
|
56
|
+
|
|
57
|
+
### 2. Flat and Generalized Keyword Arguments
|
|
58
|
+
|
|
59
|
+
- No more nested `options` dictionaries! You can pass `tol`, `maxiter`, and other common options directly as top-level keyword arguments.
|
|
60
|
+
- `better_optimize` automatically sorts and promotes these arguments to the correct place for each optimizer.
|
|
61
|
+
- Generalizes argument handling: always provides `tol` and `maxiter` (or their equivalents) to the optimizer, even if you forget.
|
|
62
|
+
|
|
63
|
+
### 3. Argument Checking and Validation
|
|
64
|
+
|
|
65
|
+
- Automatic checking of provided gradient (`jac`), Hessian (`hess`), and Hessian-vector (`hessp`) functions.
|
|
66
|
+
- Warns if you provide unnecessary or unused arguments for a given method.
|
|
67
|
+
- Detects and handles fused objective functions (e.g., functions returning `(loss, grad)` or `(loss, grad, hess)` tuples).
|
|
68
|
+
- Ensures that the correct function signatures and return types are used for each optimizer.
|
|
69
|
+
|
|
70
|
+
### 4. LRUCache1 for Fused Functions
|
|
71
|
+
|
|
72
|
+
- Provides an `LRUCache1` utility to cache the results of expensive objective/gradient/Hessian computations.
|
|
73
|
+
- Especially useful for triple-fused functions that return value, gradient, and Hessian together, avoiding redundant computation.
|
|
74
|
+
- Totally invisible -- just pass a function with 3 return values. Seamlessly integrated into the optimization workflow.
|
|
75
|
+
|
|
76
|
+
### 5. Robust Basin-Hopping with Failure Tolerance
|
|
77
|
+
|
|
78
|
+
- Enhanced `basinhopping` implementation allows you to continue even if the local minimizer fails.
|
|
79
|
+
- Optionally accepts and stores failed minimizer results if they improve the global minimum.
|
|
80
|
+
- Useful for noisy or non-smooth objective functions where local minimization may occasionally fail.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Example Usage
|
|
85
|
+
|
|
86
|
+
### Simple Example
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from better_optimize import minimize
|
|
90
|
+
|
|
91
|
+
def rosenbrock(x):
|
|
92
|
+
return sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
|
|
93
|
+
|
|
94
|
+
result = minimize(
|
|
95
|
+
rosenbrock,
|
|
96
|
+
x0=[-1, 2],
|
|
97
|
+
method="L-BFGS-B",
|
|
98
|
+
tol=1e-6,
|
|
99
|
+
maxiter=1000,
|
|
100
|
+
progressbar=True, # Show a rich progress bar!
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```shell
|
|
105
|
+
Minimizing Elapsed Iteration Objective ||grad||
|
|
106
|
+
──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
107
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0:00:00 721/721 0.34271757 0.92457651
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The result object is a standard `OptimizeResult` from `scipy.optimize`, so there are no surprises there!
|
|
111
|
+
|
|
112
|
+
### Triple-Fused Function using Pytensor
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from better_optimize import minimize
|
|
116
|
+
import pytensor.tensor as pt
|
|
117
|
+
from pytensor import function
|
|
118
|
+
import numpy as np
|
|
119
|
+
|
|
120
|
+
x = pt.vector('x')
|
|
121
|
+
value = pt.sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
|
|
122
|
+
grad = pt.grad(value, x)
|
|
123
|
+
hess = pt.hessian(value, x)
|
|
124
|
+
|
|
125
|
+
fused_fn = function([x], [value, grad, hess])
|
|
126
|
+
x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
|
|
127
|
+
|
|
128
|
+
result = minimize(
|
|
129
|
+
fused_fn, # No need to set flags separately, `better_optimize` handles it!
|
|
130
|
+
x0=x0,
|
|
131
|
+
method="Newton-CG",
|
|
132
|
+
tol=1e-6,
|
|
133
|
+
maxiter=1000,
|
|
134
|
+
progressbar=True, # Show a rich progress bar!
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Many sub-computations are repeated between the objective, gradient, and hessian functions. Scipy allows you to pass a
|
|
139
|
+
fused value_and_grad function, but `better_optimize` also lets you pass a triple-fused value_grad_and_hess function.
|
|
140
|
+
This avoids redundant computation and speeds up the optimization process.
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## Contributing
|
|
144
|
+
|
|
145
|
+
We welcome contributions! If you find a bug, have a feature request, or want to improve the documentation, please open
|
|
146
|
+
an issue or submit a pull request on GitHub.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Better Optimization!
|
|
2
|
+
|
|
3
|
+
`better_optimize` is a friendlier front-end to scipy's `optimize.minimize` and `optimize.root` functions. Features
|
|
4
|
+
include:
|
|
5
|
+
|
|
6
|
+
- Progress bar!
|
|
7
|
+
- Early stopping!
|
|
8
|
+
- Better propagation of common arguments (`maxiters`, `tol`)!
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
To install `better_optimize`, simply use conda:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
conda install -c conda-forge better_optimize
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or, if you prefer pip:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install better_optimize
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What does `better_optimize` provide over basic scipy?
|
|
25
|
+
|
|
26
|
+
### 1. Progress Bars
|
|
27
|
+
|
|
28
|
+
All optimization routines in `better_optimize` can display a rich, informative progress bar using the [rich](https://github.com/Textualize/rich) library. This includes:
|
|
29
|
+
|
|
30
|
+
- Iteration counts, elapsed time, and objective values.
|
|
31
|
+
- Gradient and Hessian norms (when available).
|
|
32
|
+
- Separate progress bars for global (basinhopping) and local (minimizer) steps.
|
|
33
|
+
- Toggleable display for headless or script environments.
|
|
34
|
+
|
|
35
|
+
### 2. Flat and Generalized Keyword Arguments
|
|
36
|
+
|
|
37
|
+
- No more nested `options` dictionaries! You can pass `tol`, `maxiter`, and other common options directly as top-level keyword arguments.
|
|
38
|
+
- `better_optimize` automatically sorts and promotes these arguments to the correct place for each optimizer.
|
|
39
|
+
- Generalizes argument handling: always provides `tol` and `maxiter` (or their equivalents) to the optimizer, even if you forget.
|
|
40
|
+
|
|
41
|
+
### 3. Argument Checking and Validation
|
|
42
|
+
|
|
43
|
+
- Automatic checking of provided gradient (`jac`), Hessian (`hess`), and Hessian-vector (`hessp`) functions.
|
|
44
|
+
- Warns if you provide unnecessary or unused arguments for a given method.
|
|
45
|
+
- Detects and handles fused objective functions (e.g., functions returning `(loss, grad)` or `(loss, grad, hess)` tuples).
|
|
46
|
+
- Ensures that the correct function signatures and return types are used for each optimizer.
|
|
47
|
+
|
|
48
|
+
### 4. LRUCache1 for Fused Functions
|
|
49
|
+
|
|
50
|
+
- Provides an `LRUCache1` utility to cache the results of expensive objective/gradient/Hessian computations.
|
|
51
|
+
- Especially useful for triple-fused functions that return value, gradient, and Hessian together, avoiding redundant computation.
|
|
52
|
+
- Totally invisible -- just pass a function with 3 return values. Seamlessly integrated into the optimization workflow.
|
|
53
|
+
|
|
54
|
+
### 5. Robust Basin-Hopping with Failure Tolerance
|
|
55
|
+
|
|
56
|
+
- Enhanced `basinhopping` implementation allows you to continue even if the local minimizer fails.
|
|
57
|
+
- Optionally accepts and stores failed minimizer results if they improve the global minimum.
|
|
58
|
+
- Useful for noisy or non-smooth objective functions where local minimization may occasionally fail.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Example Usage
|
|
63
|
+
|
|
64
|
+
### Simple Example
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from better_optimize import minimize
|
|
68
|
+
|
|
69
|
+
def rosenbrock(x):
|
|
70
|
+
return sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
|
|
71
|
+
|
|
72
|
+
result = minimize(
|
|
73
|
+
rosenbrock,
|
|
74
|
+
x0=[-1, 2],
|
|
75
|
+
method="L-BFGS-B",
|
|
76
|
+
tol=1e-6,
|
|
77
|
+
maxiter=1000,
|
|
78
|
+
progressbar=True, # Show a rich progress bar!
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```shell
|
|
83
|
+
Minimizing Elapsed Iteration Objective ||grad||
|
|
84
|
+
──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
85
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0:00:00 721/721 0.34271757 0.92457651
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The result object is a standard `OptimizeResult` from `scipy.optimize`, so there are no surprises there!
|
|
89
|
+
|
|
90
|
+
### Triple-Fused Function using Pytensor
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from better_optimize import minimize
|
|
94
|
+
import pytensor.tensor as pt
|
|
95
|
+
from pytensor import function
|
|
96
|
+
import numpy as np
|
|
97
|
+
|
|
98
|
+
x = pt.vector('x')
|
|
99
|
+
value = pt.sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
|
|
100
|
+
grad = pt.grad(value, x)
|
|
101
|
+
hess = pt.hessian(value, x)
|
|
102
|
+
|
|
103
|
+
fused_fn = function([x], [value, grad, hess])
|
|
104
|
+
x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
|
|
105
|
+
|
|
106
|
+
result = minimize(
|
|
107
|
+
fused_fn, # No need to set flags separately, `better_optimize` handles it!
|
|
108
|
+
x0=x0,
|
|
109
|
+
method="Newton-CG",
|
|
110
|
+
tol=1e-6,
|
|
111
|
+
maxiter=1000,
|
|
112
|
+
progressbar=True, # Show a rich progress bar!
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Many sub-computations are repeated between the objective, gradient, and hessian functions. Scipy allows you to pass a
|
|
117
|
+
fused value_and_grad function, but `better_optimize` also lets you pass a triple-fused value_grad_and_hess function.
|
|
118
|
+
This avoids redundant computation and speeds up the optimization process.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Contributing
|
|
122
|
+
|
|
123
|
+
We welcome contributions! If you find a bug, have a feature request, or want to improve the documentation, please open
|
|
124
|
+
an issue or submit a pull request on GitHub.
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2025-
|
|
11
|
+
"date": "2025-06-28T08:22:47+0800",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.1.
|
|
14
|
+
"full-revisionid": "323b00a655190dbb83a66ff9c7ce55df30db16c4",
|
|
15
|
+
"version": "0.1.4"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -22,7 +22,13 @@ from scipy.optimize._basinhopping import (
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
from better_optimize.minimize import minimize
|
|
25
|
-
from better_optimize.utilities import
|
|
25
|
+
from better_optimize.utilities import (
|
|
26
|
+
LRUCache1,
|
|
27
|
+
ToggleableProgress,
|
|
28
|
+
check_f_is_fused_minimize,
|
|
29
|
+
determine_maxiter,
|
|
30
|
+
validate_provided_functions_minimize,
|
|
31
|
+
)
|
|
26
32
|
|
|
27
33
|
|
|
28
34
|
def initialize_progress_bar(progressbar, use_jac=True, use_hess=False):
|
|
@@ -166,12 +172,36 @@ def basinhopping(
|
|
|
166
172
|
# set up minimizer
|
|
167
173
|
if minimizer_kwargs is None:
|
|
168
174
|
minimizer_kwargs = dict()
|
|
175
|
+
minimizer_kwargs = minimizer_kwargs.copy() # avoid mutating caller's dict
|
|
176
|
+
|
|
177
|
+
has_fused_f_and_grad, has_fused_f_grad_hess = check_f_is_fused_minimize(
|
|
178
|
+
func, x0, minimizer_kwargs.get("args", ())
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
jac = minimizer_kwargs.pop("jac", None)
|
|
182
|
+
hess = minimizer_kwargs.pop("hess", None)
|
|
183
|
+
hessp = minimizer_kwargs.pop("hessp", None)
|
|
184
|
+
method = minimizer_kwargs.pop("method", "L-BFGS-B")
|
|
185
|
+
args = minimizer_kwargs.pop("args", ())
|
|
186
|
+
|
|
187
|
+
# Use the validation helper to determine which functions to use
|
|
188
|
+
use_jac, use_hess, use_hessp = validate_provided_functions_minimize(
|
|
189
|
+
method,
|
|
190
|
+
jac,
|
|
191
|
+
hess,
|
|
192
|
+
hessp,
|
|
193
|
+
has_fused_f_and_grad,
|
|
194
|
+
has_fused_f_grad_hess,
|
|
195
|
+
verbose=verbose,
|
|
196
|
+
)
|
|
169
197
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
198
|
+
f_returns_list = has_fused_f_and_grad or has_fused_f_grad_hess
|
|
199
|
+
f_cached = LRUCache1(func, f_returns_list=f_returns_list, copy_x=False, dtype=x0.dtype)
|
|
200
|
+
|
|
201
|
+
# If triple-fused, use hess from cache
|
|
202
|
+
if has_fused_f_grad_hess:
|
|
203
|
+
hess = f_cached.hess
|
|
173
204
|
|
|
174
|
-
use_jac = has_fused_f_and_grad or minimizer_kwargs.get("jac", False)
|
|
175
205
|
progress = initialize_progress_bar(progressbar, use_jac=use_jac, use_hess=False)
|
|
176
206
|
|
|
177
207
|
if progressbar:
|
|
@@ -188,6 +218,11 @@ def basinhopping(
|
|
|
188
218
|
hess_norm=0.0,
|
|
189
219
|
)
|
|
190
220
|
|
|
221
|
+
n_vars = x0.size if hasattr(x0, "size") else len(x0)
|
|
222
|
+
if "options" not in minimizer_kwargs:
|
|
223
|
+
minimizer_kwargs["options"] = {}
|
|
224
|
+
minimizer_maxiter, minimizer_kwargs = determine_maxiter(minimizer_kwargs, method, n_vars)
|
|
225
|
+
|
|
191
226
|
minimize_task = progress.add_task(
|
|
192
227
|
description="Minimize",
|
|
193
228
|
name="Minimize",
|
|
@@ -196,18 +231,28 @@ def basinhopping(
|
|
|
196
231
|
f_value=0.0,
|
|
197
232
|
grad_norm=0.0,
|
|
198
233
|
hess_norm=0.0,
|
|
234
|
+
total=minimizer_maxiter,
|
|
199
235
|
)
|
|
200
236
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
237
|
+
def wrapped_minimize(x0_inner, *args, **kwargs):
|
|
238
|
+
# Always keep the minimizer progress bar's total fixed at the requested maxiter
|
|
239
|
+
progress.update(minimize_task, total=minimizer_maxiter)
|
|
240
|
+
return minimize(
|
|
241
|
+
method=method,
|
|
242
|
+
f=f_cached.value_and_grad if has_fused_f_and_grad else f_cached.value,
|
|
243
|
+
x0=x0_inner,
|
|
244
|
+
args=args,
|
|
245
|
+
jac=None if not use_jac else (True if has_fused_f_and_grad else jac),
|
|
246
|
+
hess=None if not use_hess else hess,
|
|
247
|
+
hessp=None if not use_hessp else hessp,
|
|
248
|
+
progressbar=progress,
|
|
249
|
+
progress_task=minimize_task,
|
|
250
|
+
verbose=verbose,
|
|
251
|
+
**minimizer_kwargs,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
wrapped_minimizer = MinimizerWrapper(wrapped_minimize)
|
|
209
255
|
|
|
210
|
-
# set up step-taking algorithm
|
|
211
256
|
if take_step is not None:
|
|
212
257
|
if not callable(take_step):
|
|
213
258
|
raise TypeError("take_step must be callable")
|
|
@@ -268,9 +313,19 @@ def basinhopping(
|
|
|
268
313
|
|
|
269
314
|
grad_norm_at_min = 0.0
|
|
270
315
|
if use_jac:
|
|
271
|
-
|
|
316
|
+
grad_val = f_cached.grad(bh.x, *args)
|
|
272
317
|
grad_norm_at_min = np.linalg.norm(grad_val)
|
|
273
318
|
|
|
319
|
+
# Update the progress bar with initial values
|
|
320
|
+
progress.update(
|
|
321
|
+
bh_task,
|
|
322
|
+
advance=1,
|
|
323
|
+
step_size=take_step_wrapped.takestep.stepsize,
|
|
324
|
+
target_accept=take_step_wrapped.target_accept_rate,
|
|
325
|
+
f_value=bh.storage.get_lowest().fun,
|
|
326
|
+
grad_norm=grad_norm_at_min,
|
|
327
|
+
)
|
|
328
|
+
|
|
274
329
|
with progress:
|
|
275
330
|
for i in range(niter):
|
|
276
331
|
if progressbar:
|
|
@@ -10,7 +10,8 @@ from scipy.sparse.linalg import LinearOperator
|
|
|
10
10
|
|
|
11
11
|
from better_optimize.constants import minimize_method
|
|
12
12
|
from better_optimize.utilities import (
|
|
13
|
-
|
|
13
|
+
LRUCache1,
|
|
14
|
+
check_f_is_fused_minimize,
|
|
14
15
|
determine_maxiter,
|
|
15
16
|
determine_tolerance,
|
|
16
17
|
kwargs_to_options,
|
|
@@ -69,11 +70,18 @@ def minimize(
|
|
|
69
70
|
Optimization result
|
|
70
71
|
|
|
71
72
|
"""
|
|
72
|
-
has_fused_f_and_grad =
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
has_fused_f_and_grad, has_fused_f_grad_hess = check_f_is_fused_minimize(f, x0, args)
|
|
74
|
+
|
|
75
|
+
use_grad, use_hess, use_hessp = validate_provided_functions_minimize(
|
|
76
|
+
method, jac, hess, hessp, has_fused_f_and_grad, has_fused_f_grad_hess, verbose=verbose
|
|
75
77
|
)
|
|
76
78
|
|
|
79
|
+
f_returns_list = has_fused_f_and_grad or has_fused_f_grad_hess
|
|
80
|
+
f_cached = LRUCache1(f, f_returns_list=f_returns_list, copy_x=False, dtype=x0.dtype)
|
|
81
|
+
|
|
82
|
+
if has_fused_f_grad_hess:
|
|
83
|
+
hess = f_cached.hess
|
|
84
|
+
|
|
77
85
|
options = optimizer_kwargs.pop("options", {})
|
|
78
86
|
optimizer_kwargs["options"] = options
|
|
79
87
|
|
|
@@ -83,12 +91,11 @@ def minimize(
|
|
|
83
91
|
|
|
84
92
|
# Test hessian function -- if it returns a LinearOperator, it can't be used inside the wrapper
|
|
85
93
|
args = () if args is None else args
|
|
86
|
-
use_hess =
|
|
87
|
-
use_hessp = hessp is not None
|
|
94
|
+
use_hess = use_hess and not isinstance(hess(x0, *args), LinearOperator)
|
|
88
95
|
|
|
89
96
|
objective = ObjectiveWrapper(
|
|
90
97
|
maxeval=maxiter,
|
|
91
|
-
f=
|
|
98
|
+
f=f_cached.value_and_grad if has_fused_f_and_grad else f_cached.value,
|
|
92
99
|
jac=jac,
|
|
93
100
|
hess=hess if use_hess else None,
|
|
94
101
|
args=args,
|
|
@@ -8,7 +8,8 @@ from scipy.optimize import root as sp_root
|
|
|
8
8
|
|
|
9
9
|
from better_optimize.constants import root_method
|
|
10
10
|
from better_optimize.utilities import (
|
|
11
|
-
|
|
11
|
+
LRUCache1,
|
|
12
|
+
check_f_is_fused_root,
|
|
12
13
|
determine_maxiter,
|
|
13
14
|
determine_tolerance,
|
|
14
15
|
kwargs_to_jac_options,
|
|
@@ -65,10 +66,10 @@ def root(
|
|
|
65
66
|
Optimization result
|
|
66
67
|
|
|
67
68
|
"""
|
|
68
|
-
has_fused_f_and_grad =
|
|
69
|
-
validate_provided_functions_root(
|
|
70
|
-
|
|
71
|
-
)
|
|
69
|
+
has_fused_f_and_grad = check_f_is_fused_root(f, x0, args)
|
|
70
|
+
validate_provided_functions_root(method, f, jac, has_fused_f_and_grad, verbose=verbose)
|
|
71
|
+
|
|
72
|
+
f_cached = LRUCache1(f, f_returns_list=has_fused_f_and_grad, copy_x=True, dtype=x0.dtype)
|
|
72
73
|
|
|
73
74
|
options = optimizer_kwargs.pop("options", {})
|
|
74
75
|
optimizer_kwargs["options"] = options
|
|
@@ -80,7 +81,7 @@ def root(
|
|
|
80
81
|
|
|
81
82
|
objective = ObjectiveWrapper(
|
|
82
83
|
maxeval=maxiter,
|
|
83
|
-
f=
|
|
84
|
+
f=f_cached.value_and_grad if has_fused_f_and_grad else f_cached.value,
|
|
84
85
|
jac=jac,
|
|
85
86
|
args=args,
|
|
86
87
|
progressbar=progressbar,
|
|
@@ -154,19 +154,36 @@ def validate_provided_functions_minimize(
|
|
|
154
154
|
f_hess: Callable[[np.ndarray], np.ndarray] | None,
|
|
155
155
|
f_hessp: Callable[[np.ndarray], np.ndarray] | None,
|
|
156
156
|
has_fused_f_and_grad: bool,
|
|
157
|
+
has_fused_f_grad_hess: bool,
|
|
157
158
|
verbose=True,
|
|
158
|
-
) ->
|
|
159
|
+
) -> tuple[bool, bool, bool]:
|
|
159
160
|
has_grad, has_hess, has_hessp = map(lambda f: f is not None, [f_grad, f_hess, f_hessp])
|
|
160
161
|
uses_grad, uses_hess, uses_hessp, *_ = MINIMIZE_MODE_KWARGS[method].values()
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
# Handle fused outputs first
|
|
164
|
+
if has_fused_f_grad_hess:
|
|
165
|
+
if verbose and (has_grad or has_hess):
|
|
166
|
+
_log.warning(
|
|
167
|
+
"Objective function returns a tuple (interpreted as (loss, gradient, hessian)), "
|
|
168
|
+
"but a gradient or hessian function was also provided. The gradient and hessian functions will be ignored."
|
|
169
|
+
)
|
|
170
|
+
if verbose and has_hessp:
|
|
171
|
+
_log.warning(
|
|
172
|
+
"Objective function returns a tuple (interpreted as (loss, gradient, hessian)), "
|
|
173
|
+
"but a hessian-vector product function was also provided. The hessian-vector product function will be ignored."
|
|
174
|
+
)
|
|
175
|
+
# Triple-fused disables external grad/hess/hessp
|
|
176
|
+
has_grad = True
|
|
177
|
+
has_hess = True
|
|
178
|
+
has_hessp = False
|
|
167
179
|
|
|
168
180
|
elif has_fused_f_and_grad:
|
|
169
|
-
|
|
181
|
+
if verbose and has_grad:
|
|
182
|
+
_log.warning(
|
|
183
|
+
"Objective function returns a tuple (interpreted as (loss, gradient)), "
|
|
184
|
+
"but a gradient function was also provided. The gradient function will be ignored."
|
|
185
|
+
)
|
|
186
|
+
has_grad = True # fused (loss, grad) disables external grad
|
|
170
187
|
|
|
171
188
|
if method not in MINIMIZE_MODE_KWARGS:
|
|
172
189
|
raise ValueError(
|
|
@@ -179,31 +196,33 @@ def validate_provided_functions_minimize(
|
|
|
179
196
|
"except trust-exact and dogleg, use_hessp is preferred."
|
|
180
197
|
)
|
|
181
198
|
|
|
182
|
-
if has_grad and not uses_grad
|
|
199
|
+
if verbose and (has_grad and not uses_grad):
|
|
183
200
|
_log.warning(
|
|
184
201
|
f"Gradient provided but not used by method {method}. Gradients will still be evaluated at each "
|
|
185
|
-
f"
|
|
202
|
+
f"optimizer step and the norm will be reported as a diagnostic. For large problems, this may be "
|
|
186
203
|
f"computationally intensive."
|
|
187
204
|
)
|
|
188
205
|
|
|
189
|
-
if (has_hess and not uses_hess) or (has_hessp and not uses_hessp)
|
|
206
|
+
if verbose and ((has_hess and not uses_hess) or (has_hessp and not uses_hessp)):
|
|
190
207
|
_log.warning(
|
|
191
|
-
f"Hessian provided but not used by method {method}.
|
|
192
|
-
f"
|
|
193
|
-
f"be computationally intensive."
|
|
208
|
+
f"Hessian or Hessian-vector product provided but not used by method {method}. "
|
|
209
|
+
f"The full Hessian or Hessian-vector product will still be evaluated at each optimizer step and "
|
|
210
|
+
f"the norm will be reported as a diagnostic. For large problems, this may be computationally intensive."
|
|
194
211
|
)
|
|
195
212
|
|
|
196
|
-
if has_hess and not has_hessp and uses_hessp and uses_hess
|
|
213
|
+
if verbose and (has_hess and not has_hessp and uses_hessp and uses_hess):
|
|
197
214
|
_log.warning(
|
|
198
|
-
f"You provided a function to compute the full
|
|
199
|
-
f"
|
|
215
|
+
f"You provided a function to compute the full Hessian, but method {method} allows the use of a "
|
|
216
|
+
f"Hessian-vector product instead. Consider passing hessp instead -- this may be significantly "
|
|
200
217
|
f"more efficient."
|
|
201
218
|
)
|
|
202
219
|
|
|
220
|
+
return has_grad, has_hess, has_hessp
|
|
221
|
+
|
|
203
222
|
|
|
204
223
|
def validate_provided_functions_root(
|
|
205
224
|
method: root_method, f, jac, has_fused_f_and_grad: bool, verbose: bool = True
|
|
206
|
-
):
|
|
225
|
+
) -> bool:
|
|
207
226
|
has_jac = jac is not None
|
|
208
227
|
info_dict = get_option_kwargs(method)
|
|
209
228
|
uses_jac = info_dict["uses_jac"]
|
|
@@ -224,19 +243,87 @@ def validate_provided_functions_root(
|
|
|
224
243
|
f"computationally intensive."
|
|
225
244
|
)
|
|
226
245
|
|
|
246
|
+
return has_jac
|
|
247
|
+
|
|
227
248
|
|
|
228
|
-
def
|
|
249
|
+
def check_f_is_fused_minimize(f, x0, args) -> tuple[bool, bool]:
|
|
250
|
+
"""
|
|
251
|
+
Check if the minimize objective function returns fused outputs (value, grad[, hess]).
|
|
252
|
+
Returns (is_fused, has_hess).
|
|
253
|
+
"""
|
|
229
254
|
args = () if args is None else args
|
|
230
255
|
output = f(x0, *args)
|
|
256
|
+
|
|
257
|
+
if not isinstance(output, tuple | list):
|
|
258
|
+
return (False, False)
|
|
259
|
+
|
|
260
|
+
if len(output) == 2:
|
|
261
|
+
value, grad = output
|
|
262
|
+
hess = None
|
|
263
|
+
ret_val = (True, False)
|
|
264
|
+
elif len(output) == 3:
|
|
265
|
+
value, grad, hess = output
|
|
266
|
+
ret_val = (True, True)
|
|
267
|
+
else:
|
|
268
|
+
raise ValueError(
|
|
269
|
+
"Objective function should return either a scalar loss, a two-tuple of (loss, gradient), "
|
|
270
|
+
"or three-tuple of (loss, gradient, hessian)."
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if not (
|
|
274
|
+
np.isscalar(value)
|
|
275
|
+
or (hasattr(value, "shape") and (value.shape == () or value.shape == (1,)))
|
|
276
|
+
):
|
|
277
|
+
raise ValueError(
|
|
278
|
+
"First value returned by the objective function must be a scalar (float or 0-d array)."
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if not (hasattr(grad, "ndim") and grad.ndim == 1):
|
|
282
|
+
raise ValueError(
|
|
283
|
+
"Second value returned by the objective function must be a 1d array representing the gradient."
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if hess is not None:
|
|
287
|
+
if not (hasattr(hess, "ndim") and hess.ndim == 2):
|
|
288
|
+
raise ValueError(
|
|
289
|
+
"Third value returned by the objective function must be a 2d array representing the Hessian."
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
return ret_val
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def check_f_is_fused_root(f, x0, args) -> bool:
|
|
296
|
+
"""
|
|
297
|
+
Check if the root objective function returns fused outputs (value[, jac]), and returns True if it does.
|
|
298
|
+
"""
|
|
299
|
+
args = () if args is None else args
|
|
300
|
+
output = f(x0, *args)
|
|
301
|
+
|
|
231
302
|
if not isinstance(output, tuple | list):
|
|
232
303
|
return False
|
|
233
304
|
|
|
234
|
-
|
|
235
|
-
|
|
305
|
+
if len(output) == 2:
|
|
306
|
+
value, jac = output
|
|
307
|
+
ret_val = True
|
|
308
|
+
elif len(output) == 1:
|
|
309
|
+
value = output[0]
|
|
310
|
+
jac = None
|
|
311
|
+
ret_val = True
|
|
312
|
+
else:
|
|
236
313
|
raise ValueError(
|
|
237
|
-
"Objective function should return either a
|
|
314
|
+
"Objective function should return either a 1d array or a two-tuple of (value, jacobian)."
|
|
238
315
|
)
|
|
239
|
-
|
|
316
|
+
|
|
317
|
+
if not (hasattr(value, "ndim") and value.ndim == 1):
|
|
318
|
+
raise ValueError("First value returned by the objective function must be a 1d array.")
|
|
319
|
+
|
|
320
|
+
if jac is not None:
|
|
321
|
+
if not (hasattr(jac, "ndim") and jac.ndim == 2):
|
|
322
|
+
raise ValueError(
|
|
323
|
+
"Second value returned by the objective function must be a 2d array representing the Jacobian."
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
return ret_val
|
|
240
327
|
|
|
241
328
|
|
|
242
329
|
def determine_maxiter(
|
|
@@ -314,3 +401,102 @@ def determine_tolerance(optimizer_kwargs: dict, method: minimize_method | root_m
|
|
|
314
401
|
optimizer_kwargs["options"][tol_name] = tol
|
|
315
402
|
|
|
316
403
|
return optimizer_kwargs
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class LRUCache1:
|
|
407
|
+
"""
|
|
408
|
+
Simple LRU cache with a memory size of 1.
|
|
409
|
+
|
|
410
|
+
This cache is only usable for a function that takes a single input `x` and returns a single output. The
|
|
411
|
+
function can also take any number of additional arguments `*args`, but these are assumed to be constant
|
|
412
|
+
between function calls.
|
|
413
|
+
|
|
414
|
+
The purpose of this cache is to allow for Hessian computation to be reused when calling scipy.optimize functions.
|
|
415
|
+
It is very often the case that some sub-computations are repeated between the objective, gradient, and hessian
|
|
416
|
+
functions, but by default scipy only allows for the objective and gradient to be fused.
|
|
417
|
+
|
|
418
|
+
By using this cache, all 3 functions can be fused, which can significantly speed up the optimization process for
|
|
419
|
+
expensive functions.
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
def __init__(
|
|
423
|
+
self, fn, f_returns_list: bool = False, copy_x: bool = False, dtype: str | None = None
|
|
424
|
+
):
|
|
425
|
+
self.fn = fn
|
|
426
|
+
self.last_x = None
|
|
427
|
+
self.last_result = None
|
|
428
|
+
self.copy_x = copy_x
|
|
429
|
+
self.f_returns_list = f_returns_list
|
|
430
|
+
|
|
431
|
+
# Scipy does not respect dtypes *at all*, so we have to force it ourselves.
|
|
432
|
+
self.dtype = dtype
|
|
433
|
+
|
|
434
|
+
self.cache_hits = 0
|
|
435
|
+
self.cache_misses = 0
|
|
436
|
+
|
|
437
|
+
self.value_calls = 0
|
|
438
|
+
self.grad_calls = 0
|
|
439
|
+
self.value_and_grad_calls = 0
|
|
440
|
+
self.hess_calls = 0
|
|
441
|
+
|
|
442
|
+
def __call__(self, x, *args):
|
|
443
|
+
"""
|
|
444
|
+
Call the cached function with the given input `x` and additional arguments `*args`.
|
|
445
|
+
|
|
446
|
+
If the input `x` is the same as the last input, return the cached result. Otherwise update the cache with the
|
|
447
|
+
new input and result.
|
|
448
|
+
"""
|
|
449
|
+
x = x.astype(self.dtype)
|
|
450
|
+
|
|
451
|
+
if self.last_result is None or not (x == self.last_x).all():
|
|
452
|
+
self.cache_misses += 1
|
|
453
|
+
|
|
454
|
+
# scipy.optimize.root changes x in place, so the cache has to copy it, otherwise we get false
|
|
455
|
+
# cache hits and optimization always fails.
|
|
456
|
+
if self.copy_x:
|
|
457
|
+
x = x.copy()
|
|
458
|
+
self.last_x = x
|
|
459
|
+
|
|
460
|
+
result = self.fn(x, *args)
|
|
461
|
+
self.last_result = result
|
|
462
|
+
|
|
463
|
+
return result
|
|
464
|
+
|
|
465
|
+
else:
|
|
466
|
+
self.cache_hits += 1
|
|
467
|
+
return self.last_result
|
|
468
|
+
|
|
469
|
+
def value(self, x, *args):
|
|
470
|
+
self.value_calls += 1
|
|
471
|
+
if not self.f_returns_list:
|
|
472
|
+
return self(x, *args)
|
|
473
|
+
else:
|
|
474
|
+
return self(x, *args)[0]
|
|
475
|
+
|
|
476
|
+
def grad(self, x, *args):
|
|
477
|
+
self.grad_calls += 1
|
|
478
|
+
return self(x, *args)[1]
|
|
479
|
+
|
|
480
|
+
def value_and_grad(self, x, *args):
|
|
481
|
+
self.value_and_grad_calls += 1
|
|
482
|
+
return self(x, *args)[:2]
|
|
483
|
+
|
|
484
|
+
def hess(self, x, *args):
|
|
485
|
+
self.hess_calls += 1
|
|
486
|
+
return self(x, *args)[-1]
|
|
487
|
+
|
|
488
|
+
def report(self):
|
|
489
|
+
_log.info(f"Value and Grad calls: {self.value_and_grad_calls}")
|
|
490
|
+
_log.info(f"Hess Calls: {self.hess_calls}")
|
|
491
|
+
_log.info(f"Hits: {self.cache_hits}")
|
|
492
|
+
_log.info(f"Misses: {self.cache_misses}")
|
|
493
|
+
|
|
494
|
+
def clear_cache(self):
|
|
495
|
+
self.last_x = None
|
|
496
|
+
self.last_result = None
|
|
497
|
+
self.cache_hits = 0
|
|
498
|
+
self.cache_misses = 0
|
|
499
|
+
self.value_calls = 0
|
|
500
|
+
self.grad_calls = 0
|
|
501
|
+
self.value_and_grad_calls = 0
|
|
502
|
+
self.hess_calls = 0
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: better-optimize
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: A drop-in replacement for scipy optimize functions with quality of life improvements
|
|
5
|
+
License: MIT License
|
|
6
|
+
Project-URL: Homepage, https://github.com/jessegrabowski/better_optimize
|
|
7
|
+
Project-URL: Repository, https://github.com/jessegrabowski/better_optimize.git
|
|
8
|
+
Project-URL: Issues, https://github.com/jessegrabowski/better_optimize/issues
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
18
|
+
Requires-Python: <3.14,>=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Provides-Extra: tests
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
|
|
23
|
+
# Better Optimization!
|
|
24
|
+
|
|
25
|
+
`better_optimize` is a friendlier front-end to scipy's `optimize.minimize` and `optimize.root` functions. Features
|
|
26
|
+
include:
|
|
27
|
+
|
|
28
|
+
- Progress bar!
|
|
29
|
+
- Early stopping!
|
|
30
|
+
- Better propagation of common arguments (`maxiters`, `tol`)!
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
To install `better_optimize`, simply use conda:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
conda install -c conda-forge better_optimize
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or, if you prefer pip:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install better_optimize
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## What does `better_optimize` provide over basic scipy?
|
|
47
|
+
|
|
48
|
+
### 1. Progress Bars
|
|
49
|
+
|
|
50
|
+
All optimization routines in `better_optimize` can display a rich, informative progress bar using the [rich](https://github.com/Textualize/rich) library. This includes:
|
|
51
|
+
|
|
52
|
+
- Iteration counts, elapsed time, and objective values.
|
|
53
|
+
- Gradient and Hessian norms (when available).
|
|
54
|
+
- Separate progress bars for global (basinhopping) and local (minimizer) steps.
|
|
55
|
+
- Toggleable display for headless or script environments.
|
|
56
|
+
|
|
57
|
+
### 2. Flat and Generalized Keyword Arguments
|
|
58
|
+
|
|
59
|
+
- No more nested `options` dictionaries! You can pass `tol`, `maxiter`, and other common options directly as top-level keyword arguments.
|
|
60
|
+
- `better_optimize` automatically sorts and promotes these arguments to the correct place for each optimizer.
|
|
61
|
+
- Generalizes argument handling: always provides `tol` and `maxiter` (or their equivalents) to the optimizer, even if you forget.
|
|
62
|
+
|
|
63
|
+
### 3. Argument Checking and Validation
|
|
64
|
+
|
|
65
|
+
- Automatic checking of provided gradient (`jac`), Hessian (`hess`), and Hessian-vector (`hessp`) functions.
|
|
66
|
+
- Warns if you provide unnecessary or unused arguments for a given method.
|
|
67
|
+
- Detects and handles fused objective functions (e.g., functions returning `(loss, grad)` or `(loss, grad, hess)` tuples).
|
|
68
|
+
- Ensures that the correct function signatures and return types are used for each optimizer.
|
|
69
|
+
|
|
70
|
+
### 4. LRUCache1 for Fused Functions
|
|
71
|
+
|
|
72
|
+
- Provides an `LRUCache1` utility to cache the results of expensive objective/gradient/Hessian computations.
|
|
73
|
+
- Especially useful for triple-fused functions that return value, gradient, and Hessian together, avoiding redundant computation.
|
|
74
|
+
- Totally invisible -- just pass a function with 3 return values. Seamlessly integrated into the optimization workflow.
|
|
75
|
+
|
|
76
|
+
### 5. Robust Basin-Hopping with Failure Tolerance
|
|
77
|
+
|
|
78
|
+
- Enhanced `basinhopping` implementation allows you to continue even if the local minimizer fails.
|
|
79
|
+
- Optionally accepts and stores failed minimizer results if they improve the global minimum.
|
|
80
|
+
- Useful for noisy or non-smooth objective functions where local minimization may occasionally fail.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Example Usage
|
|
85
|
+
|
|
86
|
+
### Simple Example
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from better_optimize import minimize
|
|
90
|
+
|
|
91
|
+
def rosenbrock(x):
|
|
92
|
+
return sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
|
|
93
|
+
|
|
94
|
+
result = minimize(
|
|
95
|
+
rosenbrock,
|
|
96
|
+
x0=[-1, 2],
|
|
97
|
+
method="L-BFGS-B",
|
|
98
|
+
tol=1e-6,
|
|
99
|
+
maxiter=1000,
|
|
100
|
+
progressbar=True, # Show a rich progress bar!
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```shell
|
|
105
|
+
Minimizing Elapsed Iteration Objective ||grad||
|
|
106
|
+
──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
107
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0:00:00 721/721 0.34271757 0.92457651
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The result object is a standard `OptimizeResult` from `scipy.optimize`, so there are no surprises there!
|
|
111
|
+
|
|
112
|
+
### Triple-Fused Function using Pytensor
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from better_optimize import minimize
|
|
116
|
+
import pytensor.tensor as pt
|
|
117
|
+
from pytensor import function
|
|
118
|
+
import numpy as np
|
|
119
|
+
|
|
120
|
+
x = pt.vector('x')
|
|
121
|
+
value = pt.sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
|
|
122
|
+
grad = pt.grad(value, x)
|
|
123
|
+
hess = pt.hessian(value, x)
|
|
124
|
+
|
|
125
|
+
fused_fn = function([x], [value, grad, hess])
|
|
126
|
+
x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
|
|
127
|
+
|
|
128
|
+
result = minimize(
|
|
129
|
+
fused_fn, # No need to set flags separately, `better_optimize` handles it!
|
|
130
|
+
x0=x0,
|
|
131
|
+
method="Newton-CG",
|
|
132
|
+
tol=1e-6,
|
|
133
|
+
maxiter=1000,
|
|
134
|
+
progressbar=True, # Show a rich progress bar!
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Many sub-computations are repeated between the objective, gradient, and hessian functions. Scipy allows you to pass a
|
|
139
|
+
fused value_and_grad function, but `better_optimize` also lets you pass a triple-fused value_grad_and_hess function.
|
|
140
|
+
This avoids redundant computation and speeds up the optimization process.
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## Contributing
|
|
144
|
+
|
|
145
|
+
We welcome contributions! If you find a bug, have a feature request, or want to improve the documentation, please open
|
|
146
|
+
an issue or submit a pull request on GitHub.
|
better_optimize-0.1.2/PKG-INFO
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: better_optimize
|
|
3
|
-
Version: 0.1.2
|
|
4
|
-
Summary: A drop-in replacement for scipy optimize functions with quality of life improvements
|
|
5
|
-
License: MIT License
|
|
6
|
-
Project-URL: Homepage, https://github.com/jessegrabowski/better_optimize
|
|
7
|
-
Project-URL: Repository, https://github.com/jessegrabowski/better_optimize.git
|
|
8
|
-
Project-URL: Issues, https://github.com/jessegrabowski/better_optimize/issues
|
|
9
|
-
Classifier: Development Status :: 4 - Beta
|
|
10
|
-
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Classifier: Topic :: Scientific/Engineering
|
|
17
|
-
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
18
|
-
Requires-Python: <3.14,>=3.10
|
|
19
|
-
Description-Content-Type: text/markdown
|
|
20
|
-
Provides-Extra: tests
|
|
21
|
-
License-File: LICENSE
|
|
22
|
-
|
|
23
|
-
# Better Optimization!
|
|
24
|
-
|
|
25
|
-
`better_optimize` is a friendlier front-end to scipy's `optimize.minimize` and `optimize.root` functions. Features
|
|
26
|
-
include:
|
|
27
|
-
|
|
28
|
-
- Progress bar!
|
|
29
|
-
- Early stopping!
|
|
30
|
-
- Better propagation of common arguments (`maxiters`, `tol`)!
|
better_optimize-0.1.2/README.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: better-optimize
|
|
3
|
-
Version: 0.1.2
|
|
4
|
-
Summary: A drop-in replacement for scipy optimize functions with quality of life improvements
|
|
5
|
-
License: MIT License
|
|
6
|
-
Project-URL: Homepage, https://github.com/jessegrabowski/better_optimize
|
|
7
|
-
Project-URL: Repository, https://github.com/jessegrabowski/better_optimize.git
|
|
8
|
-
Project-URL: Issues, https://github.com/jessegrabowski/better_optimize/issues
|
|
9
|
-
Classifier: Development Status :: 4 - Beta
|
|
10
|
-
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Classifier: Topic :: Scientific/Engineering
|
|
17
|
-
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
18
|
-
Requires-Python: <3.14,>=3.10
|
|
19
|
-
Description-Content-Type: text/markdown
|
|
20
|
-
Provides-Extra: tests
|
|
21
|
-
License-File: LICENSE
|
|
22
|
-
|
|
23
|
-
# Better Optimization!
|
|
24
|
-
|
|
25
|
-
`better_optimize` is a friendlier front-end to scipy's `optimize.minimize` and `optimize.root` functions. Features
|
|
26
|
-
include:
|
|
27
|
-
|
|
28
|
-
- Progress bar!
|
|
29
|
-
- Early stopping!
|
|
30
|
-
- Better propagation of common arguments (`maxiters`, `tol`)!
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{better_optimize-0.1.2 → better_optimize-0.1.4}/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
|