better-optimize 0.0.0__tar.gz → 0.0.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.0.0 → better_optimize-0.0.2}/PKG-INFO +2 -1
- better_optimize-0.0.2/better_optimize/__init__.py +17 -0
- better_optimize-0.0.2/better_optimize/_version.py +21 -0
- better_optimize-0.0.2/better_optimize/constants.py +359 -0
- better_optimize-0.0.2/better_optimize/minimize.py +105 -0
- better_optimize-0.0.2/better_optimize/root.py +96 -0
- better_optimize-0.0.2/better_optimize/utilities.py +188 -0
- better_optimize-0.0.2/better_optimize/wrapper.py +180 -0
- {better_optimize-0.0.0 → better_optimize-0.0.2}/better_optimize.egg-info/PKG-INFO +2 -1
- better_optimize-0.0.2/better_optimize.egg-info/SOURCES.txt +15 -0
- {better_optimize-0.0.0 → better_optimize-0.0.2}/pyproject.toml +10 -1
- better_optimize-0.0.2/setup.py +19 -0
- better_optimize-0.0.0/better_optimize.egg-info/SOURCES.txt +0 -8
- better_optimize-0.0.0/setup.py +0 -3
- {better_optimize-0.0.0 → better_optimize-0.0.2}/README.md +0 -0
- {better_optimize-0.0.0 → better_optimize-0.0.2}/better_optimize.egg-info/dependency_links.txt +0 -0
- {better_optimize-0.0.0 → better_optimize-0.0.2}/better_optimize.egg-info/requires.txt +0 -0
- {better_optimize-0.0.0 → better_optimize-0.0.2}/better_optimize.egg-info/top_level.txt +0 -0
- {better_optimize-0.0.0 → better_optimize-0.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: better_optimize
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.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
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Topic :: Scientific/Engineering
|
|
16
16
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
17
|
+
Requires-Python: <3.13,>=3.10
|
|
17
18
|
Description-Content-Type: text/markdown
|
|
18
19
|
Provides-Extra: tests
|
|
19
20
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from better_optimize._version import get_versions
|
|
4
|
+
from better_optimize.minimize import minimize
|
|
5
|
+
from better_optimize.root import root
|
|
6
|
+
|
|
7
|
+
_log = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
if not logging.root.handlers:
|
|
10
|
+
_log.setLevel(logging.INFO)
|
|
11
|
+
if len(_log.handlers) == 0:
|
|
12
|
+
handler = logging.StreamHandler()
|
|
13
|
+
_log.addHandler(handler)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__version__ = get_versions()["version"]
|
|
17
|
+
__all__ = ["minimize", "root"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
# This file was generated by 'versioneer.py' (0.29) from
|
|
3
|
+
# revision-control system data, or from the parent directory name of an
|
|
4
|
+
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
|
5
|
+
# of this file.
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
version_json = '''
|
|
10
|
+
{
|
|
11
|
+
"date": "2024-08-19T18:20:27+0800",
|
|
12
|
+
"dirty": false,
|
|
13
|
+
"error": null,
|
|
14
|
+
"full-revisionid": "3595536b9c1ead4ca6ea916046e4379d86e36ecc",
|
|
15
|
+
"version": "0.0.2"
|
|
16
|
+
}
|
|
17
|
+
''' # END VERSION_JSON
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_versions():
|
|
21
|
+
return json.loads(version_json)
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
minimize_method = Literal[
|
|
4
|
+
"nelder-mead",
|
|
5
|
+
"powell",
|
|
6
|
+
"CG",
|
|
7
|
+
"BFGS",
|
|
8
|
+
"Newton-CG",
|
|
9
|
+
"L-BFGS-B",
|
|
10
|
+
"TNC",
|
|
11
|
+
"COBYLA",
|
|
12
|
+
"SLSQP",
|
|
13
|
+
"trust-constr",
|
|
14
|
+
"dogleg",
|
|
15
|
+
"trust-ncg",
|
|
16
|
+
"trust-exact",
|
|
17
|
+
"trust-krylov",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
root_method = Literal[
|
|
21
|
+
"hybr",
|
|
22
|
+
"lm",
|
|
23
|
+
"broyden1",
|
|
24
|
+
"broyden2",
|
|
25
|
+
"anderson",
|
|
26
|
+
"linearmixing",
|
|
27
|
+
"diagbroyden",
|
|
28
|
+
"excitingmixing",
|
|
29
|
+
"krylov",
|
|
30
|
+
"df-sane",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
TOLERANCES = ["xtol", "ftol", "gtol", "fatol", "xatol"]
|
|
34
|
+
|
|
35
|
+
MINIMIZE_MODE_KWARGS = {
|
|
36
|
+
"nelder-mead": {
|
|
37
|
+
"uses_grad": False,
|
|
38
|
+
"uses_hess": False,
|
|
39
|
+
"uses_hessp": False,
|
|
40
|
+
"valid_options": [
|
|
41
|
+
"disp",
|
|
42
|
+
"maxiter",
|
|
43
|
+
"maxfev",
|
|
44
|
+
"return_all",
|
|
45
|
+
"initial_simplex",
|
|
46
|
+
"xatol",
|
|
47
|
+
"fatol",
|
|
48
|
+
"adaptive",
|
|
49
|
+
"bounds",
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
"powell": {
|
|
53
|
+
"uses_grad": False,
|
|
54
|
+
"uses_hess": False,
|
|
55
|
+
"uses_hessp": False,
|
|
56
|
+
"valid_options": ["disp", "xtol", "ftol", "maxiter", "maxfev", "direc", "return_all"],
|
|
57
|
+
},
|
|
58
|
+
"CG": {
|
|
59
|
+
"uses_grad": True,
|
|
60
|
+
"uses_hess": False,
|
|
61
|
+
"uses_hessp": False,
|
|
62
|
+
"valid_options": [
|
|
63
|
+
"disp",
|
|
64
|
+
"maxiter",
|
|
65
|
+
"gtol",
|
|
66
|
+
"norm",
|
|
67
|
+
"eps",
|
|
68
|
+
"return_all",
|
|
69
|
+
"finite_diff_rel_step",
|
|
70
|
+
"c1",
|
|
71
|
+
"c2",
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
"BFGS": {
|
|
75
|
+
"uses_grad": True,
|
|
76
|
+
"uses_hess": False,
|
|
77
|
+
"uses_hessp": False,
|
|
78
|
+
"valid_options": [
|
|
79
|
+
"disp",
|
|
80
|
+
"maxiter",
|
|
81
|
+
"gtol",
|
|
82
|
+
"norm",
|
|
83
|
+
"eps",
|
|
84
|
+
"return_all",
|
|
85
|
+
"finite_diff_rel_step",
|
|
86
|
+
"xrtol",
|
|
87
|
+
"c1",
|
|
88
|
+
"c2",
|
|
89
|
+
"hess_inv0",
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
"Newton-CG": {
|
|
93
|
+
"uses_grad": True,
|
|
94
|
+
"uses_hess": True,
|
|
95
|
+
"uses_hessp": True,
|
|
96
|
+
"valid_options": ["disp", "xtol", "maxiter", "eps", "return_all", "c1", "c2"],
|
|
97
|
+
},
|
|
98
|
+
"L-BFGS-B": {
|
|
99
|
+
"uses_grad": True,
|
|
100
|
+
"uses_hess": False,
|
|
101
|
+
"uses_hessp": False,
|
|
102
|
+
"valid_options": [
|
|
103
|
+
"disp",
|
|
104
|
+
"maxcor",
|
|
105
|
+
"ftol",
|
|
106
|
+
"gtol",
|
|
107
|
+
"eps",
|
|
108
|
+
"maxfun",
|
|
109
|
+
"maxiter",
|
|
110
|
+
"iprint",
|
|
111
|
+
"maxls",
|
|
112
|
+
"finite_diff_rel_step",
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
"TNC": {
|
|
116
|
+
"uses_grad": True,
|
|
117
|
+
"uses_hess": False,
|
|
118
|
+
"uses_hessp": False,
|
|
119
|
+
"valid_options": [
|
|
120
|
+
"eps",
|
|
121
|
+
"scale",
|
|
122
|
+
"offset",
|
|
123
|
+
"disp",
|
|
124
|
+
"maxCGit",
|
|
125
|
+
"eta",
|
|
126
|
+
"stepmx",
|
|
127
|
+
"accuracy",
|
|
128
|
+
"minfev",
|
|
129
|
+
"ftol",
|
|
130
|
+
"xtol",
|
|
131
|
+
"gtol",
|
|
132
|
+
"rescale",
|
|
133
|
+
"finite_diff_rel_step",
|
|
134
|
+
"maxfun",
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
"COBYLA": {
|
|
138
|
+
"uses_grad": False,
|
|
139
|
+
"uses_hess": False,
|
|
140
|
+
"uses_hessp": False,
|
|
141
|
+
"valid_options": ["rhobeg", "tol", "disp", "maxiter", "catol"],
|
|
142
|
+
},
|
|
143
|
+
"SLSQP": {
|
|
144
|
+
"uses_grad": True,
|
|
145
|
+
"uses_hess": False,
|
|
146
|
+
"uses_hessp": False,
|
|
147
|
+
"valid_options": [
|
|
148
|
+
"ftol",
|
|
149
|
+
"eps",
|
|
150
|
+
"disp",
|
|
151
|
+
"maxiter",
|
|
152
|
+
"finite_diff_rel_step",
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
"trust-constr": {
|
|
156
|
+
"uses_grad": True,
|
|
157
|
+
"uses_hess": True,
|
|
158
|
+
"uses_hessp": True,
|
|
159
|
+
"valid_options": [
|
|
160
|
+
"gtol",
|
|
161
|
+
"xtol",
|
|
162
|
+
"barrier_tol",
|
|
163
|
+
"sparse_jacobian",
|
|
164
|
+
"initial_tr_radius",
|
|
165
|
+
"initial_constr_penalty",
|
|
166
|
+
"initial_barrier_parameter",
|
|
167
|
+
"initial_barrier_tolerance",
|
|
168
|
+
"factorization_method",
|
|
169
|
+
"finite_diff_rel_step",
|
|
170
|
+
"maxiter",
|
|
171
|
+
"verbose",
|
|
172
|
+
"disp",
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
"dogleg": {
|
|
176
|
+
"uses_grad": True,
|
|
177
|
+
"uses_hess": True,
|
|
178
|
+
"uses_hessp": False,
|
|
179
|
+
"valid_options": ["initial_trust_radius", "max_trust_radius", "eta", "gtol"],
|
|
180
|
+
},
|
|
181
|
+
"trust-ncg": {
|
|
182
|
+
"uses_grad": True,
|
|
183
|
+
"uses_hess": True,
|
|
184
|
+
"uses_hessp": True,
|
|
185
|
+
"valid_options": ["initial_trust_radius", "max_trust_radius", "eta", "gtol"],
|
|
186
|
+
},
|
|
187
|
+
"trust-exact": {
|
|
188
|
+
"uses_grad": True,
|
|
189
|
+
"uses_hess": True,
|
|
190
|
+
"uses_hessp": False,
|
|
191
|
+
"valid_options": ["initial_trust_radius", "max_trust_radius", "eta", "gtol"],
|
|
192
|
+
},
|
|
193
|
+
"trust-krylov": {
|
|
194
|
+
"uses_grad": True,
|
|
195
|
+
"uses_hess": True,
|
|
196
|
+
"uses_hessp": True,
|
|
197
|
+
"valid_options": ["inexact"],
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
root_method = Literal[
|
|
202
|
+
"hybr",
|
|
203
|
+
"lm",
|
|
204
|
+
"broyden1",
|
|
205
|
+
"broyden2",
|
|
206
|
+
"anderson",
|
|
207
|
+
"linearmixing",
|
|
208
|
+
"diagbroyden",
|
|
209
|
+
"excitingmixing",
|
|
210
|
+
"krylov",
|
|
211
|
+
"df-sane",
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
ROOT_MODE_KWARGS = {
|
|
215
|
+
"hybr": {
|
|
216
|
+
"uses_jac": True,
|
|
217
|
+
"valid_options": ["col_deriv", "xtol", "maxfev", "band", "eps", "factor", "diag"],
|
|
218
|
+
},
|
|
219
|
+
"lm": {
|
|
220
|
+
"uses_jac": True,
|
|
221
|
+
"valid_options": ["col_deriv", "ftol", "xtol", "gtol", "maxiter", "eps", "factor", "diag"],
|
|
222
|
+
},
|
|
223
|
+
"broyden1": {
|
|
224
|
+
"uses_jac": False,
|
|
225
|
+
"valid_options": [
|
|
226
|
+
"nit",
|
|
227
|
+
"disp",
|
|
228
|
+
"maxiter",
|
|
229
|
+
"ftol",
|
|
230
|
+
"fatol",
|
|
231
|
+
"xtol",
|
|
232
|
+
"xatol",
|
|
233
|
+
"tol_norm",
|
|
234
|
+
"line_search",
|
|
235
|
+
"max_rank",
|
|
236
|
+
"jac_options",
|
|
237
|
+
],
|
|
238
|
+
"jac_options": ["alpha", "reduction_method", "to_retain"],
|
|
239
|
+
},
|
|
240
|
+
"broyden2": {
|
|
241
|
+
"uses_jac": False,
|
|
242
|
+
"valid_options": [
|
|
243
|
+
"nit",
|
|
244
|
+
"disp",
|
|
245
|
+
"maxiter",
|
|
246
|
+
"ftol",
|
|
247
|
+
"fatol",
|
|
248
|
+
"xtol",
|
|
249
|
+
"xatol",
|
|
250
|
+
"tol_norm",
|
|
251
|
+
"line_search",
|
|
252
|
+
"max_rank",
|
|
253
|
+
"jac_options",
|
|
254
|
+
],
|
|
255
|
+
"jac_options": ["alpha", "reduction_method", "to_retain"],
|
|
256
|
+
},
|
|
257
|
+
"anderson": {
|
|
258
|
+
"uses_jac": False,
|
|
259
|
+
"valid_options": [
|
|
260
|
+
"nit",
|
|
261
|
+
"disp",
|
|
262
|
+
"maxiter",
|
|
263
|
+
"ftol",
|
|
264
|
+
"fatol",
|
|
265
|
+
"xtol",
|
|
266
|
+
"xatol",
|
|
267
|
+
"tol_norm",
|
|
268
|
+
"line_search",
|
|
269
|
+
"jac_options",
|
|
270
|
+
],
|
|
271
|
+
"jac_options": ["alpha", "M", "w0"],
|
|
272
|
+
},
|
|
273
|
+
"linearmixing": {
|
|
274
|
+
"uses_jac": False,
|
|
275
|
+
"valid_options": [
|
|
276
|
+
"nit",
|
|
277
|
+
"disp",
|
|
278
|
+
"maxiter",
|
|
279
|
+
"ftol",
|
|
280
|
+
"fatol",
|
|
281
|
+
"xtol",
|
|
282
|
+
"xatol",
|
|
283
|
+
"tol_norm",
|
|
284
|
+
"line_search",
|
|
285
|
+
"jac_options",
|
|
286
|
+
],
|
|
287
|
+
"jac_options": ["alpha"],
|
|
288
|
+
},
|
|
289
|
+
"diagbroyden": {
|
|
290
|
+
"uses_jac": False,
|
|
291
|
+
"valid_options": [
|
|
292
|
+
"nit",
|
|
293
|
+
"disp",
|
|
294
|
+
"maxiter",
|
|
295
|
+
"ftol",
|
|
296
|
+
"fatol",
|
|
297
|
+
"xtol",
|
|
298
|
+
"xatol",
|
|
299
|
+
"tol_norm",
|
|
300
|
+
"line_search",
|
|
301
|
+
"jac_options",
|
|
302
|
+
],
|
|
303
|
+
"jac_options": ["alpha"],
|
|
304
|
+
},
|
|
305
|
+
"excitingmixing": {
|
|
306
|
+
"uses_jac": False,
|
|
307
|
+
"valid_options": [
|
|
308
|
+
"nit",
|
|
309
|
+
"disp",
|
|
310
|
+
"maxiter",
|
|
311
|
+
"ftol",
|
|
312
|
+
"fatol",
|
|
313
|
+
"xtol",
|
|
314
|
+
"xatol",
|
|
315
|
+
"tol_norm",
|
|
316
|
+
"line_search",
|
|
317
|
+
"jac_options",
|
|
318
|
+
],
|
|
319
|
+
"jac_options": ["alpha", "alphamax"],
|
|
320
|
+
},
|
|
321
|
+
"krylov": {
|
|
322
|
+
"uses_jac": False,
|
|
323
|
+
"valid_options": [
|
|
324
|
+
"nit",
|
|
325
|
+
"disp",
|
|
326
|
+
"maxiter",
|
|
327
|
+
"ftol",
|
|
328
|
+
"fatol",
|
|
329
|
+
"xtol",
|
|
330
|
+
"xatol",
|
|
331
|
+
"tol_norm",
|
|
332
|
+
"line_search",
|
|
333
|
+
"jac_options",
|
|
334
|
+
],
|
|
335
|
+
"jac_options": [
|
|
336
|
+
"rdiff",
|
|
337
|
+
"method",
|
|
338
|
+
"inner_M",
|
|
339
|
+
"inner_tol",
|
|
340
|
+
"inner_maxiter",
|
|
341
|
+
"outer_k",
|
|
342
|
+
],
|
|
343
|
+
},
|
|
344
|
+
"df-sane": {
|
|
345
|
+
"uses_jac": False,
|
|
346
|
+
"valid_options": [
|
|
347
|
+
"ftol",
|
|
348
|
+
"fatol",
|
|
349
|
+
"fnorm",
|
|
350
|
+
"maxfev",
|
|
351
|
+
"disp",
|
|
352
|
+
"eta_strategy",
|
|
353
|
+
"sigma_eps",
|
|
354
|
+
"sigma_0",
|
|
355
|
+
"M",
|
|
356
|
+
"line_search",
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from scipy.optimize import OptimizeResult
|
|
7
|
+
from scipy.optimize import minimize as sp_minimize
|
|
8
|
+
from scipy.sparse.linalg import LinearOperator
|
|
9
|
+
|
|
10
|
+
from better_optimize.constants import minimize_method
|
|
11
|
+
from better_optimize.utilities import (
|
|
12
|
+
check_f_is_fused,
|
|
13
|
+
determine_maxiter,
|
|
14
|
+
determine_tolerance,
|
|
15
|
+
kwargs_to_options,
|
|
16
|
+
validate_provided_functions_minimize,
|
|
17
|
+
)
|
|
18
|
+
from better_optimize.wrapper import ObjectiveWrapper, optimzer_early_stopping_wrapper
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def minimize(
|
|
22
|
+
f: Callable[..., float | tuple[float, np.ndarray]],
|
|
23
|
+
x0: np.ndarray,
|
|
24
|
+
method: minimize_method,
|
|
25
|
+
jac: Callable[..., np.ndarray] | None = None,
|
|
26
|
+
hess: Callable[..., np.ndarray | LinearOperator] | None = None,
|
|
27
|
+
hessp: Callable[..., np.ndarray] | None = None,
|
|
28
|
+
progressbar: bool = True,
|
|
29
|
+
verbose: bool = True,
|
|
30
|
+
args: tuple | None = None,
|
|
31
|
+
**optimizer_kwargs,
|
|
32
|
+
) -> OptimizeResult:
|
|
33
|
+
"""
|
|
34
|
+
Solve a minimization problem using the scipy.optimize.minimize function.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
x0: np.ndarray
|
|
39
|
+
The initial values of the parameters to optimize
|
|
40
|
+
f: Callable
|
|
41
|
+
The objective function to minimize
|
|
42
|
+
args: tuple, optional
|
|
43
|
+
Additional arguments to pass to the objective function. Additional arguments are also passed to the gradient
|
|
44
|
+
and Hessian functions, if provided
|
|
45
|
+
jac: Callable, optional
|
|
46
|
+
The gradient of the objective function
|
|
47
|
+
hess: Callable, optional
|
|
48
|
+
The Hessian of the objective function
|
|
49
|
+
hessp: Callable, optional
|
|
50
|
+
The Hessian-vector product of the objective function
|
|
51
|
+
method: str
|
|
52
|
+
The optimization method to use
|
|
53
|
+
progressbar: bool
|
|
54
|
+
Whether to display a progress bar
|
|
55
|
+
verbose: bool
|
|
56
|
+
Whether to display verbose output
|
|
57
|
+
optimizer_kwargs
|
|
58
|
+
Additional keyword arguments to pass to the optimizer
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
optimizer_result: OptimizeResult
|
|
63
|
+
Optimization result
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
has_fused_f_and_grad = check_f_is_fused(f, x0, args)
|
|
67
|
+
validate_provided_functions_minimize(
|
|
68
|
+
method, jac, hess, hessp, has_fused_f_and_grad, verbose=verbose
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
options = optimizer_kwargs.pop("options", {})
|
|
72
|
+
optimizer_kwargs["options"] = options
|
|
73
|
+
|
|
74
|
+
optimizer_kwargs = kwargs_to_options(optimizer_kwargs, method)
|
|
75
|
+
maxiter, optimizer_kwargs = determine_maxiter(optimizer_kwargs, method)
|
|
76
|
+
optimizer_kwargs = determine_tolerance(optimizer_kwargs, method)
|
|
77
|
+
|
|
78
|
+
# Test hessian function -- if it returns a LinearOperator, it can't be used inside the wrapper
|
|
79
|
+
args = () if args is None else args
|
|
80
|
+
use_hess = hess is not None and not isinstance(hess(x0, *args), LinearOperator)
|
|
81
|
+
|
|
82
|
+
objective = ObjectiveWrapper(
|
|
83
|
+
maxeval=maxiter,
|
|
84
|
+
f=f,
|
|
85
|
+
jac=jac,
|
|
86
|
+
hess=hess if use_hess else None,
|
|
87
|
+
args=args,
|
|
88
|
+
progressbar=progressbar,
|
|
89
|
+
has_fused_f_and_grad=has_fused_f_and_grad,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
f_optim = partial(
|
|
93
|
+
sp_minimize,
|
|
94
|
+
fun=objective,
|
|
95
|
+
x0=x0,
|
|
96
|
+
method=method,
|
|
97
|
+
jac=True if has_fused_f_and_grad or jac is not None else None,
|
|
98
|
+
hess=hess,
|
|
99
|
+
hessp=hessp,
|
|
100
|
+
callback=objective.callback,
|
|
101
|
+
**optimizer_kwargs,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
optimizer_result = optimzer_early_stopping_wrapper(f_optim)
|
|
105
|
+
return optimizer_result
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from scipy.optimize import OptimizeResult
|
|
7
|
+
from scipy.optimize import root as sp_root
|
|
8
|
+
|
|
9
|
+
from better_optimize.constants import root_method
|
|
10
|
+
from better_optimize.utilities import (
|
|
11
|
+
check_f_is_fused,
|
|
12
|
+
determine_maxiter,
|
|
13
|
+
determine_tolerance,
|
|
14
|
+
kwargs_to_jac_options,
|
|
15
|
+
kwargs_to_options,
|
|
16
|
+
validate_provided_functions_root,
|
|
17
|
+
)
|
|
18
|
+
from better_optimize.wrapper import ObjectiveWrapper, optimzer_early_stopping_wrapper
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def root(
|
|
22
|
+
f: Callable[..., np.ndarray | tuple[np.ndarray, np.ndarray]],
|
|
23
|
+
x0: np.ndarray,
|
|
24
|
+
method: root_method,
|
|
25
|
+
jac: Callable[..., np.ndarray] | None = None,
|
|
26
|
+
progressbar: bool = True,
|
|
27
|
+
verbose: bool = True,
|
|
28
|
+
args: tuple | None = None,
|
|
29
|
+
**optimizer_kwargs,
|
|
30
|
+
) -> OptimizeResult:
|
|
31
|
+
"""
|
|
32
|
+
Solve a minimization problem using the scipy.optimize.minimize function.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
x0: np.ndarray
|
|
37
|
+
The initial values of the parameters to optimize
|
|
38
|
+
f: Callable
|
|
39
|
+
The objective function to minimize
|
|
40
|
+
args: tuple, optional
|
|
41
|
+
Additional arguments to pass to the objective function. Additional arguments are also passed to the gradient
|
|
42
|
+
and Hessian functions, if provided
|
|
43
|
+
jac: Callable, optional
|
|
44
|
+
The gradient of the objective function
|
|
45
|
+
hess: Callable, optional
|
|
46
|
+
The Hessian of the objective function
|
|
47
|
+
hessp: Callable, optional
|
|
48
|
+
The Hessian-vector product of the objective function
|
|
49
|
+
method: str
|
|
50
|
+
The optimization method to use
|
|
51
|
+
progressbar: bool
|
|
52
|
+
Whether to display a progress bar
|
|
53
|
+
verbose: bool
|
|
54
|
+
Whether to display verbose output
|
|
55
|
+
optimizer_kwargs
|
|
56
|
+
Additional keyword arguments to pass to the optimizer
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
optimizer_result: OptimizeResult
|
|
61
|
+
Optimization result
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
has_fused_f_and_grad = check_f_is_fused(f, x0, args)
|
|
65
|
+
validate_provided_functions_root(
|
|
66
|
+
method, jac, has_fused_f_and_grad, has_fused_f_and_grad, verbose=verbose
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
options = optimizer_kwargs.pop("options", {})
|
|
70
|
+
optimizer_kwargs["options"] = options
|
|
71
|
+
optimizer_kwargs = kwargs_to_options(optimizer_kwargs, method)
|
|
72
|
+
optimizer_kwargs = kwargs_to_jac_options(optimizer_kwargs, method)
|
|
73
|
+
|
|
74
|
+
maxiter, optimizer_kwargs = determine_maxiter(optimizer_kwargs, method)
|
|
75
|
+
optimizer_kwargs = determine_tolerance(optimizer_kwargs, method)
|
|
76
|
+
|
|
77
|
+
objective = ObjectiveWrapper(
|
|
78
|
+
maxeval=maxiter,
|
|
79
|
+
f=f,
|
|
80
|
+
jac=jac,
|
|
81
|
+
args=args,
|
|
82
|
+
progressbar=progressbar,
|
|
83
|
+
has_fused_f_and_grad=has_fused_f_and_grad,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
f_optim = partial(
|
|
87
|
+
sp_root,
|
|
88
|
+
fun=objective,
|
|
89
|
+
x0=x0,
|
|
90
|
+
method=method,
|
|
91
|
+
jac=True if has_fused_f_and_grad or jac is not None else None,
|
|
92
|
+
**optimizer_kwargs,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
optimizer_result = optimzer_early_stopping_wrapper(f_optim)
|
|
96
|
+
return optimizer_result
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from better_optimize.constants import (
|
|
9
|
+
MINIMIZE_MODE_KWARGS,
|
|
10
|
+
ROOT_MODE_KWARGS,
|
|
11
|
+
TOLERANCES,
|
|
12
|
+
minimize_method,
|
|
13
|
+
root_method,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
_log = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_option_kwargs(method: minimize_method | root_method):
|
|
20
|
+
if method in MINIMIZE_MODE_KWARGS.keys():
|
|
21
|
+
options_kwargs = MINIMIZE_MODE_KWARGS[method]
|
|
22
|
+
elif method in ROOT_MODE_KWARGS.keys():
|
|
23
|
+
options_kwargs = ROOT_MODE_KWARGS[method]
|
|
24
|
+
else:
|
|
25
|
+
raise ValueError(f"Unknown method: {method}")
|
|
26
|
+
|
|
27
|
+
return options_kwargs
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def validate_provided_functions_minimize(
|
|
31
|
+
method: minimize_method,
|
|
32
|
+
f_grad: Callable[[np.ndarray], np.ndarray] | None,
|
|
33
|
+
f_hess: Callable[[np.ndarray], np.ndarray] | None,
|
|
34
|
+
f_hessp: Callable[[np.ndarray], np.ndarray] | None,
|
|
35
|
+
has_fused_f_and_grad: bool,
|
|
36
|
+
verbose=True,
|
|
37
|
+
) -> None:
|
|
38
|
+
has_grad, has_hess, has_hessp = map(lambda f: f is not None, [f_grad, f_hess, f_hessp])
|
|
39
|
+
uses_grad, uses_hess, uses_hessp, _ = MINIMIZE_MODE_KWARGS[method].values()
|
|
40
|
+
|
|
41
|
+
if has_fused_f_and_grad and has_grad:
|
|
42
|
+
_log.warning(
|
|
43
|
+
"Objective function returns a tuple (interpreted as (loss, gradient), but a gradient function was "
|
|
44
|
+
"also provided. The gradient function will be ignored."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
elif has_fused_f_and_grad:
|
|
48
|
+
has_grad = True
|
|
49
|
+
|
|
50
|
+
if method not in MINIMIZE_MODE_KWARGS:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"Method {method} not recognized. Must be one of {list(MINIMIZE_MODE_KWARGS.keys())}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if has_hess and has_hessp:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
"Cannot ask for Hessian and Hessian-vector product at the same time. For all algorithms "
|
|
58
|
+
"except trust-exact and dogleg, use_hessp is preferred."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if has_grad and not uses_grad and verbose:
|
|
62
|
+
_log.warning(
|
|
63
|
+
f"Gradient provided but not used by method {method}. Gradients will still be evaluated at each "
|
|
64
|
+
f"optimzer step and the norm will be reported as a diagnositc. For large problems, this may be "
|
|
65
|
+
f"computationally intensive."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if (has_hess and not uses_hess) or (has_hessp and not uses_hessp) and verbose:
|
|
69
|
+
_log.warning(
|
|
70
|
+
f"Hessian provided but not used by method {method}. The full hessian will still be evaluated at "
|
|
71
|
+
f"each optimzer step and the norm will be reported as a diagnositc. For large problems, this may "
|
|
72
|
+
f"be computationally intensive."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if has_hess and not has_hessp and uses_hessp and uses_hess:
|
|
76
|
+
_log.warning(
|
|
77
|
+
f"You provided a function to compute the full hessian, but method {method} allows the use of a "
|
|
78
|
+
f"hessian-vector product instead. Consider passing hessp instead -- this may be significantly "
|
|
79
|
+
f"more efficient."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def validate_provided_functions_root(
|
|
84
|
+
method: root_method, f, jac, has_fused_f_and_grad: bool, verbose: bool = True
|
|
85
|
+
):
|
|
86
|
+
has_jac = jac is not None
|
|
87
|
+
info_dict = get_option_kwargs(method)
|
|
88
|
+
uses_jac = info_dict["uses_jac"]
|
|
89
|
+
|
|
90
|
+
if has_fused_f_and_grad and has_jac:
|
|
91
|
+
_log.warning(
|
|
92
|
+
"Objective function returns a tuple (interpreted as (loss, gradient), but a gradient function was "
|
|
93
|
+
"also provided. The gradient function will be ignored."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
elif has_fused_f_and_grad:
|
|
97
|
+
has_jac = True
|
|
98
|
+
|
|
99
|
+
if has_jac and not uses_jac and verbose:
|
|
100
|
+
_log.warning(
|
|
101
|
+
f"Gradient provided but not used by method {method}. Gradients will still be evaluated at each "
|
|
102
|
+
f"optimzer step and the norm will be reported as a diagnositc. For large problems, this may be "
|
|
103
|
+
f"computationally intensive."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def check_f_is_fused(f, x0, args):
|
|
108
|
+
args = () if args is None else args
|
|
109
|
+
output = f(x0, *args)
|
|
110
|
+
if not isinstance(output, tuple | list):
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
# If the output is a tuple, it should be length 2 (returning the value and the grad).If not, something is wrong
|
|
114
|
+
if len(output) != 2:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
"Objective function should return either a scalar loss or a two-tuple of (loss, gradient)"
|
|
117
|
+
)
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def determine_maxiter(
|
|
122
|
+
optimizer_kwargs: dict, method: minimize_method | root_method
|
|
123
|
+
) -> tuple[int, dict]:
|
|
124
|
+
MAXITER_KWARGS = ["maxiter", "maxfun", "maxfev"]
|
|
125
|
+
maxiter = optimizer_kwargs.pop("maxiter", 5000)
|
|
126
|
+
optimizer_kwargs["options"].update({"maxiter": maxiter})
|
|
127
|
+
maxiter_kwargs = [x for x in get_option_kwargs(method)["valid_options"] if x in MAXITER_KWARGS]
|
|
128
|
+
|
|
129
|
+
for kwarg in maxiter_kwargs:
|
|
130
|
+
if kwarg not in optimizer_kwargs["options"]:
|
|
131
|
+
optimizer_kwargs["options"][kwarg] = maxiter
|
|
132
|
+
|
|
133
|
+
return maxiter, optimizer_kwargs
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def kwargs_to_options(optimizer_kwargs: dict, method: minimize_method | root_method) -> dict:
|
|
137
|
+
optimizer_kwargs = deepcopy(optimizer_kwargs)
|
|
138
|
+
|
|
139
|
+
NEVER_AUTO_PROMOTE = ["bounds", "tol", "jac_options"]
|
|
140
|
+
option_kwargs = get_option_kwargs(method)["valid_options"]
|
|
141
|
+
|
|
142
|
+
provided_kwargs = list(optimizer_kwargs.keys())
|
|
143
|
+
options = optimizer_kwargs.get("options", {})
|
|
144
|
+
|
|
145
|
+
for kwarg in option_kwargs:
|
|
146
|
+
if kwarg in provided_kwargs and kwarg not in NEVER_AUTO_PROMOTE:
|
|
147
|
+
options[kwarg] = optimizer_kwargs.pop(kwarg)
|
|
148
|
+
|
|
149
|
+
optimizer_kwargs["options"] = options
|
|
150
|
+
return optimizer_kwargs
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def kwargs_to_jac_options(optimizer_kwargs: dict, method: root_method) -> dict:
|
|
154
|
+
provided_kwargs = list(optimizer_kwargs.keys())
|
|
155
|
+
method_jac_kwargs = get_option_kwargs(method).get("jac_options", None)
|
|
156
|
+
|
|
157
|
+
if method_jac_kwargs is None:
|
|
158
|
+
return optimizer_kwargs
|
|
159
|
+
|
|
160
|
+
optimizer_kwargs = deepcopy(optimizer_kwargs)
|
|
161
|
+
|
|
162
|
+
if "options" not in optimizer_kwargs:
|
|
163
|
+
optimizer_kwargs["options"] = {}
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
any(kwarg in method_jac_kwargs for kwarg in provided_kwargs)
|
|
167
|
+
and "jac_options" not in optimizer_kwargs["options"]
|
|
168
|
+
):
|
|
169
|
+
optimizer_kwargs["options"]["jac_options"] = {}
|
|
170
|
+
|
|
171
|
+
for kwarg in provided_kwargs:
|
|
172
|
+
if kwarg in method_jac_kwargs:
|
|
173
|
+
optimizer_kwargs["options"]["jac_options"][kwarg] = optimizer_kwargs.pop(kwarg)
|
|
174
|
+
|
|
175
|
+
return optimizer_kwargs
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def determine_tolerance(optimizer_kwargs: dict, method: minimize_method | root_method) -> dict:
|
|
179
|
+
tol = optimizer_kwargs.pop("tol", 1e-8)
|
|
180
|
+
optimizer_kwargs["tol"] = tol
|
|
181
|
+
method_options = get_option_kwargs(method)["valid_options"]
|
|
182
|
+
method_tolerances = [tol_type for tol_type in method_options if tol_type in TOLERANCES]
|
|
183
|
+
|
|
184
|
+
for tol_name in method_tolerances:
|
|
185
|
+
if tol_name not in optimizer_kwargs["options"]:
|
|
186
|
+
optimizer_kwargs["options"][tol_name] = tol
|
|
187
|
+
|
|
188
|
+
return optimizer_kwargs
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from rich.progress import (
|
|
8
|
+
BarColumn,
|
|
9
|
+
MofNCompleteColumn,
|
|
10
|
+
Progress,
|
|
11
|
+
SpinnerColumn,
|
|
12
|
+
TextColumn,
|
|
13
|
+
TimeElapsedColumn,
|
|
14
|
+
)
|
|
15
|
+
from rich.table import Column
|
|
16
|
+
|
|
17
|
+
_log = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ObjectiveWrapper:
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
f: Callable[[np.ndarray | float, ...], float | np.ndarray | tuple[float, np.ndarray]],
|
|
24
|
+
jac: Callable[[np.ndarray | float, ...], np.ndarray] | None = None,
|
|
25
|
+
hess: Callable[[np.ndarray | float, ...], np.ndarray] | None = None,
|
|
26
|
+
has_fused_f_and_grad: bool = False,
|
|
27
|
+
args: tuple | None = None,
|
|
28
|
+
maxeval: int = 5000,
|
|
29
|
+
progressbar: bool = True,
|
|
30
|
+
update_every: int = 10,
|
|
31
|
+
):
|
|
32
|
+
self.n_eval = 0
|
|
33
|
+
self.maxeval = maxeval
|
|
34
|
+
self.args = args if args is not None else ()
|
|
35
|
+
self.f = lambda x: f(x, *self.args)
|
|
36
|
+
self.use_jac = False
|
|
37
|
+
self.use_hess = False
|
|
38
|
+
self.has_fused_f_and_grad = has_fused_f_and_grad
|
|
39
|
+
|
|
40
|
+
self.progress = None
|
|
41
|
+
self.task = None
|
|
42
|
+
|
|
43
|
+
self.update_every = update_every
|
|
44
|
+
self.interrupted = False
|
|
45
|
+
self.desc = "f = {task.fields[f_value]:,.5g}"
|
|
46
|
+
|
|
47
|
+
if jac is not None or has_fused_f_and_grad:
|
|
48
|
+
self.desc += ", ||grad|| = {task.fields[grad_norm]:,.5g}"
|
|
49
|
+
self.use_jac = True
|
|
50
|
+
self.f_jac = lambda x: jac(x, *self.args)
|
|
51
|
+
|
|
52
|
+
if hess is not None:
|
|
53
|
+
self.desc += ", ||hess|| = {task.fields[hess_norm]:,.5g}"
|
|
54
|
+
self.use_hess = True
|
|
55
|
+
self.f_hess = lambda x: hess(x, *self.args)
|
|
56
|
+
|
|
57
|
+
self.previous_x = None
|
|
58
|
+
self.progressbar = progressbar
|
|
59
|
+
if progressbar:
|
|
60
|
+
self.progress = self.initialize_progress_bar()
|
|
61
|
+
self.progress.start()
|
|
62
|
+
|
|
63
|
+
def step(self, x):
|
|
64
|
+
grad = None
|
|
65
|
+
hess = None
|
|
66
|
+
if self.has_fused_f_and_grad:
|
|
67
|
+
value, grad = self.f(x)
|
|
68
|
+
else:
|
|
69
|
+
value = self.f(x)
|
|
70
|
+
|
|
71
|
+
if self.use_jac and not self.has_fused_f_and_grad:
|
|
72
|
+
grad = self.f_jac(x)
|
|
73
|
+
if self.use_hess:
|
|
74
|
+
hess = self.f_hess(x)
|
|
75
|
+
|
|
76
|
+
if np.all(np.isfinite(x)):
|
|
77
|
+
self.previous_x = x
|
|
78
|
+
|
|
79
|
+
if self.n_eval % self.update_every == 0:
|
|
80
|
+
self.update_progressbar(value, grad, hess)
|
|
81
|
+
|
|
82
|
+
if self.n_eval > self.maxeval:
|
|
83
|
+
self.update_progressbar(value, grad, hess)
|
|
84
|
+
self.interrupted = True
|
|
85
|
+
|
|
86
|
+
self.n_eval += 1
|
|
87
|
+
|
|
88
|
+
if self.use_hess:
|
|
89
|
+
return value, grad, hess
|
|
90
|
+
elif self.use_jac:
|
|
91
|
+
return value, grad
|
|
92
|
+
else:
|
|
93
|
+
return value
|
|
94
|
+
|
|
95
|
+
def __call__(self, x):
|
|
96
|
+
try:
|
|
97
|
+
return self.step(x)
|
|
98
|
+
except (KeyboardInterrupt, StopIteration):
|
|
99
|
+
self.interrupted = True
|
|
100
|
+
return self.step(x)
|
|
101
|
+
|
|
102
|
+
def callback(self, *args):
|
|
103
|
+
if self.interrupted:
|
|
104
|
+
raise StopIteration
|
|
105
|
+
|
|
106
|
+
def update_progressbar(
|
|
107
|
+
self, value: float, grad: np.float64 = None, hess: np.float64 = None, completed=False
|
|
108
|
+
) -> None:
|
|
109
|
+
if not self.progressbar:
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
if isinstance(value, np.ndarray):
|
|
113
|
+
value = (value**2).sum()
|
|
114
|
+
elif isinstance(value, list | tuple):
|
|
115
|
+
value = sum([x**2 for x in value])
|
|
116
|
+
|
|
117
|
+
value_dict = {"f_value": value}
|
|
118
|
+
if grad is not None:
|
|
119
|
+
grad_norm = np.linalg.norm(grad)
|
|
120
|
+
value_dict["grad_norm"] = grad_norm
|
|
121
|
+
if hess is not None:
|
|
122
|
+
hess_norm = np.linalg.norm(hess)
|
|
123
|
+
value_dict["hess_norm"] = hess_norm
|
|
124
|
+
|
|
125
|
+
if self.n_eval == 0:
|
|
126
|
+
self.task = self.progress.add_task("Optimizing", **value_dict)
|
|
127
|
+
|
|
128
|
+
if not completed:
|
|
129
|
+
self.progress.update(self.task, advance=self.update_every, **value_dict)
|
|
130
|
+
else:
|
|
131
|
+
self.progress.update(self.task, total=self.n_eval, completed=self.n_eval, **value_dict)
|
|
132
|
+
|
|
133
|
+
def initialize_progress_bar(self):
|
|
134
|
+
text_column = TextColumn("{task.description}", table_column=Column(ratio=1))
|
|
135
|
+
bar_column = BarColumn(bar_width=None, table_column=Column(ratio=2))
|
|
136
|
+
time_column = TimeElapsedColumn()
|
|
137
|
+
m_of_n = MofNCompleteColumn()
|
|
138
|
+
spinner = SpinnerColumn()
|
|
139
|
+
|
|
140
|
+
stat_column = TextColumn(self.desc, table_column=Column(ratio=1))
|
|
141
|
+
|
|
142
|
+
return Progress(
|
|
143
|
+
text_column, spinner, bar_column, time_column, m_of_n, stat_column, expand=False
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def optimzer_early_stopping_wrapper(f_optim):
|
|
148
|
+
objective = f_optim.keywords["fun"]
|
|
149
|
+
progressbar = objective.progressbar
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
# Do the optimization. This calls either optimize.root or optimize.minimize; all arguments are pre-configured
|
|
153
|
+
# at this point.
|
|
154
|
+
res = f_optim()
|
|
155
|
+
except Exception as e:
|
|
156
|
+
# Teardown the progress bar if necessary, then forward the error
|
|
157
|
+
if progressbar:
|
|
158
|
+
objective.progress.stop()
|
|
159
|
+
raise e
|
|
160
|
+
|
|
161
|
+
x_final = res.x
|
|
162
|
+
|
|
163
|
+
if progressbar:
|
|
164
|
+
# Evaluate the objective function one last time to get the final value, gradient, and hessian
|
|
165
|
+
# and update the progress bar to show the true final state
|
|
166
|
+
outputs = objective.step(x_final)
|
|
167
|
+
if not objective.use_jac and not objective.use_hess:
|
|
168
|
+
value = outputs
|
|
169
|
+
grad = None
|
|
170
|
+
hess = None
|
|
171
|
+
elif objective.use_jac and not objective.use_hess:
|
|
172
|
+
value, grad = outputs
|
|
173
|
+
hess = None
|
|
174
|
+
else:
|
|
175
|
+
value, grad, hess = outputs
|
|
176
|
+
|
|
177
|
+
objective.update_progressbar(value, grad, hess, completed=True)
|
|
178
|
+
objective.progress.stop()
|
|
179
|
+
|
|
180
|
+
return res
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: better-optimize
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.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
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Topic :: Scientific/Engineering
|
|
16
16
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
17
|
+
Requires-Python: <3.13,>=3.10
|
|
17
18
|
Description-Content-Type: text/markdown
|
|
18
19
|
Provides-Extra: tests
|
|
19
20
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
better_optimize/__init__.py
|
|
5
|
+
better_optimize/_version.py
|
|
6
|
+
better_optimize/constants.py
|
|
7
|
+
better_optimize/minimize.py
|
|
8
|
+
better_optimize/root.py
|
|
9
|
+
better_optimize/utilities.py
|
|
10
|
+
better_optimize/wrapper.py
|
|
11
|
+
better_optimize.egg-info/PKG-INFO
|
|
12
|
+
better_optimize.egg-info/SOURCES.txt
|
|
13
|
+
better_optimize.egg-info/dependency_links.txt
|
|
14
|
+
better_optimize.egg-info/requires.txt
|
|
15
|
+
better_optimize.egg-info/top_level.txt
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"numpy",
|
|
4
|
+
"setuptools",
|
|
5
|
+
"versioneer[toml]",
|
|
6
|
+
]
|
|
7
|
+
build-backend = "setuptools.build_meta"
|
|
8
|
+
|
|
1
9
|
[project]
|
|
2
10
|
name = 'better_optimize'
|
|
3
11
|
dynamic = ['version']
|
|
12
|
+
requires-python = ">=3.10,<3.13"
|
|
4
13
|
description = "A drop-in replacement for scipy optimize functions with quality of life improvements"
|
|
5
14
|
readme = "README.md"
|
|
6
15
|
license = {text = "MIT License"}
|
|
@@ -58,7 +67,7 @@ exclude_lines = [
|
|
|
58
67
|
[tool.ruff]
|
|
59
68
|
line-length = 100
|
|
60
69
|
target-version = "py310"
|
|
61
|
-
extend-exclude = ["better_optimize/_version.py"]
|
|
70
|
+
extend-exclude = ["better_optimize/_version.py", "setup.py"]
|
|
62
71
|
|
|
63
72
|
[tool.ruff.lint]
|
|
64
73
|
select = ["D", "E", "F", "I", "UP", "W", "RUF"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from setuptools import find_packages, setup
|
|
2
|
+
from setuptools.dist import Distribution
|
|
3
|
+
|
|
4
|
+
import versioneer
|
|
5
|
+
|
|
6
|
+
dist = Distribution()
|
|
7
|
+
dist.parse_config_files()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
NAME: str = dist.get_name() # type: ignore
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
setup(
|
|
15
|
+
name=NAME,
|
|
16
|
+
version=versioneer.get_version(),
|
|
17
|
+
cmdclass=versioneer.get_cmdclass(),
|
|
18
|
+
packages=find_packages(exclude=["tests*"]),
|
|
19
|
+
)
|
better_optimize-0.0.0/setup.py
DELETED
|
File without changes
|
{better_optimize-0.0.0 → better_optimize-0.0.2}/better_optimize.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|