Constrained Linear Optimization Setup

398 views Asked by At

I am struggling with the following setup.

My data is as follows:

Group   ID  Wt       Coeff      Coeff*Wt
------  --- ------   -------    -------
Group1  A   10.00%   1.00000     0.100 
Group1  B   10.00%   1.00000     0.100 
Group1  C   10.00%   3.00005     0.300 
Group2  D   10.00%   1.00000     0.100 
Group2  E   10.00%   1.00000     0.100 
Group2  F   10.00%   1.00000     0.100 
Group2  G   10.00%   7.80016     0.780 
Group3  H   10.00%   7.80485     0.780 
Group3  I   10.00%   1.00000     0.100 
Group3  J   10.00%   0.39529     0.040 



Objective function: Fmin = mimimize(sum of weights * coeff)

I need to implement following constraints:

Sum of Weights*Coeff of Group1 = 20% of total minimized fmin
Sum of Weights*Coeff of Group1 = 45% of total minimized fmin
Sum of Weights*Coeff of Group1 = 35% of total minimized fmin

And the following bounding conditions:

Weights <=10% and Weights > 0.30%

And

Sum of weights = 100%

I am trying to acomplish this with the following code.

I don't know why this is not working:

from scipy.optimize import linprog

c = [ 1.0000 ,1.0000 ,3.0001 ,1.0000 ,1.0000 ,1.0000 ,7.8002 ,7.8049 ,1.0000 ,0.3953 ]

groupPerID = ['Group1','Group1','Group1','Group2','Group2','Group2','Group2','Group3','Group3','Group3']

groupList = ['Group1','Group2','Group3']

groupUpperBound = [0.20,0.45,0.40]

A_eq_list = []
A_eq_list.append([1]*len(c))

b_eq_list = [1]

for idx,currentGroup in enumerate(groupList):

    matches = [i for i in range(len(groupPerID)) if groupPerID[i] == currentGroup]

    currentGroupUB = groupUpperBound[idx]

    x_list = [float(-1*currentGroupUB*coeff) for coeff in c]

    for idx in matches:
        x_list[idx] = float((1-currentGroupUB)*c[idx])

    A_eq_list.append(x_list)

b_eq_list.extend([0]*len(groupUpperBound))
res = linprog(c, A_eq=A_eq_list, b_eq=b_eq_list,bounds =(0.003,0.1),options={'tol':0.05})
print(res)

Can someone please point out what mistake I am making?

1

There are 1 answers

2
tBuLi On BEST ANSWER

So I implemented it in my scipy wrapper symfit which takes care of all the boiler plate code. It now works, except for the fact that I did not yet implement your bounds on the weights. However, I think those are wrong as stated in your question, because the only way to meet the constraint that all the weight should sum up to 1, is to set them all to the upper limit of 0.1. Other than that, here is my attempt:

from symfit import parameters, Minimize, Variable, Eq
import numpy as np

# Make 10 weight parameters w_i to optimize
weights = parameters(','.join('w_{}'.format(i) for i in range(1, 11)))
c = np.array([1.0000, 1.0000, 3.0001, 1.0000, 1.0000, 1.0000, 7.8002, 7.8049, 1.0000, 0.3953])
f = Variable()

for w_i in weights:
    w_i.min = 0.003
    w_i.max = 1.0
    w_i.value = 0.1

sum_of_group_1 = sum(c_i * w_i for c_i, w_i in zip(c, weights)[0:3])
sum_of_group_2 = sum(c_i * w_i for c_i, w_i in zip(c, weights)[3:7])
sum_of_group_3 = sum(c_i * w_i for c_i, w_i in zip(c, weights)[7:10])
# Function to minimize
model = {f: sum_of_group_1 + sum_of_group_2 + sum_of_group_3}

constraints = [
    Eq(0.20 * sum_of_group_1, 0.45 * sum_of_group_2),
    Eq(0.20 * sum_of_group_1, 0.35 * sum_of_group_3),
    Eq(sum(weights), 1)
]

fit = Minimize(model, constraints=constraints)
fit.eval_jacobian = None  # Workaround needed because f is just a scalar, not an array
fit_result = fit.execute()

print(fit_result)
print(sum(fit_result.value(w) for w in weights)) # >>> 1.0

You can read more in the docs here.