pythoncplexconstraint-programmingdocplexkpi

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


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!


Solution

  • 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