scijit.optimize.basinhopping

scijit.optimize.basinhopping(func, x0, niter=100, T=1.0, stepsize=0.5, minimizer_kwargs=None, take_step=None, accept_test=None, callback=None, interval=50, disp=False, niter_success=None, rng=None, target_accept_rate=0.5, stepwise_factor=0.9, args=None, seed=None, xtol=0.0001, ftol=0.0001, gtol=1e-05)

Basin-hopping global optimization by random restarts.

Alternates a random displacement with a local minimization, keeping or rejecting each new basin by a Metropolis test.

Callback style Afunc and the gradient are plain @njit functions passed as first-class arguments. No @cfunc, no .address.

Parameters:
func@njit function func(x, *args) -> float64

Objective.

x0float64 array, shape (N,)

Starting point. Cast to float64 and copied.

niterint, optional

Basin-hopping iterations. Default 100.

Tfloat, optional

Metropolis temperature. Default 1.0. T = 0 makes beta infinite, so every uphill move is rejected. T < 0 inverts the test.

stepsizefloat, optional

Initial random-displacement size. Default 0.5. Must be positive.

minimizer_kwargsdict, optional

The local minimizer and its gradient. 'method' is one of 'Nelder-Mead', 'Powell', 'BFGS' or 'CG', defaulting to 'BFGS'; 'jac' is a plain @njit grad(x, *args) -> float64 array. Omitting 'jac' under a gradient method uses forward differences. Inside @njit the dict must be written as a LITERAL at the call site. See Notes.

take_step, accept_testNone

Accepted only at their defaults. See Notes.

callbackcallable, optional

Called once on the initial quench and once per iteration, as callback(x, f, accept) on the TRIAL point, and halts the run on a truthy return. Two spellings are served: a plain Python callable, and a numba @njit callback(x, f, accept) returning a bool. See Notes.

intervalint, optional

Period of the step-size adaptation, in iterations. Default 50. The step is adapted whenever nstep % interval == 0, so the sign carries no meaning. 0 raises.

dispbool, optional

Print the per-step lines, the step-size adaptation line, the new-global-minimum line and the local-minimization-failure warning. Default False.

niter_successint or None, optional

Stop after this many consecutive iterations without improving the global minimum. None (default) uses niter + 2, which the run can still reach when it breaks early. Every integer is a real threshold, -1 included: it stops after the first iteration that does not improve the global minimum.

rngint or numpy.random.Generator, optional

Source of the displacement and Metropolis draws. None (default) uses the internal xorshift64* generator; an integer builds a numpy.random.default_rng for the call; a Generator is drawn from directly and its state advances. See Notes.

target_accept_ratefloat, optional

Acceptance rate the adaptive step size aims at. Default 0.5. Must lie in (0, 1).

stepwise_factorfloat, optional

Multiplicative factor applied when adapting the step. Default 0.9. Must lie in (0, 1).

argstuple or ndarray, optional

Extra parameters forwarded to func and to the gradient. 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. None (default) calls func(x). An ndarray or a list arrives as ONE argument instead, func(x, args). See Notes.

seedint, optional

BEYOND SCIPY. Seeds the internal xorshift64* generator, which is the rng at None path. None (default) draws fresh entropy per call, so two identical calls give different answers. See Notes.

xtol, ftolfloat, optional

Tolerances handed to 'Nelder-Mead' and 'Powell'. Both 1e-4.

gtolfloat, optional

Gradient tolerance handed to 'BFGS' and 'CG'. Default 1e-5.

Returns:
resBasinhoppingResult

A namedtuple carrying x, fun, nit, nfev, njev, minimization_failures, success and message. message is a list holding one string, so res.message[0] is the sentence. njev is 0 under 'Nelder-Mead' and 'Powell'. success is the success flag of the local minimization that produced x. minimization_failures counts the quench from x0 as well as the per-iteration ones.

Raises:
ValueError

A 2-D or empty x0, niter < 0, stepsize <= 0, a target_accept_rate or stepwise_factor outside (0, 1), an unknown 'method', a key in minimizer_kwargs other than 'method' and 'jac', take_step or accept_test away from None, a rng that is not None, an integer or a numpy.random.Generator, or a callback that is a @cfunc, a raw function address or a non-callable.

ZeroDivisionError

interval == 0. interval divides the step counter.

See also

scipy.optimize.basinhopping

The scipy routine this mirrors.

scijit.optimize.differential_evolution

Population-based, no start point.

scijit.optimize.brute

Exhaustive grid search.

scijit.optimize.minimize

One local minimization, no restarts.

Notes

minimizer_kwargs carries 'method' and 'jac' only. scipy forwards the whole dict to minimize; here args, xtol, ftol and gtol are explicit arguments of basinhopping itself.

ADDITIVE: args is a top-level parameter. scipy publishes none, and raises TypeError for one. A tuple is unpacked into the objective’s argument list, func(x, *args); an ndarray or a list reaches func as ONE argument instead, because the arity of a compiled call is fixed when it compiles.

Inside @njit the dict must be written as a LITERAL at the call site. A dict built elsewhere and passed in as a variable raises TypingError: a literal is typed where it is written, carrying its keys and a callable value, while a variable has to be unboxed and a numba dict cannot hold a function.

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 iteration. It is also not prange-safe, because the slot is module state that two concurrent runs share. Inside @njit only the @njit spelling is accepted, since a Python callable cannot cross into compiled code as an argument. The @njit spelling must RETURN a bool; a Python one may return None, which scipy reads as no halt.

NOT IMPLEMENTED: take_step and accept_test. Both are Python callables. Step taking, adaptation and acceptance reproduce scipy’s RandomDisplacement + AdaptiveStepsize + Metropolis with all their defaults, including consuming an RNG draw on downhill moves.

UNAVAILABLE: lowest_optimization_result. It is a nested result object whose field set varies with the local minimizer.

ADDITIVE: njev is always present and is 0 on the derivative-free minimizers. scipy carries the key only when the quench used a gradient, and can, because its result is a dict.

With rng at None the draws come from an internal xorshift64* generator carried in a per-call state array. seed selects its stream and seed=None draws a fresh one per call.

DELIBERATELY DIFFERENT: an integer rng builds a numpy.random.default_rng, which is PCG64. scipy’s check_random_state maps an integer to the legacy RandomState, which is MT19937 and which compiled code cannot hold, so the two explore differently at the same integer. A Generator passed in is used directly, and its draws are numpy’s own.

A rng at None is safe to call from a numba.prange loop, because the generator state is per-call. One Generator SHARED across a prange is not: 70,499 of 100,000 draws were distinct across 32 threads. An integer rng builds its generator inside the call, so it is per-call as well. A Python callback is not prange-safe either, and serializes the loop.

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

Examples

>>> import numpy as np
>>> from numba import njit
>>> from scijit.optimize import basinhopping
>>> @njit
... def q(x):
...     return (x[0] - 1.5) ** 2 + (x[1] + 0.5) ** 2
>>> @njit
... def run():
...     return basinhopping(q, np.array([2.0, 2.0]), 20)
>>> res = run()
>>> np.round(res.x, 4)
array([ 1.5, -0.5])
>>> res.njev > 0
True