ScipyOptimizer#
- class braintools.optim.ScipyOptimizer(loss_fun, bounds, method=None, constraints=(), tol=None, callback=None, options=None)#
SciPy-based optimizer with dict/sequence bounds compatible with Nevergrad.
This optimizer accepts the same bounds structure as
braintools.optim.NevergradOptimizerand a loss function with matching signature:If
boundsis a sequence of(min, max)pairs,loss_funis called positionally asloss_fun(*params).If
boundsis a dict mapping names to(min, max)pairs,loss_funis called by keyword asloss_fun(**params).
Bounds can be scalars or arrays, and may optionally be provided as
brainunit.Quantity. Internally, SciPy works on the numeric mantissas while yourloss_funreceives values in the same structure as the bounds (without units).- Parameters:
loss_fun (
Callable) – Objective function returning a scalar (0-D array-like). Its signature must match the structure ofboundsas described above.bounds (
Sequence|Dict[str,Any]) – Search space bounds, as in Nevergrad: each value is(min, max)where elements are scalars or arrays, optionallyu.Quantity. All elements in a pair must share shape, and units (if any) must be compatible. For dict bounds, names define keyword arguments toloss_fun.method (
str|None) – SciPy method (e.g.,'L-BFGS-B','TNC','SLSQP','Powell'). If omitted, SciPy selects a default based on constraints/bounds.constraints – Passed through to
scipy.optimize.minimize.tol – Passed through to
scipy.optimize.minimize.callback – Passed through to
scipy.optimize.minimize.options – Passed through to
scipy.optimize.minimize.
Notes
This wrapper flattens parameters to a 1-D vector for SciPy and unflattens results back to the same structure found in
bounds.Gradients are computed via JAX auto-differentiation, but only for gradient-based methods. Gradient-free methods (
'Nelder-Mead','Powell','COBYLA','COBYQA') skip the Jacobian entirely.Everything SciPy operates on (the parameter vector, the Jacobian and the bounds) is cast to
float64so that the'TNC'and'SLSQP'kernels work despite JAX defaulting tofloat32.This is a black-box optimizer: it implements
minimize()only and does not honor the State-basedregister_trainable_weights()/update()contract of theOptimizerbase (those raiseNotImplementedError).
Examples
Minimize a simple quadratic with tuple bounds:
>>> import jax.numpy as jnp >>> def loss(x, y): ... return (x - 1.0)**2 + (y + 2.0)**2 >>> bounds = [(-5.0, 5.0), (-3.0, 3.0)] >>> opt = ScipyOptimizer(loss_fun=loss, bounds=bounds, method='L-BFGS-B') >>> res = opt.minimize(n_iter=3) >>> isinstance(res.x, (list, tuple)) # same structure as bounds True
With named parameters using dict bounds:
>>> def loss(**p): ... return (p['a'] - 1.0)**2 + (p['b'] + 2.0)**2 >>> bounds = {"a": (-5.0, 5.0), "b": (-3.0, 3.0)} >>> opt = ScipyOptimizer(loss_fun=loss, bounds=bounds, method='TNC') >>> res = opt.minimize(n_iter=2) >>> isinstance(res.x, dict) True
- minimize(n_iter=1)[source]#
Run SciPy minimization with random restarts.
Each iteration draws a fresh starting point uniformly from the bounds and runs
scipy.optimize.minimizeonce; the result with the lowest objective value across all restarts is returned.- Parameters:
n_iter (
int) – Number of random restarts. Each restart is an independent SciPy minimization from a new random initial point; more restarts increase the chance of escaping poor local minima.- Returns:
The best result across restarts, with
res.xunflattened back into the same structure asbounds(atuplefor sequence bounds, adictfor dict bounds). If every restart yields a non-finite objective (e.g. a diverging loss), the first such result is returned with its non-finiteres.funrather thanNone.- Return type:
scipy.optimize.OptimizeResult
- Raises:
AssertionError – If
n_iteris not a positive integer.