MVMV
MVMV

Reputation: 37

Scipy optimized function with dynamic conditions

Here is the piece of code that I'm currently running:

def fun_test(x):
    return 3*x[0]**2 + x[1] + 4*x[2]

con = [{"type" : "ineq", "fun" : lambda x: fun_test(x)},
              {"type" : "ineq", "fun" : lambda x: x[0]-x[1]},
              {"type" : "ineq", "fun" : lambda x: x[1]-x[2]},]

res = minimize(fun_test, method='COBYLA', x0=[3, 3, 3], constraints=con)
print("Resulting factors:",res.x)

So here, I'm optimizing the factors of fun_test with the conditions that x[0] >= x[1], and x[1] >= x[2]. So far so good, and the result is:

Resulting factors: [ 0.84965894 -0.31342016 -0.46308519]

Now, my goal is to be able to set the last two conditions dynamically, for example deciding that x[2] >= x[1] and x[1] >= x[0], with a list stating the superiority of each factor to the others. For example, the list [2, 1, 0] would mean that x[2]>=x[1] and x[1]>=x[0].

What I'm doing right now is this:

def fun_test(x):
    return 3*x[0]**2 + x[1] + 4*x[2]
    
Lorder = [2,1,0]
con = [{"type" : "ineq", "fun" : lambda x: fun_test(x)}]
for i in range(np.shape(Lorder)[0]-1):
    con.append({"type" : "ineq", "fun" : lambda x: x[Lorder[i]]-x[Lorder[i+1]]})

res = minimize(fun_test, method='COBYLA', x0=[3, 3, 3], constraints=con)
print("Resulting factors:",res.x)

But it doesn't work, and it gives me this result:

Resulting factors: [-0.22910497  2.05130651 -0.55219344]

Would you have an idea about why it doesn't work and how to make it work?

Upvotes: 1

Views: 129

Answers (1)

joni
joni

Reputation: 7157

You need to capture the loop variable i when creating lambda expressions in a loop, see here for more details:

con = [{"type" : "ineq", "fun" : lambda x: fun_test(x)}]
for i in range(np.shape(Lorder)[0]-1):
    con.append({"type" : "ineq", "fun" : lambda x, i=i: x[Lorder[i]]-x[Lorder[i+1]]})

However, note also that

( -x[0] + x[1] ) >= 0
( -x[1] + x[2] ) >= 0

can be written as a matrix-vector product

        ( -1  1   0)   (x[0]) 
D @ x = ( 0  -1   1) @ (x[1]) >= 0
        ( 0   0   0)   (x[2])

So you could add all constraints at once:

n = 3
D = np.zeros((n, n))
for i in range(len(Lorder)-1):
    k = Lorder[i]
    r = Lorder[i+1]
    D[-i+1, [k, r]] = [1.0, -1.0]

con = [{"type" : "ineq", "fun" : lambda x: fun_test(x)},
       {"type" : "ineq", "fun" : lambda x: D @ x}]

Upvotes: 1

Related Questions