racon
racon

Reputation: 23

Dynamically choose argument for which to minimize a function in python using scipy.optimize

I have a function which takes a list of variables as an argument and I would like to minimize this function using scipy.optimize.minimize.
The problem is that it is decided on runtime for which variable in the argument list the minimization should be done. All other variables will get a fixed value.

Let's make an example to clarify:

a = 1
c = 1.1
d = -1.2

def func( b ):
    return function_to_minimize( array=[a,b,c,d] )

sol = scipy.optimize.minimize( func, [b0], args=(a,c,d) )

This works, however, it could be that b, c and d are known and I want to optimize a to find the minimum solution.

To make it even more complicated, the length of the list is not known either. That means there could be a variabel e, f, g, ... and so on.

The actual notation is as follows. The element which is None is the one which should be optimized for.

array = [1, 1.1, None, -0.5, 4]

def func(arr):
    return function_to_minimize(arr)

startvalue = 1.0
sol = scipy.optimize.minimize( func, [startvalue], args='Array without None' )

Is there a way to tell scipy.optimize.minimize for which element to optimize for? Is there perhaps a smart lambda trick which I could do?

I would really appreciate your help!

Upvotes: 2

Views: 2156

Answers (2)

racon
racon

Reputation: 23

I just wanted to provide the adaptation of unutbu's answer regarding an unknown number of variables using a list arr as single input, where the parameter to fit is set to None.

The function to minimize fm is a dummy function, simply trying to minimize the standard deviation of the array using numpy.std.

It looks a bit clunky and not very pythonesque, but it works.

import textwrap
import scipy.optimize as optimize


def make_model(n,*fixed):
    template = textwrap.dedent("""
        import numpy as np
        def fm(arr):
            return np.std(arr)
        def func(variable, {fixed}):
            {variable} = variable
            return fm(["""+",".join(["a"+str(i) for i in range(n)])+"""])
        """)
    settuple = tuple(['a'+str(i) for i in range(n)])
    variable = set(settuple).difference(fixed)
    ns = dict()
    funcstr = template.format(variable=', '.join(variable), fixed=', '.join(fixed))
    print(funcstr)  # comment out if you don't want to see the function
    exec funcstr in ns
    return ns['func']


def solve(initial_guess, n, **givens):
    fixed = tuple(givens.keys())
    vals = tuple(givens.values())
    sol = optimize.minimize(make_model(n,*fixed), initial_guess, args=vals)
    return sol


arr = [1, 1.1, None, -0.5, 4, 3]

s = ""
for i,a in enumerate(arr):
    if a is not None:
        s+=", a"+str(i)+"="+str(a)

print "print(solve(initial_guess=1, n="+str(len(arr))+s+"))"  # comment out if you don't want to see the function       
exec "print(solve(initial_guess=1, n="+str(len(arr))+s+"))"

Upvotes: 0

unutbu
unutbu

Reputation: 879591

As you know, the function to be minimized changes depending on what parameters are given. So we need to write some code which defines the function dynamically. One way to do this is to define a template string, do some string formatting to modify the template based on which parameters are given, and then use exec to define the function. There is some precedence for this -- the standard library uses this technique to define namedtuples.

So, for example, if the expression we wish to minimize is

4*(b-a)**2 + 5*(c-d)**2

then you could use

import textwrap
import scipy.optimize as optimize

def make_model(*fixed):
    template = textwrap.dedent("""
        def func(variable, {fixed}):
            {variable} = variable
            return 4*(b-a)**2 + 5*(c-d)**2
        """)
    variable = set(('a', 'b', 'c', 'd')).difference(fixed)
    ns = dict()
    funcstr = template.format(variable=', '.join(variable), fixed=', '.join(fixed))
    print(funcstr)  # comment out if you don't want to see the function
    exec funcstr in ns
    return ns['func']

def solve(initial_guess, **givens):
    fixed = tuple(givens.keys())
    vals = tuple(givens.values())
    sol = optimize.minimize(make_model(*fixed), initial_guess, args=vals)
    return sol

print(solve(initial_guess=1, a=1, c=1.1, d=-1.2))

which yields

def func(variable, a, c, d):
    b = variable
    return 4*(b-a)**2 + 5*(c-d)**2

   status: 0
  success: True
     njev: 1
     nfev: 3
 hess_inv: array([[1]])
      fun: array([ 26.45])
        x: array([ 1.])
  message: 'Optimization terminated successfully.'
      jac: array([ 0.])
      nit: 0

print(solve(initial_guess=(1, 1), a=1, c=1.1))

yields

def func(variable, a, c):
    b, d = variable
    return 4*(b-a)**2 + 5*(c-d)**2

   status: 0
  success: True
     njev: 3
     nfev: 12
 hess_inv: array([[1, 0],
       [0, 1]])
      fun: 2.4611848645596973e-16
        x: array([ 0.99999999,  1.1       ])
  message: 'Optimization terminated successfully.'
      jac: array([  1.19209279e-08,   2.88966118e-08])
      nit: 1

Upvotes: 1

Related Questions