scijit.optimize.fmin_slsqp

scijit.optimize.fmin_slsqp(func, x0, eqcons=(), f_eqcons=None, ieqcons=(), f_ieqcons=None, bounds=(), fprime=None, fprime_eqcons=None, fprime_ieqcons=None, args=(), iter=100, acc=1e-06, iprint=1, disp=None, full_output=0, epsilon=1.4901161193847656e-08, callback=None)

Minimize a function subject to equality, inequality and bound constraints.

SLSQP – sequential least-squares programming – replaces the problem at each iteration with a quadratic model under linearized constraints, and solves that subproblem as a least-squares fit.

Callable from Python and from inside @njit. Both entries run the same compiled driver. Where a refused argument is reported differs: from Python as a ValueError while the call runs, from @njit as a TypingError while it compiles, both naming the argument.

Parameters:
funccallable

A plain @njit f(x, *args) -> value.

x0array_like

Initial guess. Any rank is flattened, and it is clipped into bounds before the first evaluation.

eqconstuple of callables, optional

Equality constraints g(x, *args) -> float or g(x, *args) -> array, feasible where the result is 0. An entry returning an array contributes one constraint per element. Every entry is a plain @njit function. Empty (default) is no equality constraint. Inside @njit the tuple is a compile-time constant: its length and the functions in it decide what is compiled. Given together with f_eqcons the two are APPENDED, these entries first, and these entries are differenced even when f_eqcons carries an analytic Jacobian.

f_eqconscallable or None, optional

ceq(x, *args) -> array, all equality constraints at once, feasible where the result is 0. Compile-time constant inside @njit.

ieqconstuple of callables, optional

Inequality constraints, feasible where the result is >= 0. Otherwise as eqcons, and appended ahead of f_ieqcons the same way.

f_ieqconscallable or None, optional

cineq(x, *args) -> array, all inequality constraints at once, feasible where the result is >= 0.

boundssequence of (min, max) pairs, (n, 2) ndarray, or (), optional

Every spelling fmin_l_bfgs_b() accepts. Empty (default) and None are both unbounded, where fmin_l_bfgs_b takes None alone and refuses an empty sequence. A length other than len(x0) is an IndexError here and a ValueError there. The pairs are read by transposition, so each row must hold exactly two entries.

fprimecallable or None, optional

grad(x, *args) -> array. None (default) computes the gradient by forward differences with absolute step epsilon.

fprime_eqcons, fprime_ieqconscallable or None, optional

(m, n) Jacobian of f_eqcons / f_ieqcons alone, not of the eqcons / ieqcons entries appended before it. None (default) computes it by forward differences. Ignored when the matching vector function is absent.

argstuple or ndarray, optional

Extra values for every callback. A tuple is unpacked into their argument lists, f(x, *args); the entries may be of any types the callbacks accept. () (default) calls f(x).

iterint, optional

Maximum major iterations. Default 100.

accfloat, optional

Requested accuracy. Default 1e-6.

iprintint, optional

1 (default) prints the exit summary. 0 and below are silent. 2 and above add a per-iteration table, one row per major iteration, ahead of the summary.

dispint or None, optional

Overrides iprint when it is not None.

full_outputbool, optional

False (default) returns x alone. True returns the 5-tuple. Compile-time constant inside @njit: it selects the return type.

epsilonfloat or ndarray, optional

Absolute step for every finite-difference path. Default sqrt(eps). An array gives one step per coordinate; a size-1 array applies to every coordinate.

callbackcallable, optional

Called once per major iteration, as callback(xk) or callback(intermediate_result). Two spellings are served: a plain Python callable, which halts the solve when it raises StopIteration, and a numba @njit callback(xk), which halts when it raises any exception. See Notes.

Returns:
outndarray, or tuple

x alone, or with full_output the 5-tuple (x, fx, its, imode, smode): the minimizer, its objective value, the major-iteration count, the exit mode and the message for it. imode == 0 is success, 8 a bad line search and 9 the iteration limit.

Raises:
IndexError

If the length of bounds is neither 0 nor len(x0).

