mrgreen
mrgreen

Reputation: 7

Scipy.minimize - How to minimize two functions at the same time

I need to optimize the mixture of different substrats to different products. The amount of each substrat should add together to the best proportion for the components C,P,N,Si for the product. From the 4 substrats i need to find the perfect proportions for 2 products. I have no problem to optimize the functions separateley, but i would like to have everything in one target-function.

I tried to return the different optimize-problems but i get the error "objective function must return a scalar"

I hope someone can help me.

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize
from scipy.optimize import fsolve

class substrat_1:
    C = 0.93
    N = 0.005
    P = 0.031
    Si = 0.034

class substrat_2:
    C = 0.523
    N = 0.3
    P = 0.123
    Si = 0.054

class substrat_3:
    C = 0.257
    N = 0.176
    P = 0.461
    Si = 0.106

class substrat_4:
    C = 0.694
    N = 0.005
    P = 0.003
    Si = 0.298

class sort_1:
    C = 0.7
    N = 0.15
    P = 0.05
    Si = 0.1

class sort_2:
    C = 0.8
    N = 0.03
    P = 0.1
    Si = 0.07

y[0] substrat_1 -> sort_1
y[1] substrat_2 -> sort_1
y[2] substrat_3 -> sort_1
y[3] substrat_4 -> sort_1
y[4] substrat_1 -> sort_2
y[5] substrat_2 -> sort_2
y[6] substrat_3 -> sort_2
y[7] substrat_4 -> sort_2

def targetFun1(y):
    amount_sort1_C = substrat_1.C*y[0] + substrat_2.C*y[1] + substrat_3.C*y[2] + substrat_4.C*y[3]
    amount_sort1_N = substrat_1.N*y[0] + substrat_2.N*y[1] + substrat_3.N*y[2] + substrat_4.N*y[3]
    amount_sort1_P = substrat_1.P*y[0] + substrat_2.P*y[1] + substrat_3.P*y[2] + substrat_4.P*y[3]
    amount_sort1_Si = substrat_1.Si*y[0] + substrat_2.Si*y[1] + substrat_3.Si*y[2] + substrat_4.Si*y[3]

    return (np.abs(amount_sort1_C-sort_1.C)+np.abs(amount_sort1_N-sort_1.N)+np.abs(amount_sort1_P-sort_1.P)+np.abs(amount_sort1_Si-sort_1.Si)) 

bnds=((0,1),(0,1),(0,1),(0,1),(0,1),(0,1),(0,1),(0,1))
y0 = np.zeros((8,))

res = minimize(targetFun1, x0 = y0, method='SLSQP', bounds=bnds) 
y = res.x
print(y)

def targetFun2(y):
    amount_sort2_C = substrat_1.C*y[4] + substrat_2.C*y[5] + substrat_3.C*y[6] + substrat_4.C*y[7]
    amount_sort2_N = substrat_1.N*y[4] + substrat_2.N*y[5] + substrat_3.N*y[6] + substrat_4.N*y[7]
    amount_sort2_P = substrat_1.P*y[4] + substrat_2.P*y[5] + substrat_3.P*y[6] + substrat_4.P*y[7]
    amount_sort2_Si = substrat_1.Si*y[4] + substrat_2.Si*y[5] + substrat_3.Si*y[6] + substrat_4.Si*y[7]
    
    return (np.abs(amount_sort2_C-sort_2.C)+np.abs(amount_sort2_N-sort_2.N)+np.abs(amount_sort2_P-sort_2.P)+np.abs(amount_sort2_Si-sort_2.Si))


res = minimize(targetFun2, x0 = y0, method='SLSQP', bounds=bnds)
y = res.x
print(y)

Upvotes: 0

Views: 2404

Answers (1)

joni
joni

Reputation: 7157

I'd highly recommend using np.ndarrays instead of classes to store your data:

substrat = np.array([
    [0.93, 0.005, 0.031, 0.034],  # substrat_1
    [0.523, 0.3, 0.123, 0.054],   # substrat_2
    [0.257, 0.176, 0.461, 0.106], # substrat_3
    [0.694, 0.005, 0.003, 0.298], # substrat_4
])

sort = np.array([
    [0.7, 0.15, 0.05, 0.1], # sort_1
    [0.8, 0.03, 0.1, 0.07]  # sort_2
])

Then, your objective functions can be written as

def targetFun1(y):
    return np.sum(np.abs(substrat.T @ y[:4] - sort[0]))

def targetFun2(y):
    return np.sum(np.abs(substrat.T @ y[4:] - sort[1]))

Here, .T denotes the transpose of the matrix / np.ndarray substrat and @ denotes the matrix multiplication operator. Since 0 is a lower bound for both functions, one way to minimize both functions simultaneously is minimizing the sum of both functions:

res = minimize(lambda y: targetFun1(y) + targetFun2(y), x0 = y0, method='SLSQP', bounds=bnds)

# Finally, we only need to reshape the solution `res.x`:
solution = res.x.reshape(4,2)

Alternatively, you could write it as one objective function:

# Create BlockDiagonalmatrix:
# ( substrat.T.  0          )
# (     0        substrat.T )
#
DiagSubstrat = np.kron(np.eye(2), substrat.T)

def targetFun(y):
    return np.sum(np.abs(DiagSubstrat @ y - sort.flatten()))

Upvotes: 1

Related Questions