Skip to main content

Multiple Objective Functions with Priority

Answered

Comments

4 comments

  • Lennart Lahrs
    • Gurobi Staff

    Hi Joyjit,

    Based on your description I would expect the primary objective to only contain \(\texttt{z_upbound}\), without the sum of all entries of \(\texttt{t_expupdated}\).

    Otherwise, I am not able to run your code example. Could you please provide a minimal reproducible example? See Tutorial: Preparing a Minimal Reproducible Example – Gurobi Help Center

    Best regards,
    Lennart

    0
  • Joyjit Bhowmick
    • Gurobi-versary
    • First Comment
    • First Question

    Hello Lennart, 

    Thank you for getting back to me. Apologies, for the confusion in the objective function, I had copied something from my previous model. Even with the objective function update as you suggested the same problem persists. I have prepared a MRE model which you can check to help me identify the problem as follows -  

    #Data Inputs 
    exp_pick_comp_time = [2412.459325024837, 2252.283005646575, 2543.8655009238464, 1428.3333333333333]
    slack_time_rev = [131.4061758990092, 291.5824952772714, 0.0, 1115.5321675905132]
    K_move = [0, 1, 2]
    K_hold = [3]
    num_cob = 3
    num_ded = 1
    V_move = [112, 107, 77, 93, 69, 83, 108, 50, 61, 78, 123, 86, 36, 16, 121, 31, 120, 54]
    dict_time_reduction_V_move= {112: 299.7113402061856, 107: 216.44827586206895, \
                                 77: 215.68067226890756, 93: 705.5384615384614, \
                                 69: 248.42268041237116, 83: 125.11904761904762, \
                                 108: 315.6578947368421, 50: 141.44827586206895, \
                                 61: 120.19469026548671, 78: 95.19469026548671, \
                                 123: 472.0697674418605, 86: 479.26530612244903, \
                                 36: 198.1578947368421, 16: 266.6190476190476, \
                                 121: 316.35087719298247, 31: 384.68067226890753, \
                                 120: 239.69565217391303, 54: 279.6666666666667}

    t_add = [[241.66666666666669, 258.33333333333337, 291.6666666666667, 308.33333333333337, 311.6666666666667, 341.6666666666667, 328.33333333333337, 181.66666666666666, 195.0, 175.0, 175.0, 195.0, 161.66666666666669, 215.0, 251.66666666666669, 225.0, 141.66666666666669], [120.00000000000001, 103.33333333333334, 103.33333333333333, 100.0, 83.33333333333334, 126.66666666666667, 146.66666666666669, 83.33333333333334, 163.33333333333334, 183.33333333333334, 203.33333333333334, 240.00000000000003, 223.33333333333331, 243.33333333333334, 263.3333333333333, 270.0, 123.33333333333334], [70.0, 86.66666666666667, 136.66666666666669, 150.0, 126.66666666666667, 140.0, 116.66666666666667, 50.0, 163.33333333333334, 200.00000000000003, 220.00000000000003, 223.33333333333334, 190.0, 243.33333333333334, 296.6666666666667, 270.0, 90.0], [233.33333333333334, 233.33333333333334, 250.00000000000003, 266.6666666666667, 270.0, 313.33333333333337, 316.6666666666667, 173.33333333333334, 170.0, 133.33333333333331, 133.33333333333331, 170.0, 153.33333333333331, 190.0, 210.0, 200.0, 133.33333333333334], [111.66666666666667, 61.66666666666667, 45.0, 41.66666666666667, 25.0, 68.33333333333333, 91.66666666666667, 28.33333333333333, 105.0, 125.00000000000001, 145.0, 181.66666666666669, 165.0, 185.0, 205.0, 245.0, 98.33333333333337], [213.33333333333334, 230.0, 273.3333333333333, 280.0, 220.0, 196.66666666666669, 173.33333333333334, 150.0, 306.6666666666667, 343.33333333333337, 363.33333333333337, 366.6666666666667, 333.3333333333333, 386.6666666666667, 433.3333333333333, 406.6666666666667, 233.33333333333337], [381.6666666666667, 346.6666666666667, 330.0, 346.6666666666667, 350.0, 393.3333333333333, 416.6666666666667, 273.33333333333337, 250.0, 230.00000000000003, 210.00000000000003, 226.66666666666669, 190.0, 170.0, 130.0, 95.0, 128.33333333333337], [218.33333333333334, 218.33333333333334, 235.00000000000003, 251.66666666666669, 255.0, 298.3333333333333, 301.6666666666667, 158.33333333333334, 155.0, 135.0, 115.0, 115.0, 78.33333333333333, 58.33333333333334, 58.33333333333333, 65.0, 58.33333333333334], [101.66666666666667, 118.33333333333334, 151.66666666666669, 168.33333333333334, 171.66666666666669, 201.66666666666669, 188.33333333333334, 25.0, 58.33333333333334, 111.66666666666667, 131.66666666666669, 135.0, 101.66666666666667, 155.0, 191.66666666666669, 165.0, 41.666666666666686], [85.0, 101.66666666666667, 151.66666666666669, 185.0, 181.66666666666669, 195.00000000000003, 171.66666666666669, 25.0, 58.33333333333334, 95.0, 115.0, 118.33333333333333, 85.0, 138.33333333333334, 191.66666666666669, 165.0, 25.0], [113.33333333333334, 130.0, 180.00000000000003, 193.33333333333334, 138.33333333333334, 120.0, 96.66666666666667, 61.66666666666666, 206.66666666666669, 243.33333333333334, 263.33333333333337, 266.6666666666667, 233.33333333333331, 286.6666666666667, 340.0, 313.33333333333337, 133.33333333333337], [288.33333333333337, 238.33333333333337, 205.00000000000003, 205.0, 225.0, 285.0, 308.3333333333333, 165.0, 141.66666666666669, 121.66666666666667, 101.66666666666667, 118.33333333333333, 81.66666666666667, 61.66666666666667, 25.0, 45.0, 75.0], [398.33333333333337, 363.33333333333337, 346.6666666666667, 363.33333333333337, 366.6666666666667, 410.0, 433.3333333333333, 290.0, 266.6666666666667, 246.66666666666669, 226.66666666666669, 243.33333333333334, 206.66666666666669, 186.66666666666669, 146.66666666666669, 111.66666666666667, 145.0], [111.66666666666667, 115.0, 135.0, 131.66666666666669, 71.66666666666667, 48.33333333333333, 36.66666666666667, 36.66666666666667, 191.66666666666669, 215.00000000000003, 235.00000000000003, 251.66666666666669, 231.66666666666669, 271.6666666666667, 295.0, 281.6666666666667, 131.66666666666669], [121.66666666666667, 138.33333333333334, 171.66666666666669, 188.33333333333334, 191.66666666666669, 221.66666666666669, 208.33333333333334, 61.66666666666666, 58.33333333333334, 75.0, 111.66666666666667, 115.0, 81.66666666666667, 135.0, 171.66666666666669, 145.0, 41.666666666666686], [78.33333333333334, 78.33333333333334, 95.0, 91.66666666666667, 75.0, 118.33333333333333, 121.66666666666667, 58.33333333333334, 155.0, 175.00000000000003, 195.00000000000003, 215.00000000000003, 198.33333333333331, 235.0, 255.0, 245.0, 98.33333333333337], [325.00000000000006, 316.6666666666667, 325.0, 341.6666666666667, 345.0, 388.3333333333333, 400.0, 256.66666666666663, 245.00000000000003, 225.00000000000003, 205.00000000000003, 213.33333333333334, 176.66666666666669, 165.0, 125.00000000000001, 63.33333333333334, 96.66666666666669], [311.6666666666667, 296.6666666666667, 298.3333333333333, 315.0, 318.33333333333337, 361.6666666666667, 380.0, 236.66666666666666, 218.33333333333334, 198.33333333333334, 178.33333333333334, 193.33333333333334, 156.66666666666669, 138.33333333333334, 98.33333333333333, 43.33333333333334, 76.66666666666669]]
    t_deduct = [[299.7113402061856, 216.44827586206895, 215.68067226890756, 705.5384615384614, 248.42268041237116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 125.11904761904762, 315.6578947368421, 141.44827586206895, 120.19469026548671, 95.19469026548671, 472.0697674418605, 479.26530612244903, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 198.1578947368421, 266.6190476190476, 316.35087719298247, 384.68067226890753, 239.69565217391303, 279.6666666666667], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
    y_insertion = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]

    #Import Packages
    import gurobipy as gp
    from gurobipy import GRB
    from itertools import product

    #Declare the model for the insertion IP
    m_insertion = gp.Model()
    m_insertion.ModelSense = GRB.MINIMIZE
    m_insertion.Params.LogToConsole = 0

    #Declare ranges
    v_move_range = range(len(V_move))
    detour_pos_range = range(len(t_add[0])) 

    #Define Decision Variable x_insertion
    x_insertion = []
    for i in v_move_range:
        x_insertion.append([])
        for j in detour_pos_range:
            x_insertion[i].append(m_insertion.addVar(vtype = GRB.BINARY))

    # Declare decision variable t_expupdated 
    t_expupdated = []
    for i in range (num_cob+num_ded):
        t_expupdated.append(m_insertion.addVar(lb=0.0, vtype=GRB.CONTINUOUS))

    # Declare decision variable z_upbound
    z_upbound = m_insertion.addVar(lb=0.0, vtype=GRB.CONTINUOUS)

    #Primary Objective
    m_insertion.setObjectiveN(z_upbound, index=0, priority =2, abstol = 0.00)

    #Secondary Objective
    m_insertion.setObjectiveN(gp.quicksum(t_expupdated[i] for i in range(num_cob + num_ded)), index=1, priority =1)

    ### CONSTRAINTS ###

    #The total expected time after adding a point to K_hold should not exceed the current slack time
    for k in K_hold:
        m_insertion.addConstr(gp.quicksum(x_insertion[i][j]*t_add[i][j]*y_insertion[k][j] for i in range(len(V_move)) \

                                          for j in detour_pos_range) <= slack_time_rev[k])

    #A detour position can not insert more than one stopping point
    for j in detour_pos_range:
        m_insertion.addConstr(gp.quicksum(x_insertion[i][j] for i in v_move_range) <= 1)

    # A stopping point in V_move cannot be inserted into more than one detour position
    for i in v_move_range:
        m_insertion.addConstr(gp.quicksum(x_insertion[i][j] for j in detour_pos_range) <= 1)

    # We can allow more than one insertion 
    m_insertion.addConstr(gp.quicksum(x_insertion[i][j] for i in v_move_range for j in detour_pos_range) >= 1)

    # Calculating of total expected waiting time after adding to the resources of K_hold
    for k in K_hold:
        m_insertion.addConstr(t_expupdated[k]==exp_pick_comp_time[k] +\
                              gp.quicksum(x_insertion[i][j]*t_add[i][j]*y_insertion[k][j] for i in v_move_range for j in detour_pos_range))

    #Calculating of total expected waiting time after removing from a sequence of K_move
    for k in K_move:
        m_insertion.addConstr(t_expupdated[k] == exp_pick_comp_time[k] - sum(t_deduct[k][i]*x_insertion[i][j] for i in v_move_range))

    #The resources which are not part of K_move or K_hold, exp pick comp time remain unchanged
    K_unchanged = list(set([i for i in range (num_cob + num_ded)]) - set(K_hold) -set(K_move))
    if len(K_unchanged) != 0:
        for k in K_unchanged:
             m_insertion.addConstr(t_expupdated[k] == exp_pick_comp_time[k])

    #Setting an upper bound for each of the updated expected travel time
    for k in range(num_cob + num_ded):
        m_insertion.addConstr(z_upbound >= t_expupdated[k])

    ### IP INSERTION - OPTIMIZATION AND RESULT ###### IP INSERTION - OPTIMIZATION AND RESULT ###### IP INSERTION - OPTIMIZATION AND RESULT ###### IP INSERTION - OPTIMIZATION AND RESULT ###
    m_insertion.optimize()

    if m_insertion.status == GRB.OPTIMAL:
        print(f' updated maximum expected pick completion time: {m_insertion.ObjVal}')
        print(f' max pick comp time improvement: {max(exp_pick_comp_time) - m_insertion.ObjVal} seconds')
        for i in v_move_range:
              for j in detour_pos_range:
                    if x_insertion[i][j].X > 0.001:
                        print(f'stopping point {V_move[i]} is inserted into detour position {j} out of {len(t_add[0])} detour positions')
                        print(f'time added {t_add[i][j]} and deducted {dict_time_reduction_V_move[V_move[i]]}')
        print(f'previous exp pick comp time: {exp_pick_comp_time}')
        print(f'updated exp pick comp time: {t_expupdated}')
    else: 
        print('no feasible insertion found')

    In the output section I get the following 2 lines which is the confusing part. It is always the case my model selects the last detour position in the, (here 16 out of 17 as indexing started  from 0 to 16 for the 17 detour positions). This is always the case no matter how bad the outcome is, the model is selecting the last detour position. If we look at the t_add matrix, specifically for the stopping point 31 (you can look it up by t_add[-3]), we will see that clearly there are other options which add less time than the current choice of 98.33333333333337. The lowest in the list is 58.333 (t_add[-3][7]) which could have improved both of the objective function even more. Could you please help me understand why it is happening?

    stopping point 31 is inserted into detour position 16 out of 17 detour positions
    time added 98.33333333333337 and deducted 384.68067226890753
    1
  • Lennart Lahrs
    • Gurobi Staff

    Hi Joyjit,

    You were missing a for-loop in one of your constraint formulations, which caused unintended behavior. Please find the updated constraint formulation below. I highly recommend using gp.quicksum() instead of sum(), like you have done in most of your code, for performance reasons. I, therefore, have also updated your constraint formulation to use gp.quicksum().

    # Calculating of total expected waiting time after removing from a sequence of K_move
    for k in K_move:
        m_insertion.addConstr( 
            t_expupdated[k] == exp_pick_comp_time[k] - 
            gp.quicksum(t_deduct[k][i] * x_insertion[i][j] for i in v_move_range for j in detour_pos_range)
        )

    Best regards,
    Lennart

    0
  • Joyjit Bhowmick
    • Gurobi-versary
    • First Comment
    • First Question

    Hello Lennart,

    Thank you for looking into the problem and coming up with a solution. I tried the fix with different variations of input values so that there is no edge cases. The fix seems to work perfectly fine now and the results are making sense. 

    Thanks again

    Joyjit Bhowmick

    0

Please sign in to leave a comment.