scijit.optimize.root

scijit.optimize.root(fun, x0, args=(), method='hybr', jac=None, tol=None, callback=None, options=None, col_deriv=0, xtol=1.49012e-08, ftol=1.49012e-08, gtol=0.0, maxfev=0, maxiter=0, band=None, eps=None, factor=100.0, diag=None, m=-1, validate=True)

Find a root of a vector function.

Dispatches over MINPACK’s two drivers: a modified Powell hybrid method, and Levenberg-Marquardt for a non-square system.

Callable from Python and from inside @njit.

Parameters:
funcallable

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

x0float or array_like

Initial guess. A scalar, list or tuple is accepted. Any rank is FLATTENED.

argstuple, optional

Extra parameters, passed to the callback as f(x, *args), one per entry. Entries may be scalars or arrays and need not share a type. A non-tuple args is coerced to the ONE argument (args,).

method{‘hybr’, ‘lm’}, optional

'hybr' (default) modified Powell hybrid, 'lm' Levenberg-Marquardt. Matched case-insensitively and NOT stripped, so 'hybr ' raises. Must be a compile-time constant inside @njit: it selects the return type.

jaccallable, bool or None, optional

None (default) uses a forward-difference Jacobian. A plain @njit jac(x, *args) selects the analytic path. Any other value is read for its truth value: a truthy one means fun returns the pair (residuals, jacobian) and a falsy one means the same as None, so 1 and 0 behave as True and False. An integer other than 0 or 1 is refused. A non-callable jac must be written out inside @njit, not held in a variable.

tolfloat or None, optional

Convenience tolerance. None (default) leaves the per-method defaults. A value sets xtol on BOTH methods, and loses to an explicit options['xtol'].

callbackcallable, optional

Accepted and ignored, with a RuntimeWarning. Neither method reads it.

optionsdict or None, optional

Solver options. Recognised keys are col_deriv, xtol, maxfev, band, eps, factor, diag for 'hybr'; col_deriv, ftol, xtol, gtol, maxiter, eps, factor, diag for 'lm'. A key the method does not read draws an OptimizeWarning and is ignored, and every key is also reachable as the argument of the same name.

Inside @njit a dict whose values are all of ONE type carries only the keys of that kind, so a dict of numbers cannot hold band’s tuple or diag’s array and raises when it names either. A dict holding values of two different types carries every key: {'xtol': 1e-10, 'band': (0, 0)} and {'xtol': 1e-10, 'diag': d} both work. An empty dict literal is not typeable by numba; None is the empty case.

col_derivint, optional

A statement about jac. 0 (default) reads the Jacobian as J[i, j] = dF_i/dx_j; a nonzero value reads it as already transposed, which on 'lm' means the shape (n, m) rather than (m, n). Ignored without jac. A runtime variable is fine.

xtol, ftol, gtolfloat, optional

Tolerances. ftol/gtol are read by 'lm' only.

maxfevint, optional

'hybr' evaluation budget. 0 (default) means 200*(n+1), or 100*(n+1) with jac.

maxiterint, optional

'lm' evaluation budget.

bandtuple of int or None, optional

(ml, mu) for a banded Jacobian. 'hybr' only.

epsfloat or None, optional

Forward-difference step. None (default) resolves to machine epsilon on 'hybr' and to 0.0 on 'lm'. MINPACK derives the step as sqrt(max(eps_given, eps_machine)), so the two spellings produce an identical step and identical evaluation counts.

factorfloat, optional

Initial step bound, in (0.1, 100). Default 100.

diagsequence or None, optional

Per-variable scale factors, all positive. An array, list or tuple. The first x0.size entries are used and any others ignored.

mint, optional

Residual count, 'lm' only. -1 (default) reads it from the callback. A value skips that call.

validatebool, optional

True (default) raises when the returned solution is degenerate; False reports it as status = -1 or -2 instead. The check that the callback wrote anything at all is always on.

Returns:
resultRootHybr, RootHybrJ, RootLm or RootLmJ

A namedtuple whose fields depend on method and on whether jac was given, reached as attributes: x, success, status, method, fun, fjac, qtf, nfev, message, plus r on 'hybr', ipvt and cov_x on 'lm', and njev whenever jac is given. fun, fjac, qtf and r are only meaningful when success.

Raises:
TypeError

If the residual count disagrees with x0.size on 'hybr', if jac returns an array of the wrong shape, or if diag has fewer entries than x0.

ValueError

If method is neither 'hybr' nor 'lm'; if fun is not a plain @njit function; if an args entry is neither a real number nor an array of real numbers; if an options dict inside @njit names band or diag and its value type cannot carry one; or, under validate=True, if the returned solution is degenerate.

See also

scipy.optimize.root

The scipy routine this mirrors.

scijit.optimize.fsolve

'hybr' alone, returning the fsolve shape.

scijit.optimize.leastsq

'lm' alone, returning the leastsq shape.

scijit.optimize.root_scalar

One equation in one variable.

Notes

scipy dispatches root over ten methods. This one implements hybr and lm, calling the MINPACK drivers directly. The remaining eight (broyden1, broyden2, anderson, linearmixing, diagbroyden, excitingmixing, krylov, df-sane) are pure Python in scipy and are planned, not unavailable in principle.

The result is a namedtuple rather than an OptimizeResult. res['x'], res.get('x'), res.keys(), res.values(), res.items() and 'x' in res all work, from Python and from inside @njit, and its field order is scipy’s key insertion order, which differs between the two methods as it does in scipy, so positional unpacking agrees too. Iterating it yields VALUES, where iterating a dict yields keys, and it is not an instance of scipy.optimize.OptimizeResult.

method, and whether jac is None, must be compile-time constants inside @njit: both change the return type.

callback draws a RuntimeWarning and is ignored, and an unrecognised options key draws an OptimizeWarning and is ignored. Both are scipy’s class and scipy’s text on these two methods.

cov_x on 'lm' is None in the two cases scipy returns None: an status outside {1, 2, 3, 4}, or a singular triangular factor.

nfev counts the evaluations this package makes before the solver runs as well as MINPACK’s own: one to check that the callback writes the residual, one on 'hybr' to read the residual count, and two more on 'hybr' without jac under validate=True for the read probe. On 'hybr' it is therefore two higher than scipy’s without jac and equal to scipy’s with one. On 'lm' the evaluation that reads the residual length is not counted, as scipy does not count its own, and nor is the one jac costs for its shape check.

args=None raises. scipy reads it as the one-item tuple (None,) and calls f(x, None), and None is not a real number.

A diag with fewer entries than x0 raises before the solver runs. scipy reads past the end of the array instead, and what MINPACK then does depends on the values that happen to follow it. A non-positive entry reaches MINPACK, which reports status = 0.

validate has no scipy counterpart; its default reports a degenerate solution that scipy would return silently.

Safe to call from a numba.prange loop. MINPACK reaches the callback through a Fortran module variable, which carries !$omp threadprivate and so resolves to one slot per thread.

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

Examples

>>> import numpy as np
>>> from numba import njit
>>> from scijit.optimize import root
>>> @njit
... def f(x):
...     return np.array([x[0] + 0.5 * (x[0] - x[1]) ** 3 - 1.0,
...                      0.5 * (x[1] - x[0]) ** 3 + x[1]])
>>> @njit
... def run():
...     return root(f, np.array([0.0, 0.0]))
>>> res = run()
>>> res.x
array([0.8411639, 0.1588361])
>>> res.success
True