scijit.optimize.minimize¶
- scijit.optimize.minimize(fun, x0, args=(), method=None, jac=None, hess=None, hessp=None, bounds=None, constraints=(), tol=None, callback=None, options=None, lower=None, upper=None, maxiter=0)¶
Minimize a scalar function of one or more variables.
Eleven algorithms behind one method string. Seven answer to the standard method names; four more are the remaining PRIMA derivative-free solvers.
Callable from Python and from inside
@njit. Both entries run the same compiled backends and reach them through the same runners. They differ in the CLASS an argument error carries, which the Raises section names, because the compiled entry decides those while the call compiles.fun is a plain
@njitfunction in every case, including for the four PRIMA methods.- Parameters:
- funcallable
A plain
@njitfunction calledfun(x, *args), in either of two shapes:-> freturns the objective,-> (f, g)returns it together with its gradient. The return type is read when the call compiles, so neither shape needs a flag and neither costs an evaluation.- x0array_like
Initial guess, one dimension.
- argstuple, optional
Extra parameters, unpacked into the argument list of fun and jac after x, so
args=(a, b)callsfun(x, a, b). Its elements may be of different types and shapes. Default(), which callsfun(x). A non-tuple args is a one-item tuple, so an ndarray reaches fun as ONE argument.- methodstr or None, optional
'Nelder-Mead','Powell','CG','BFGS','L-BFGS-B','SLSQP','COBYLA', or one of the additional'UOBYQA','NEWUOA','BOBYQA','LINCOA'. Matched case-insensitively.None(default) selects'SLSQP'with constraints,'L-BFGS-B'with bounds and'BFGS'otherwise. From inside@njitit must be a literal written at the call site: the result’s field set is the method’s, and a compiled function has one return type per signature. A variable there raises numba.TypingError. The Python entry takes any string.- jaccallable, bool, int or None, optional
jac(x, *args) -> array, the gradient, called with the same args as fun.None(default),Falseand0all mean no gradient: the gradient then comes from anfg-style fun, or, where the method needs one and fun returns onlyf, from forward differences.- hess, hesspcallable or None, optional
Accepted and ignored, with a
RuntimeWarningnaming the method. No method reached from here uses second-order information.- bounds(n, 2) array_like or None, optional
One
(min, max)pair per variable.None(default) is unbounded. Use-np.inf/np.inffor a one-sided bound. Used by'L-BFGS-B','SLSQP','COBYLA','BOBYQA'and'LINCOA'. The other six warn and ignore them. A(0, 2)array is also unbounded; it counts as bounds for the method default, because that is settled from the type rather than the length. See Notes.- constraintstuple, optional
Accepted only as empty.
'SLSQP'and'COBYLA'raise on a non-empty one; the other nine warn and ignore it.fmin_slsqp()andfmin_cobyla()take constraints.- tolfloat or None, optional
Headline tolerance for the chosen method.
None(default) uses that method’s own:xatol/fatol1e-4 for Nelder-Mead,xtol/ftol1e-4 for Powell,gtol1e-5 for CG and BFGS,gtol1e-5 andftol2.220446049250313e-09 for L-BFGS-B,acc1e-6 for SLSQP,rhoend1e-4 for COBYLA and 1e-6 for the PRIMA four. A value given sets every tolerance the method has, so0.0runs it to its iteration cap.- callbackcallable or None, optional
Called once per iteration as
callback(xk), with xk the current iterate. Either a plain Python callable or an@njitfunction of that shape. A Python callable whose single parameter is namedintermediate_resultis called with anOptimizeResultinstead, carryingxandfun. RaisingStopIterationhalts the solve, and the result then carriesstatus99 andsuccessFalse. Served by'Nelder-Mead','Powell','CG','BFGS','L-BFGS-B'and'SLSQP'.- optionsdict or None, optional
Solver options.
maxiterreaches maxiter. Three methods have a tolerance PAIR and each name reaches a slot of its own:xatolandfatolon Nelder-Mead,xtolandftolon Powell,ftolandgtolon L-BFGS-B. For the rest,xtol,ftol,gtol,pgtol,acc,rhoendandtolreach tol;rhobegreaches'COBYLA'and the PRIMA four,nptreaches'NEWUOA','BOBYQA'and'LINCOA',maxfevreaches'Nelder-Mead'and'Powell', andmaxfunreaches'L-BFGS-B'. Any other key draws anOptimizeWarningnaming it and is ignored, and the key set is per method. From inside@njitthis is a dict LITERAL at the call site.- lower, upperndarray, optional
The bounds as two separate arrays, which is the form the Fortran drivers take. Each is empty (default) or length n. Ignored when bounds is non-empty.
- maxiterint, optional
Iteration budget.
0(default) uses the method default:200*nfor Nelder-Mead, CG and BFGS,1000*nfor Powell, 15000 for L-BFGS-B, 100 for SLSQP, 1000 objective evaluations for COBYLA and500*nfor the PRIMA four.
- Returns:
- resOptimizeResult
x is the minimizer and fun its objective value. Every method also carries nfev, status, message and success. The rest of the field set is the METHOD’s, and a field the method did not compute is absent:
method
also carries
‘Nelder-Mead’
nit,final_simplex‘Powell’
nit,direc‘CG’
nit,jac,njev‘BFGS’
nit,jac,njev,hess_inv‘L-BFGS-B’
nit,jac,njev,hess_inv‘SLSQP’
nit,jac,njev,multipliers‘COBYLA’
maxcvthe PRIMA four
nothing further
Reading an absent field raises AttributeError, and numba.TypingError from inside
@njit.res.keys()lists what a given result holds.
- Raises:
- ValueError
If method is not one of the eleven; if constraints is non-empty on
'SLSQP'or'COBYLA'; if jac is neitherNonenor an@njitfunction; if jac is given together with anfg-style fun; if bounds is not(n, 2)or has an upper bound below its lower one; if x0 has more than one dimension; if lower or upper has a length that is neither 0 nor n; or if fun or jac returns a gradient whose length is notlen(x0).From inside
@njitthe first four of those are numba.TypingError instead, because the condition is decided while the call compiles rather than while it runs.A callback given to
'COBYLA','UOBYQA','NEWUOA','BOBYQA'or'LINCOA'raises ValueError, and numba.TypingError from inside@njitwhere method is a literal. Those five reach PRIMA, whose wrapper passes nocallback_fcn.- TypeError
If fun or jac does not bind
(x, *args)for the args given, which from inside@njitis a numba.TypingError; if x0 is complex, in any of the array, list and tuple spellings, from both entry points; if options is neither a mapping norNone, which from inside@njitis a numba.TypingError.
See also
scipy.optimize.minimizeThe scipy routine this mirrors.
scijit.optimize.fmin_l_bfgs_bL-BFGS-B with its full control set.
scijit.optimize.fmin_slsqpSLSQP with constraints.
scijit.optimize.fmin_cobylaCOBYLA with nonlinear constraints.
scijit.optimize.minimize_scalarOne variable, no starting point.
Notes
THE METHOD TABLE.
gradientis what the method does with jac;boundsis whether it uses them;prangeis whether concurrent calls are safe.method
gradient
bounds
prange
backend
‘Nelder-Mead’
ignored
no
yes
pure port
‘Powell’
ignored
no
yes
pure port
‘CG’
uses
no
yes
pure port
‘BFGS’
uses
no
yes
pure port
‘L-BFGS-B’
uses
yes
yes
Fortran
‘SLSQP’
uses
yes
yes
Fortran
‘COBYLA’
ignored
yes
yes
Fortran
‘UOBYQA’
ignored
no
yes
Fortran
‘NEWUOA’
ignored
no
yes
Fortran
‘BOBYQA’
ignored
yes
yes
Fortran
‘LINCOA’
ignored
yes
yes
Fortran
Every method is safe to call from a
numba.prangeloop. The reverse-communication solvers keep their state in caller-owned arrays; PRIMA reaches its callback through a Fortran module variable carrying!$omp threadprivate, so that resolves to one slot per thread. The 32-thread measurement behind the PRIMA half is in scijit.optimize.minimize_newuoa; the pure ports hold no shared state to corrupt.constraints is empty for every method. Nonlinear constraints reach COBYLA through
fmin_cobyla(), equality and inequality constraints reach SLSQP throughfmin_slsqp(), and linear constraints reach LINCOA throughminimize_lincoa().nitandjac. Nelder-Mead, Powell, CG, BFGS, L-BFGS-B and SLSQP report an iteration count. COBYLA and the PRIMA four count objective evaluations instead and carry nonit. CG, BFGS, L-BFGS-B and SLSQP report the gradient at the solution; the other seven carry nojac.THE RESULT’S FIELD ORDER is the same for every method:
x,fun, the method’s own outputs, the counters, thenstatus,message,success. scipy’s orders differ from each other,'BFGS'and'L-BFGS-B'carrying one field set in two orders, so there is no one order to match. Reaching a field by name or by attribute is unaffected.multiplierson'SLSQP'holds the constraint multipliers of the quadratic subproblem the solver finished on, one per constraint, the equalities first. Its length is the number of constraints, so(0,)where there are none. Bound rows contribute no entry.WHICH SCIPY METHODS ARE ABSENT.
'Newton-CG','TNC','COBYQA','dogleg','trust-ncg','trust-krylov','trust-exact'and'trust-constr'raiseValueError('Unknown solver <method>'), which is scipy’s own text for a name it does not have. A callable method, which scipy forwards**optionsto, raises as well.scipy 1.18’s
'COBYLA'is PRIMA, which is the library behind this one too, so the two run the same implementation rather than two versions of the same idea.THE OBJECTIVE. Both shapes are accepted and told apart by the return type. An
fgobjective handed to a method that takes a scalar one is split into two compiled functions, each of which calls it and drops half of what it computed: the evaluation counts match scipy’s, the work per evaluation does not. A scalar objective with jac handed to SLSQP is joined into onefgfor the same reason.args is unpacked into the objective’s argument list,
fun(x, *args). A non-tuple args is read as a one-item tuple and so reaches fun as one argument. The four PRIMA methods reach the objective through a C function pointer, which carries onedouble*: the elements cross it flattened and are rebuilt before the call, so the same spellings work.jac accepts
None,False,0or a compiled gradient. scipy’s'2-point','3-point'and'cs'are not implemented;jac=Nonealready forward-differences on the methods that need a gradient, which is what scipy’s'2-point'does.jac=Trueis spelled by having fun return(f, g). From inside@njit,Falseand0have to be written at the call site: a variable holding one is refused, because its value is not known when the call compiles, and scipy accepts it.constraints is unavailable as a list of dicts holding callables.
THE CALLBACK. From inside
@njitonly an@njitcallback is reachable, because a Python callable cannot cross into compiled code as an argument. A Python callback reaches the solver through a module-level slot and takes the GIL once per iteration, so two solves running at once in anumba.prangeloop overwrite each other’s callback and the loop stops running in parallel. An@njitcallback travels as an argument and does neither.An exception other than
StopIterationfrom a Python callback reaches the caller after the solve has run its exit path, where scipy raises it from inside the solver loop, so the traceback carries no solver frames. An@njitcallback halts the solve on ANY exception, and the result then carries thestatus99 aStopIterationgives.res is a scijit.optimize.OptimizeResult, not scipy’s. Its field set is the solver’s, as scipy’s is: COBYLA carries
maxcv, Nelder-Meadfinal_simplex, Powelldirec, SLSQPmultipliers, and BFGS and L-BFGS-Bhess_inv. A field the method did not compute is absent, and reading it raisesAttributeErrorfrom Python and aTypingErrorwhen the call compiles.'Nelder-Mead'and'Powell'DO NOT USE bounds, and warn that they cannot. scipy’s do use them: on the box[(0, 0.5), (0, 0.5)]with(x[0] - 1)**2 + (x[1] - 2)**2from(0, 0), scipy returns[0.5, 0.5]and[0.49997985, 0.5]where this returns[1., 2.]on both. The warning is what makes that visible, and it is a warning scipy does not raise.A
(0, 2)bounds array is unbounded and still counts as bounds for themethod=Nonedefault, because that is settled from the type rather than the length. scipy has no counterpart for this spelling.x0with every variable pinned by equal bounds goes to the solver here. scipy short-circuits it and returns a result built without calling the solver, whose field set is smaller again.A gradient whose length is not
len(x0)raises, and so does a lower or upper of the wrong length. scipy 1.18 raises on neither of the two Fortran methods offered here.options reaches the arguments listed under Parameters, and every other key draws an
OptimizeWarningnaming it, which is scipy’s class and scipy’s text for a key IT does not read. So a key scipy reads and this does not,disp,return_all,maxcor,eps,maxls,initial_simplex,direc,norm,adaptive,catolandiprintamong them, is announced rather than honoured.toland tol name one quantity twice, and so domaxiterand maxiter. Where both are given,optionswins, which is scipy’ssetdefaultorder.A complex x0 raises
TypeError. scipy 1.18 accepts one and returnscomplex128, and the imaginary part never moves from its starting value: measured onf(z) = |z - (2+3j)|**2, whose minimiser is2+3jand whose minimum is 0,x0 = 1+1jgivesx = 1.9999999504278225+1jandfun = 9.0,x0 = 0jgivesx = 2.000000136913649+0jandfun = 9.0, andx0 = 5-2jgivesx = 2.0000004414283588-2jandfun = 9.0, each withsuccess=True.https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
Examples
The objective returns the value and the gradient together:
>>> import numpy as np >>> from numba import njit >>> from scijit.optimize import minimize >>> @njit ... def fg(x): ... f = (x[0] - 1.0) ** 2 + (x[1] - 2.5) ** 2 ... return f, np.array([2.0 * (x[0] - 1.0), 2.0 * (x[1] - 2.5)]) >>> @njit ... def run(): ... return minimize(fg, np.array([0.0, 0.0])) >>> res = run() >>> res.x array([1. , 2.5]) >>> res.success True
scipy’s shape, an objective returning only the value:
>>> @njit ... def f(x): ... return (x[0] - 1.0) ** 2 + (x[1] - 2.5) ** 2 >>> @njit ... def run_nm(): ... return minimize(f, np.array([0.0, 0.0]), method='Nelder-Mead') >>> np.round(run_nm().x, 6) array([1.000008, 2.499982])
Nelder-Mead stops on a simplex smaller than
xatol, 1e-4 by default, so the last digits above are the tolerance rather than the arithmetic.Extra parameters, one array and one scalar, unpacked after x:
>>> @njit ... def fa(x, target, w): ... return w * ((x[0] - target[0]) ** 2 + (x[1] - target[1]) ** 2) >>> @njit ... def run_args(): ... return minimize(fa, np.array([0.0, 0.0]), ... args=(np.array([1.0, 2.5]), 3.0)) >>> np.round(run_args().x, 6) array([1. , 2.5])
A derivative-free PRIMA method, from the same plain function:
>>> @njit ... def run_newuoa(): ... return minimize(f, np.array([0.0, 0.0]), method='NEWUOA') >>> np.round(run_newuoa().x, 6) array([1. , 2.5])
Bounded, with SLSQP:
>>> @njit ... def run_bounded(): ... return minimize(fg, np.array([0.0, 0.0]), method='SLSQP', ... bounds=np.array([[0.0, 0.5], [0.0, 0.5]])) >>> run_bounded().x array([0.5, 0.5])