I am using cplex for an optimization model, to optimize diet compositions. Now I would like to add an extra variable/KPI, which is the sum of other KPIs in the model. However, this sum is a score with maximum values, which means that when each KPI exceeds a certain upper limit, the sum should not consider values above this value. The score is just a measure for each food composition, and therefore, the upper limits are not contraints to the model, but just limits to calculate the scores, so it should not affect the model results. Finally, I would like to minimize the total score.
I have tried to add this using mdl.sum to sum each indicator and added them to a list, and then I have tried to replace the sum by values of another list if the upper limits were exceeded. See example below:
# Decision variables, limited to be >= Food.qmin and <= Food.qmax
ftype = mdl.integer_vartype if ints else mdl.continuous_vartype
qty = mdl.var_dict(foods, ftype, lb=lambda f: f.qmin,ub=lambda f: f.qmax, name=lambda f:
"q_%s" % f.name)
# Limit range of nutrients, and mark them as KPIs
for c in constraints:
amount = mdl.sum(qty[f] * food_constraints[f.name, c.name] for f in foods)
mdl.add_range(c.qmin, amount, c.qmax)
mdl.add_kpi(amount, publish_name="Total %s" % c.name)
# add sum of indicators with max values:
score1 = mdl.sum(qty[f] * f.veg for f in foods)
score2 = mdl.sum(qty[f] * f.fruit for f in foods)
score3 = mdl.sum(qty[f] * f.fish for f in foods)
# add sum of indicators without max values:
score4 = mdl.sum(qty[f] * f.sugar for f in foods)
score5 = mdl.sum(qty[f] * f.fats for f in foods)
Score_sum_A = [score1, score2, score3]
max_scores = [10.6, 3.3, 9.7]
# cap values of Score_sum_A to max scores:
for i in range(len(max_scores)):
if Score_sum_A[i] >= max_scores[i]:
Score_sum_A[i] = max_scores[i]
else:
Score_sum_A[i] = Score_sum_A[i]
Score_sum_B = [score4, score5]
total_score_sum = sum(Score_sum_A + Score_sum_B)
mdl.add_kpi(total_score_sum , 'Total food score')
mdl.minimize(total_score_sum)
However, when running the model, I get the error: "TypeError: Cannot convert linear constraint to a boolean value", which is because of the if statement. So it seems like the model cannot operate with this sort of if statement inside the model.
Does anyone know, whether it is possible in cplex to add such a indicator with maximum values, without contraining the model results?
Any hint would be highly appreciated.
Thanks!
You cannot write
if Score_sum_A[i] >= max_scores[i]:
Score_sum_A[i] = max_scores[i]
since Score_sum_A is not a constant. You should use max
from docplex.mp.model import Model
mdl = Model(name='buses')
nbKids=300;
buses=[30,40,50]
#decision variables
mdl.nbBus = {b: mdl.integer_var(name="nbBus"+str(b)) for b in buses}
# Constraint
mdl.add_constraint(sum(mdl.nbBus[b]*b for b in buses) >= nbKids, 'kids')
# Objective
# logical constraint is the max of all nbBus
mdl.minimize(mdl.max(mdl.nbBus[b] for b in buses))
mdl.solve(log_output=True,)
mdl.export("c:\\temp\\buses.lp")
for v in mdl.iter_integer_vars():
print(v," = ",v.solution_value)
or if_then from docplex
from docplex.mp.model import Model
mdl = Model(name='buses')
nbbus40 = mdl.integer_var(name='nbBus40')
nbbus30 = mdl.integer_var(name='nbBus30')
mdl.add_constraint(nbbus40*40 + nbbus30*30 >= 300, 'kids')
mdl.minimize(nbbus40*500 + nbbus30*400)
mdl.solve()
for v in mdl.iter_integer_vars():
print(v," = ",v.solution_value)
print()
print("with if nb buses 40 more than 3 then nbBuses30 more than 7")
#if then constraint
mdl.add(mdl.if_then(nbbus40>=3,nbbus30>=7))
mdl.minimize(nbbus40*500 + nbbus30*400)
mdl.solve()
for v in mdl.iter_integer_vars():
print(v," = ",v.solution_value)
If I change slighly https://ibmdecisionoptimization.github.io/docplex-doc/mp/diet.html
from
def nb_products(mdl_, s_):
qvs = mdl_.find_matching_vars(pattern="q_")
return sum(1 for qv in qvs if s_[qv] >= 1e-5)
mdl.add_kpi(nb_products, 'Nb foods')
to
def nb_products(mdl_, s_):
qvs = mdl_.find_matching_vars(pattern="q_")
return sum(1 for qv in qvs if s_[qv] >= 1e-5)
def nb_products_capped(mdl_, s_):
qvs = mdl_.find_matching_vars(pattern="q_")
return mdl_.min(3,sum(1 for qv in qvs if s_[qv] >= 1e-5))
mdl.add_kpi(nb_products, 'Nb foods')
mdl.add_kpi(nb_products_capped,'Nb foods capped to 3')
then I will get
* KPI: Nb foods = 5.000000
* KPI: Nb foods capped to 3 = 3.000000
In a smaller example like zookpi
from docplex.mp.model import Model
mdl = Model(name='buses')
nbbus40 = mdl.integer_var(name='nbBus40')
nbbus30 = mdl.integer_var(name='nbBus30')
nbbus=nbbus30+nbbus40
mdl.add_kpi(nbbus,"nbbus")
mdl.add_constraint(nbbus40*40 + nbbus30*30 >= 300, 'kids')
mdl.minimize(nbbus40*500 + nbbus30*400)
mdl.solve(log_output=True,)
mdl.export("c:\\temp\\buses.lp")
for v in mdl.iter_integer_vars():
print(v," = ",v.solution_value)
for k in mdl.iter_kpis():
print(k," = ",k.solution_value)
adding
mdl.add_kpi(mdl.min(nbbus,3),"nbbuscapped3")
will give
nbBus40 = 6.0
nbBus30 = 2.0
DecisionKPI(name=nbbus,expr=nbBus40+nbBus30) = 8.0
DecisionKPI(name=nbbuscapped3,expr=min(nbBus40+nbBus30,3)) = 3