5

I'm trying to maximize a function defined by sympy but cannot make it work. The basic idea can be summarized as follows:

    import sympy
    from scipy.optimize import minimize
    from sympy.utilities.lambdify import lambdify

    a,b,G = sympy.symbols('a b G')
    func = (G - a)**2 + b
    my_func = lambdify((G,a,b), -1*func)
    results = minimize(my_func,[0.1,0.1,0.1])

The code works if I define a single-variable function, but as long as I have more than one variable, I receive the following error message.

    TypeError: <lambda>() takes exactly 3 arguments (1 given)

Can someone help me to identify where went wrong?

user3821012
  • 1,291
  • 2
  • 16
  • 27
  • 1
    `minimize` expects the function to be of "one argument" in the sense the thing you pass in is a single object. The one object, however, can be a list. Are you sure you need `sympy` for this? `Scipy` (not `sympy`) does numerical stuff, not symbolic... – Dair Dec 06 '15 at 08:04
  • 1
    @Dair Yes, I need to use sympy for convenience, otherwise it'll be a pain for me to define the objective function. I understand that the scope is only for numerical computation, but do you know whether there is a way to lambdify a symbolic function so that it can be passed to scipy.minimize? – user3821012 Dec 06 '15 at 08:25
  • I can't think of a symbolic way but the non-symbolic way is not too bad: `func = lambda x: (x[0] - x[1])**2 + x[2]` – Dair Dec 06 '15 at 09:40
  • Does [this](http://stackoverflow.com/questions/31865549/want-to-do-multi-variation-minimize-with-sympy) help? – lhcgeneva Dec 06 '15 at 10:31
  • @lhcgeneva, theoretically this can work. But I have more than 20 independent variables -- in other words, the dimension of x is more than 20 -- and this number of variables can change. This means I have to write out all the variables by hand, and need to change it whenever the number of variables changes, so this is not ideal... – user3821012 Dec 06 '15 at 11:56
  • But you are doing the same thing now, or am I getting this wrong? Can't you just define your jacobian dynamically? – lhcgeneva Dec 06 '15 at 12:04
  • No, I'm not calculating the Jacobian. The above code just gave an example. What I am actually doing is optimization of a symbolic function. I have a super long symbolic function (with 20 variables), and need to find the values (for the 20 variables) that maximize the function. – user3821012 Dec 06 '15 at 12:37
  • No, you are not calculating the Jacobian, but you are defining your function, just like in the post I linked to. The Jacobian you can also calculate using twenty or whatever number of variables, that shouldn't be a problem. – lhcgeneva Dec 06 '15 at 17:41

1 Answers1

5

As @Dair pointed out, sympy's lambdify in general requires more than one arguments, while scipy expects only one argument, a list (or an array) that contains all the values of each variable. Because my objective function is most conveniently defined using sympy, I need to find a way to get around this incompatibility of sympy and scipy.

@lhcgeneva pointed out the answer to a similar question. This answer does not conveniently handle a large number of independent variables, especially when the number of independent variables can change, requiring writing out the "vectorized" version of the objective function to be redefined. However, inspired by this post, I figured out the following solution using *tuple():

    import sympy
    from scipy.optimize import minimize
    from sympy.utilities.lambdify import lambdify

    a,b,G = sympy.symbols('a b G')
    func = -1*((G - a)**2 + b)
    my_func = lambdify((G,a,b), func)

    def my_func_v(x):
        return my_func(*tuple(x))

    results = minimize(my_func_v,[0.1,0.1,0.1])

In the example I gave, it seems unnecessary to use *tuple(), but for the problem I want to solve, it saves a lot of hassle. Here's an example that is more similar to the question that I want to solve

NUM_VAR = 10
x = np.array(sympy.symbols('x0:%d'%NUM_VAR))
func = np.sum((x-1)**2)
my_func = lambdify(x, func)


def my_func_v(x):
    return my_func(*tuple(x))

results = minimize(my_func_v,np.zeros(NUM_VAR))

This *tuple() thing can save me from writing out all the elements of x like the following (for the case of NUM_VAR=10):

def my_func_v(x):
    return my_func(x[0],x[1],x[2],x[3],x[4],x[5],x[6],x[7],x[8],x[9])

Also, we don't need to change my_func_v when NUM_VAR changes.

Community
  • 1
  • 1
user3821012
  • 1,291
  • 2
  • 16
  • 27