I believe that the accept_test function should be called by basinhopping before evaluating the function. The reason is that if there are constraints on the parameters, it would be more efficient to reject a hop before evaluating the function rather than afterwards. Furthermore, if a function cannot be evaluated with some combinations of parameters (returns inf, NaN, throws an error, etc), then we shouldn't call the basinhopping function.
E.g. the below function should be find an optimum around (2,4), but instead it returns an optimum of -inf at (1,1), which isn't a sensible answer.
from math import tanh
class MyBounds2(object):
def init(self):
self.xmax=np.array([6,6])
self.xmin=np.array([1,1])
def call(self,**kwargs):
x=kwargs["x_new"]
print(x)
constraint=bool(x.sum()<6)
print(constraint)
tmax=bool(np.all(x<=self.xmax))
tmin=bool(np.all(x>=self.xmin))
return tmax and tmin and constraint
def f(x):
ret= -(f1(x[0]) + f2(x[1])) # -ve sign for maximization
print(x[0],x[1],ret)
return(ret)
def f1(x):
return tanh(x)
def f2(x):
return (10 ** (0.2 * x))
def StackOFEx():
from scipy.optimize import basinhopping
# Starting point
x0 = [1., 1.]
minimizer_kwargs = dict(method="L-BFGS-B")
bounds=MyBounds2()
result = basinhopping(f,
x0,
minimizer_kwargs=minimizer_kwargs,
niter=200,accept_test=bounds)
print("global max: x = [%.4f, %.4f], f(x0) = %.4f" % (result.x[0], result.x[1], result.fun))
I don't think this is correct. See https://github.com/scipy/scipy/pull/7819#issuecomment-326719547 The accept_test is for rejecting the function value of the local minima and whether it is higher in energy than the last local minima. It's not for rejecting the x value of the step itself.
if there are constraints on the parameters, it would be more efficient to reject a hop before evaluating the function rather than afterwards.
Even if the starting point violates the constraints, the minimization can still converge to a point on the edge of the constraints that is valid. It's conceivable that there could be cases where this works better than starting inside the constraints?
Furthermore, if a function cannot be evaluated with some combinations of parameters (returns inf, NaN, throws an error, etc), then we shouldn't call the basinhopping function.
Ah, you mean a function that _can't_ be evaluated outside the constraints. The function could be wrapped so that it immediately returns inf when inputs are out of bounds? I'm not sure if that's the job of basinhopping() or the job of the person writing the function to be minimized?
Also I would hope the local minimizer just fails immediately as soon as it sees a NaN or error in the function evaluation.
but instead it returns an optimum of -inf at (1,1), which isn't a sensible answer.
You're giving a boundary to basinhopping for whether it should accept the step or not, but you're not giving any boundaries to the local minimizer, so it's just going to run down the hill forever, no matter what starting points basinhopping feeds to it. Obviously the documentation could be more clear about this.

"L-BFGS-B" doesn't support triangular constraints anyway, only box boundaries. You'd need to use COBYLA and express the constraint as a function instead of a class.
result.success is False and result.message is ABNORMAL_TERMINATION_IN_LNSRCH, so it didn't actually find a global minimum at all.
This works (with my mods from https://github.com/scipy/scipy/pull/7819, if that matters)
constraints = {'type': 'ineq', 'fun': lambda x: 6 - x.sum()}
bounds = ((1, 6), (1, 6))
minimizer_kwargs = {'constraints':constraints, 'bounds':bounds}
result = basinhopping(f, x0, minimizer_kwargs=minimizer_kwargs,
niter=200)
global max: x = [1.0000, 5.0000], f(x0) = -10.7616
Though this problem doesn't require basinhopping at all. Even this works:
minimize(f, (100, 100), bounds=bounds, constraints=constraints)
Out[49]:
fun: -10.761594155956233
jac: array([-0.42 , -4.605])
message: 'Optimization terminated successfully.'
nfev: 8
nit: 2
njev: 2
status: 0
success: True
x: array([ 1., 5.])
It's not for rejecting the x value of the step itself.
In the final part of the documentation of basinhopping, the parameter accept_test is used to define bounds (and of course, in that example, it fails to actually do so as defining xmin = [0, 0] will show), so either accept_test is intended for defining bounds (at which point it's buggy), or the documentation is straight out incorrect.
Most helpful comment
In the final part of the documentation of
basinhopping, the parameteraccept_testis used to define bounds (and of course, in that example, it fails to actually do so as definingxmin = [0, 0]will show), so eitheraccept_testis intended for defining bounds (at which point it's buggy), or the documentation is straight out incorrect.