ValueError

If callback is a @cfunc, a raw function address or a non-callable; if an entry of eqcons or ieqcons is a plain Python callable; if a row of bounds does not hold two entries; if any lower bound exceeds its upper bound; if epsilon does not broadcast against x0 while a forward difference is taken; or if a gradient or constraint Jacobian comes back with the wrong shape.

TypeError

If an entry of eqcons or ieqcons is not callable, or if a row of bounds is a number rather than a pair.

See also

scipy.optimize.fmin_slsqp

The scipy routine this mirrors.

scijit.optimize.minimize

Dispatches here for method='SLSQP'.

scijit.optimize.fmin_cobyla

Derivative-free, nonlinear constraints.

scijit.optimize.fmin_l_bfgs_b

Bounds only, no general constraints.

Notes

eqcons and ieqcons are a TUPLE of @njit functions, where scipy takes a list of Python callables. The tuple is combined into one vector-valued function when the call compiles and handed to the same slot f_eqcons and f_ieqcons fill, so the two spellings run the same code and their Jacobians come from the same forward differences.

An ndarray or a list args reaches every callback as ONE argument, f(x, args), where scipy unpacks those two element by element. The arity of a compiled call is fixed when it compiles, so a sequence whose length is known only at run time cannot be unpacked.

Whether f_eqcons, f_ieqcons, fprime, fprime_eqcons and fprime_ieqcons are None is a compile-time constant inside @njit, since numba has no None-able function argument. full_output is likewise a compile-time literal, because it selects the return type.

A Python callback is reached from compiled code through a module-level slot and a numba.objmode block, so it takes the GIL and pays an interpreter round trip once per major iteration. It is also not prange-safe, because the slot is module state that two concurrent solves share. Inside @njit only the @njit spelling is accepted, since a Python callable cannot cross into compiled code as an argument.

The two spellings differ in what halts the solve. The Python one halts on StopIteration, which is scipy’s contract; anything else it raises reaches the caller. The @njit one halts on ANY exception, because numba matches no exception class: except StopIteration does not compile.

iprint prints scipy’s exit summary at its scipy default of 1, so a bare call writes to stdout.

A bounds LONGER than x0 raises the same IndexError as a shorter one. scipy reaches an np.clip before its own length test in that direction and surfaces a numpy broadcast ValueError instead.

A gradient or constraint Jacobian of the wrong shape raises. numba applies no bounds checking, so an unchecked wrong shape would read past the end of the buffer rather than fail.

Safe to call from a numba.prange loop with callback at None or an @njit function: the solver is reverse communication, so its state lives in caller-owned arrays. A Python callback is not, and serializes the loop.

https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_slsqp.html

Examples

Minimize x0**2 + x1**2 subject to x0 + x1 >= 1:

>>> import numpy as np
>>> from numba import njit
>>> from scijit.optimize import fmin_slsqp
>>> @njit
... def obj(x):
...     return x[0] ** 2 + x[1] ** 2
>>> @njit
... def cineq(x):
...     return np.array([x[0] + x[1] - 1.0])
>>> @njit
... def run():
...     return fmin_slsqp(obj, np.array([2.0, 1.0]), f_ieqcons=cineq)
>>> run()
Optimization terminated successfully    (Exit mode 0)
            Current function value: 0.5
            Iterations: 4
            Function evaluations: 12
            Gradient evaluations: 4
array([0.5, 0.5])

iprint defaults to scipy’s 1, so the exit summary above is what a bare call writes to stdout. With full_output, which must be a literal inside @njit:

>>> @njit
... def run_full():
...     return fmin_slsqp(obj, np.array([2.0, 1.0]), f_ieqcons=cineq,
...                       full_output=True)
>>> x, fx, its, imode, smode = run_full()
Optimization terminated successfully    (Exit mode 0)
            Current function value: 0.5
            Iterations: 4
            Function evaluations: 12
            Gradient evaluations: 4
>>> fx, its, imode
(0.5, 4, 0)
>>> smode
'Optimization terminated successfully'