Can I add kpi with maximum values without contraining CPLEX model?

119 views Asked by At

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!

1

There are 1 answers

7
Alex Fleischer On BEST ANSWER

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