Search code examples
pythonoptimizationscipyclass-method

Minimising an objective function which is a method of a class


I have a class XYZ() with methods build and Predict. I have another class BuildSurrogate() which has an attribute SurrogateClass pointing to an object of type class XYZ(). A method of SurrogateClass is _minima which is defined below.

class XYZ():
        ...

    def build(self):
        ...

    def Predict(self):
        ...

class BuildSurrogate():
    def __init__(self, args):
        self.SurrogateClass = XYZ(*args)

    def _minima(self):
        """
        This returns the minima of the surrogate
        """

        x0 = copy(self.x0_)
        solution = minimize(fun=self.SurrogateClass.Predict, x0=x0, args=args, method='Nelder-Mead')# x0=x0, options={'xtol':1e-5,'ftol':1e-5})
        return solution

The script raises an error:

 Traceback (most recent call last):
  File "/path/to/optiblade/Optimizer/SurrogateModel.py", line 1067, in <module>
    print SurrogateObj._minima()
  File "/path/to/optiblade/Optimizer/SurrogateModel.py", line 1006, in _minima
    solution = minimize(fun=self.SurrogateClass.Predict, x0=x0, method='Nelder-Mead')# x0=x0, options={'xtol':1e-5,'ftol':1e-5})
  File "/usr/local/lib/python2.7/dist-packages/scipy/optimize/_minimize.py", line 435, in minimize
    return _minimize_neldermead(fun, x0, args, callback, **options)
  File "/usr/local/lib/python2.7/dist-packages/scipy/optimize/optimize.py", line 439, in _minimize_neldermead
    fsim[0] = func(x0)
ValueError: setting an array element with a sequence.

I presume this error is raised because self where self = self.SurrogateClass is to be passed to Predict. Do I have to change my code structure or is there a way I can solve the above problem without changing my code structure?


Solution

  • Starting with an example for minimize documentation:

    In [183]: x0 = [1.3, 0.7, 0.8, 1.9, 1.2]
    In [185]: optimize.minimize(optimize.rosen, x0, method='Nelder-Mead', tol=1e-6)
    Out[185]: 
     final_simplex: (array([[ 1.00000002,  1.00000002,  1.00000007,  1.00000015,  1.00000028],
    ...
                 x: array([ 1.00000002,  1.00000002,  1.00000007,  1.00000015,  1.00000028])
    

    Define a callable class that does the same thing:

    In [191]: class Class1(object):
        def __init__(self):
            pass
        def __call__(self,args):
            return optimize.rosen(args)
    In [190]: optimize.minimize(Class1(), x0, method='Nelder-Mead', tol=1e-6)
    

    A Class1 object is callable, so I can use it exactly as though I had defined a rosen method

    In [191]: Class1()(x0)
    Out[191]: 848.22000000000003
    

    I could have defined it this way:

    In [192]: class Class2(object):
        def rosen(self,args):
            return optimize.rosen(args)   
    
    In [193]: optimize.minimize(Class2().rosen, x0, method='Nelder-Mead', tol=1e-6)
    
    In [194]: Class2().rosen(x0)
    Out[194]: 848.22000000000003
    

    Adding in class layer shouldn't make a difference, just so long as the first argument to minimize is a function that returns a value when called with x0.

    In your case you just need to define:

    class XYZ(object):
        def predict(self, args):
            return optimize.rosen(args)
    
    class BuildSurrogate(object):
        def __init__(self):
            self.surrogateObj = XYZ()
        def minima(self,x0):
            return optimize.minimize(self.surrogateObj.predict, x0, method='Nelder-Mead', tol=1e-6)
    

    And use:

    BuildSurrogate().minima(x0)
    

    Or something comparable.

    I could have also defined self.SurrogateClass=XYZ and called self.SurrogateClass().predict. As long as predict is a method of the XYZ class I have to create an XYZ() object before using it.