Search code examples
optimizationpandasindexingscipyminimize

scipy.optimize.minimize with general array indexing


I want to solve an optimization problem with the method 'COBYLA' in scipy.optimize.minimize as follows:

test = spopt.minimize(testobj, x_init, method='COBYLA', constraints=cons1)
y = test.x
print 'solution x =', y

However, since the program is quite large, a scalable way to write the objective function (and the constraints) is to use a general index for the arguments. For example, if I could use x['parameter1'] or x.param1 instead of x[0], then the program would be easier to read and debug. I tried both writing x as an object or a pandas Series with general indexing like x['parameter1'], as follows:

def testobj(x):
    return x['a']**2 + x['b'] + 1

def testcon1(x):
    return x['a']

def testcon2(x):
    return x['b']

def testcon3(x):
    return 1 - x['a'] - x['b']


x_init = pd.Series([0.1, 0.1])
x_init.index = ['a','b']

cons1 = ({'type': 'ineq', 'fun': testcon1}, \
    {'type': 'ineq', 'fun': testcon2}, \
    {'type': 'ineq', 'fun': testcon3})

but whenever I pass that into the minimize routine, it throws an error:

return x['a']**2 + x['b'] + 1
ValueError: field named a not found

It works perfectly if I use the normal numpy array. Perhaps I'm not doing it right, but is that a limitation of the minimize function that I have to use numpy array and not any other data structure? The scipy documentation on this topic mentions that the initial guess has to be ndarray, but I'm curious how is the routine calling the arguments, because for pandas Series calling the variable with x[0] or x['a'] are equivalent.


Solution

  • As you note, scipy optimize uses numpy arrays as input, not pandas Series. When you initialize with a pandas series, it effectively converts it to an array and so you cannot access the fields by name anymore.

    Probably the easiest way to go is to just create a function which re-wraps the parameters each time you call them; for example:

    def make_series(params):
        return pd.Series(params, index=['a', 'b'])
    
    def testobj(x):
        x = make_series(x)
        return x['a']**2 + x['b'] + 1
    
    def testcon1(x):
        x = make_series(x)
        return x['a']
    
    def testcon2(x):
        x = make_series(x)
        return x['b']
    
    def testcon3(x):
        x = make_series(x)
        return 1 - x['a'] - x['b']
    
    x_init = make_series([1, 1])
    test = spopt.minimize(testobj, x_init, method='COBYLA', constraints=cons1)
    print('solution x =', test.x)
    # solution x = [  1.38777878e-17   0.00000000e+00]