scijit.optimize.fmin_powell

scijit.optimize.fmin_powell(func, x0, args=(), xtol=0.0001, ftol=0.0001, maxiter=None, maxfun=None, full_output=0, disp=1, retall=0, callback=None, direc=None)

Minimize a function using Powell’s direction-set method.

Derivative-free. Minimizes along a set of directions that is updated each sweep, using a scalar Brent line search.

Callable from Python and from inside @njit. Both entries run the same compiled core, so they cannot disagree.

Parameters:
funccallable

A plain @njit func(x, *args) -> float.

x0array_like

Initial guess. Any rank is flattened.

argstuple or ndarray, optional

Extra parameters. A tuple is unpacked into separate arguments after x, so args=(a, b) calls func(x, a, b). Its elements may be of any type a compiled call accepts, arrays and strings included. Default (), which calls func(x). An ndarray or a list arrives as ONE argument instead, func(x, args). See Notes.

xtolfloat, optional

Line-search tolerance. Default 1e-4. The inner Brent search runs at xtol * 100.

ftolfloat, optional

Relative convergence tolerance on f between sweeps. Default 1e-4.

maxiter, maxfunint, float or None, optional

Sweep and evaluation budgets. None (default) means N*1000 for BOTH, but supplying only ONE leaves the OTHER unbounded rather than at the default. np.inf is accepted and means unbounded.

full_outputbool, optional

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

dispint, optional

1 (default) prints a summary. The status line appears only on success and becomes a RuntimeWarning otherwise; the counters print either way. 0 is silent, and is what a prange sweep wants: see Notes.

retallbool, optional

Append allvecs to the return. Compile-time constant.

callbackcallable or None, optional

Called once per iteration as callback(xk), with a copy of the current iterate. A plain Python function may instead be written callback(intermediate_result) and is handed an object carrying x and fun. Raising StopIteration stops the run and returns the current best. From inside @njit the callback is an @njit callback(xk).

direcarray_like or None, optional

(N, N) initial direction set. None (default) is the identity.

Returns:
xoptndarray

The minimizer.

foptfloat

func(xopt, args). With full_output.

direcndarray, shape (N, N)

The final direction set. With full_output.

iterint

Sweeps performed. With full_output.

funcallsint

Objective evaluations used. With full_output.

warnflagint

0 converged, 1 maxfun reached, 2 maxiter reached, 3 the search entered a NaN region. With full_output.

allvecslist of ndarray

The best point after each sweep, iter + 1 entries of shape (N,), the first being x0. With retall. From inside @njit this is a numba.typed.List of the same arrays.

Raises:
ValueError

Empty x0, a direc whose shape is not (N, N), a maxfun at or below zero, or a callback that is neither a callable nor an @njit function. A maxiter of 0 or below does NOT raise: one sweep runs and warnflag is 2.

Warns:
OptimizeWarning

A direc that is not full rank.

See also

scipy.optimize.fmin_powell

The scipy routine this mirrors.

scijit.optimize.fmin

Derivative-free, simplex.

scijit.optimize.fmin_bfgs

Uses a gradient.

scijit.optimize.minimize

method='Powell' reaches the same core.

Notes

Covers scipy’s _minimize_powell along its unbounded path only, where bounds=None. warnflag 4, the out-of-bounds code, therefore never appears.

full_output and retall must be compile-time literals inside @njit: they select the return type.

callback takes a plain Python function or an @njit function, and both halt on StopIteration. The Python spelling reaches the compiled core through a numba.objmode block over a module-level slot: it takes the GIL once per iteration, and two concurrent solves that both install one overwrite each other, so a prange loop over it serializes and is not reentrant.

Two differences from scipy follow from the compiled path. A plain Python callable cannot be an argument of a compiled function, so a call written inside @njit passes the @njit spelling and a Python one is refused by numba. And numba matches no exception class, except StopIteration being a TypingError on 0.66, so any exception raised inside an @njit callback stops the run the way StopIteration does, where scipy propagates it. A Python callback keeps scipy’s behaviour: only StopIteration halts, and anything else reaches the caller.

A direc whose shape is not (N, N) raises ValueError here. scipy does not check the shape: a short one raises IndexError from inside the sweep and a wide one runs with the extra columns ignored.

An empty x0 raises ValueError here, and so does a maxfun at or below zero. scipy refuses both too, through its private _MaxFuncCallError, which subclasses RuntimeError and is not importable; the empty x0 reaches it because the default maxfun of N*1000 is zero when N is zero.

disp reproduces scipy’s summary and scipy’s warning, at scipy’s default, so a bare call writes to stdout and warns on a limit path. The warning takes the GIL through a numba.objmode block and the printing through Python’s stdout, so with disp set a prange loop over this routine still gives the right answer but serializes. disp=0 is the parallel path.

Under retall, allvecs is a python list of 1-D arrays, as scipy’s is. From inside @njit it is a numba.typed.List of the same arrays.

args is unpacked into the objective’s argument list, func(x, *args), which is scipy’s contract. An ndarray or a list args reaches func as ONE argument instead. scipy accepts neither spelling at this name, so the one-argument form is additive and no scipy-shaped call reaches it.

Pure @njit, so it is safe to call from a numba.prange loop with disp=0 and with callback left at None or given as an @njit function.

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

Examples

>>> import numpy as np
>>> from numba import njit
>>> from scijit.optimize import fmin_powell
>>> @njit
... def rosen(x):
...     s = 0.0
...     for i in range(x.size - 1):
...         s += 100.0 * (x[i + 1] - x[i] ** 2) ** 2 + (1.0 - x[i]) ** 2
...     return s
>>> @njit
... def run():
...     return fmin_powell(rosen, np.array([0.0, 0.0]), (), disp=0)
>>> np.round(run(), 8)
array([1., 1.])