0

I'm getting this error when using basin-hopping: basinhopping_bounds() got an unexpected keyword argument 'f_new'

I'm trying to implement the analysis of X,F models in Python to solving a DTLZ7 problem.

So, I've started with a problem with 4 linear FO, which the result I know. When trying to solve the problem using basin-hopping for global minimization, I'm getting the error above (scipy-1.2.1.). Does anybody knows what is going wrong?

Here follows part of the code:

f1 = f_linear([0.06, 0.53, 0.18, 0.18, 0.06], "max")
f2 = f_linear([25, 70, 60, 95, 45], "max")
f3 = f_linear([0, 32.5, 300, 120, 0], "min")
f4 = f_linear([0.1, 0.1, 0.11, 0.35, 0.33], "min")
A_eq = np.array([[1, 1, 1, 1, 1]])
b_eq = np.array([3000])
x0_bounds = (0, 850)
x1_bounds = (0, 220)
x2_bounds = (0, 1300)
x3_bounds = (0, 1615)
x4_bounds = (0, 700)
F = [f1, f2, f3, f4]
def mu_D(x, F):
    x = np.array(x)
    return max([f_.mu(x) for f_ in F])
def basinhopping_bounds(x):
    resp = True
    if np.dot(x, A_eq[0]) != b_eq[0]:
        resp = False
    if x[0] < x0_bounds[0] or x[0] > x0_bounds[1]:
        resp = False
    if x[1] < x1_bounds[0] or x[1] > x1_bounds[1]:
        resp = False
    if x[2] < x2_bounds[0] or x[2] > x2_bounds[1]:
        resp = False
    if x[3] < x3_bounds[0] or x[3] > x3_bounds[1]:
        resp = False
    if x[4] < x4_bounds[0] or x[4] > x4_bounds[1]:
        resp = False
    return resp


cobyla_constraints = [
    {"type": "ineq", "fun": lambda x: x[0]},
    {"type": "ineq", "fun": lambda x: x0_bounds[1] - x[0]},
    {"type": "ineq", "fun": lambda x: x[1]},
    {"type": "ineq", "fun": lambda x: x1_bounds[1] - x[1]},
    {"type": "ineq", "fun": lambda x: x[2]},
    {"type": "ineq", "fun": lambda x: x2_bounds[1] - x[2]},
    {"type": "ineq", "fun": lambda x: x[3]},
    {"type": "ineq", "fun": lambda x: x3_bounds[1] - x[3]},
    {"type": "ineq", "fun": lambda x: x[4]},
    {"type": "ineq", "fun": lambda x: x4_bounds[1] - x[4]},
    {"type": "eq", "fun": lambda x: np.dot(x, A_eq[0]) - b_eq[0]},
]
minimizer_kwargs = {"args": F, "method": "SLSQP", "constraints": cobyla_constraints}
opt.basinhopping(
    mu_D,
    f1.x_max,
    minimizer_kwargs=minimizer_kwargs,
    accept_test=basinhopping_bounds,
    disp=True,
)
basinhopping step 0: f 1

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-11-ba4f3efaec5d> in <module>
      5     minimizer_kwargs=minimizer_kwargs,
      6     accept_test=basinhopping_bounds,
----> 7     disp=True,
      8 )

~/anaconda3/lib/python3.6/site-packages/scipy/optimize/_basinhopping.py in basinhopping(func, x0, niter, T, stepsize, minimizer_kwargs, take_step, accept_test, callback, interval, disp, niter_success, seed)
    674                " successfully"]
    675     for i in range(niter):
--> 676         new_global_min = bh.one_cycle()
    677 
    678         if callable(callback):

~/anaconda3/lib/python3.6/site-packages/scipy/optimize/_basinhopping.py in one_cycle(self)
    152         new_global_min = False
    153 
--> 154         accept, minres = self._monte_carlo_step()
    155 
    156         if accept:

~/anaconda3/lib/python3.6/site-packages/scipy/optimize/_basinhopping.py in _monte_carlo_step(self)
    127         for test in self.accept_tests:
    128             testres = test(f_new=energy_after_quench, x_new=x_after_quench,
--> 129                            f_old=self.energy, x_old=self.x)
    130             if testres == 'force accept':
    131                 accept = True

TypeError: basinhopping_bounds() got an unexpected keyword argument 'f_new'

SuperKogito
  • 2,998
  • 3
  • 16
  • 37

2 Answers2

1

Your bounds definition is incorrect. In basinhopping your bounds should be defined as a class instance. You should use the following:

import numpy as np
import scipy.optimize as opt


class MyBounds(object):
    ''' 
    bounds class to make sure your variable is with in the inspected bounds
    '''
    def __init__(self, xmin, xmax):
        self.xmax = np.array(xmax)
        self.xmin = np.array(xmin)

    def __call__(self, **kwargs):
        x = kwargs["x_new"]
        tmax = bool(np.all(x <= self.xmax))
        tmin = bool(np.all(x >= self.xmin))
        return tmax and tmin

# init bounds
lower_bounds = [  0,   0,    0,    0,   0]
upper_bounds = [850, 220, 1300, 1615, 700]
my_bounds    = MyBounds(lower_bounds, upper_bounds)

...

# optimize 
result = opt.basinhopping(mu_D,
                          f1.x_max,
                          minimizer_kwargs = minimizer_kwargs,
                          accept_test      = my_bounds,
                          disp             = True)

Also consider merging your constraints. If you use arrays instead of element-wise constraints, you will only have three constraints. However, looking at your constraints (with exception to the last one) you are just defining your boundaries again.

SuperKogito
  • 2,998
  • 3
  • 16
  • 37
0

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

This docs describes the accept_test argument. It must be callable that recognizes a set of keyword arguments (or at least doesn't choke when given them):

accept_test : callable, accept_test(f_new=f_new, x_new=x_new, f_old=fold, x_old=x_old), optional

Define a test which will be used to judge whether or not to accept the step. 
This will be used in addition to the Metropolis test based on “temperature” T. 
The acceptable return values are True, False, or "force accept". If any of the 
tests return False then the step is rejected. If the latter, then this will 
override any other tests in order to accept the step. This can be used, for 
example, to forcefully escape from a local minimum that basinhopping is 
trapped in.

Your function only takes on positional argument:

def basinhopping_bounds(x):

You can also see how minimize calls your function in the error traceback:

testres = test(f_new=energy_after_quench, x_new=x_after_quench,
--> 129                            f_old=self.energy, x_old=self.x)
hpaulj
  • 221,503
  • 14
  • 230
  • 353