PROC OPTMODEL Statements and Output

For completeness, all statements are shown. Statements that are new or changed from Chapter 25 are indicated.

proc optmodel;
   set <str> DEPOTS;
   read data depot_data into DEPOTS=[depot];

   set DAYS;
   str day_name {DAYS};
   num demand {DEPOTS, DAYS};
   read data demand_data into DAYS=[_N_];
   num num_days = card(DAYS);
   DAYS = 0..num_days-1;
   read data demand_data into [_N_]
      {depot in DEPOTS} <demand[depot,_N_-1]=col(depot)>;

   set LENGTHS;
   num length_prob {LENGTHS};
   num cost {LENGTHS};
   num price_same {LENGTHS};
   num price_diff {LENGTHS};
   read data length_data into LENGTHS=[length]
      length_prob=prob cost price_same price_diff;

   num transition_prob {DEPOTS, DEPOTS};
   read data transition_prob_data into [i]
      {j in DEPOTS} <transition_prob[i,j]=col(j)>;
   for {i in DEPOTS, j in DEPOTS}
      transition_prob[i,j] = transition_prob[i,j] / 100;

   num transfer_cost {DEPOTS, DEPOTS} init 0;
   read data transfer_cost_data nomiss into [i]
      {j in DEPOTS} <transfer_cost[i,j]=col(j)>;

   num repair_capacity {DEPOTS} init 0;
   read data repair_data into [depot] repair_capacity;

   num rental_price {i in DEPOTS, j in DEPOTS, day in DAYS, length in LENGTHS} =
      (if i = j then price_same[length] else price_diff[length])
    - (if day = 5 and length = 1 then &saturday_discount);

   num max_length = max {length in LENGTHS} length;
   num mod {s in -max_length..num_days+max_length} = mod(s+num_days,num_days);

   var NumCars >= 0;

   var NumUndamagedCarsStart {DEPOTS, DAYS} >= 0;
   var NumDamagedCarsStart {DEPOTS, DAYS} >= 0;

   var NumCarsRented_i_day {i in DEPOTS, day in DAYS} >= 0 <= demand[i,day];
   impvar NumCarsRented
      {i in DEPOTS, j in DEPOTS, day in DAYS, length in LENGTHS} =
      transition_prob[i,j] * length_prob[length] * NumCarsRented_i_day[i,day];

   var NumUndamagedCarsIdle {DEPOTS, DAYS} >= 0;
   var NumDamagedCarsIdle {DEPOTS, DAYS} >= 0;

   var NumUndamagedCarsTransferred {i in DEPOTS, DEPOTS diff {i}, DAYS} >= 0;
   var NumDamagedCarsTransferred {i in DEPOTS, DEPOTS diff {i}, DAYS} >= 0;
   impvar NumCarsTransferred {i in DEPOTS, j in DEPOTS diff {i}, day in DAYS} =
      NumUndamagedCarsTransferred[i,j,day]
    + NumDamagedCarsTransferred[i,j,day];

   var NumDamagedCarsRepaired {i in DEPOTS, DAYS} >= 0 <= repair_capacity[i];

   max Profit =
      sum {i in DEPOTS, j in DEPOTS, day in DAYS, length in LENGTHS}
         (rental_price[i,j,day,length] - cost[length])
       * NumCarsRented[i,j,day,length]
    + sum {i in DEPOTS, day in DAYS}
         &damage_prob * &damage_charge * NumCarsRented_i_day[i,day]
    - sum {i in DEPOTS, j in DEPOTS diff {i}, day in DAYS}
         transfer_cost[i,j] * NumCarsTransferred[i,j,day]
    - &opportunity_cost_per_week * NumCars;

   con Undamaged_Inflow_con {i in DEPOTS, day in DAYS}:
      NumUndamagedCarsStart[i,day]
    = (1 - &damage_prob) * sum {j in DEPOTS, length in LENGTHS}
         NumCarsRented[j,i,mod[day-length],length]
    + sum {j in DEPOTS diff {i}}
         NumUndamagedCarsTransferred[j,i,mod[day-&transfer_length]]
    + NumDamagedCarsRepaired[i,mod[day-&repair_length]]
    + NumUndamagedCarsIdle[i,mod[day-1]];

   con Damaged_Inflow_con {i in DEPOTS, day in DAYS}:
      NumDamagedCarsStart[i,day]
    = &damage_prob * sum {j in DEPOTS, length in LENGTHS}
         NumCarsRented[j,i,mod[day-length],length]
    + sum {j in DEPOTS diff {i}}
         NumDamagedCarsTransferred[j,i,mod[day-&transfer_length]]
    + NumDamagedCarsIdle[i,mod[day-1]];

   con Undamaged_Outflow_con {i in DEPOTS, day in DAYS}:
      NumUndamagedCarsStart[i,day]
    = NumCarsRented_i_day[i,day]
    + sum {j in DEPOTS diff {i}} NumUndamagedCarsTransferred[i,j,day]
    + NumUndamagedCarsIdle[i,day];

   con Damaged_Outflow_con {i in DEPOTS, day in DAYS}:
      NumDamagedCarsStart[i,day]
    = NumDamagedCarsRepaired[i,day]
    + sum {j in DEPOTS diff {i}} NumDamagedCarsTransferred[i,j,day]
    + NumDamagedCarsIdle[i,day];

   con NumCars_con:
      NumCars = sum {i in DEPOTS} (
         length_prob[3] * NumCarsRented_i_day[i,0]
       + sum {length in 2..3} length_prob[length] * NumCarsRented_i_day[i,1]
       + NumUndamagedCarsStart[i,2]
       + NumDamagedCarsStart[i,2]);

The remaining statements are new in this example. The following statements declare an additional index set and parameters and then read the additional input data:

   set EXPANSIONS;
   str expansion_depot {EXPANSIONS};
   num expansion_amount {EXPANSIONS};
   num expansion_cost {EXPANSIONS};
   num expansion_prerequisite {EXPANSIONS};
   read data expansion_data into EXPANSIONS=[expansion]
      expansion_depot expansion_amount expansion_cost expansion_prerequisite;

The BINARY option in the following VAR statement declares ExpandCapacity to be a binary variable:

   var ExpandCapacity {EXPANSIONS} binary;

The following statement uses the CONSTANT function to effectively remove the previously declared upper bound on the NumDamagedCarsRepaired variables by replacing it with the largest machine-representable number:

   for {expansion in EXPANSIONS, day in DAYS}
      NumDamagedCarsRepaired[expansion_depot[expansion],day].ub =
      constant('BIG');

This large number does not cause any numerical difficulties for the solver, because PROC OPTMODEL recognizes this special constant and treats the variable as having no upper bound. The following Expansion_con constraint accounts for the new limit on the number of cars repaired, according to the expansion options that are chosen:

   con Expansion_con {i in DEPOTS, day in DAYS}:
      NumDamagedCarsRepaired[i,day]
   <= repair_capacity[i]
    + sum {expansion in EXPANSIONS: expansion_depot[expansion] = i}
         expansion_amount[expansion] * ExpandCapacity[expansion];

The following ExpansionPrerequisite_con constraint enforces the rule that $\Variable{ExpandCapacity[expansion]} = 1$ implies that $\Variable{ExpandCapacity[expansion\_ prerequisite[expansion]]} = 1$:

   con ExpansionPrerequisite_con {expansion in EXPANSIONS:
         expansion_prerequisite[expansion] ne .}:
      ExpandCapacity[expansion]
   <= ExpandCapacity[expansion_prerequisite[expansion]];

The following Cardinality constraint enforces the limit on the number of expansions that are chosen:

   con Cardinality:
      sum {expansion in EXPANSIONS} ExpandCapacity[expansion]
   <= &max_num_expansions;

The following objective declaration uses the previously declared objective function:

   max Profit2 = Profit -
      sum {expansion in EXPANSIONS}
         expansion_cost[expansion] * ExpandCapacity[expansion];

The following statements call the mixed integer linear programming solver, round all variables, and print the specified parts of the solution:

   solve;
   for {j in 1.._NVAR_} _VAR_[j] = round(_VAR_[j].sol);
   print expansion_depot ExpandCapacity;
   print NumDamagedCarsRepaired;
   print NumCars;
quit;

Figure 26.1 shows the output from the mixed integer linear programming solver.

Figure 26.1: Output from Mixed Integer Linear Programming Solver

The OPTMODEL Procedure

Problem Summary
Objective Sense Maximization
Objective Function Profit2
Objective Type Linear
   
Number of Variables 294
Bounded Above 0
Bounded Below 259
Bounded Below and Above 29
Free 0
Fixed 6
Binary 5
Integer 0
   
Number of Constraints 124
Linear LE (<=) 27
Linear EQ (=) 97
Linear GE (>=) 0
Linear Range 0
   
Constraint Coefficients 1208

Performance Information
Execution Mode Single-Machine
Number of Threads 1

Solution Summary
Solver MILP
Algorithm Branch and Cut
Objective Function Profit2
Solution Status Optimal
Objective Value 130792.96054
   
Relative Gap 0
Absolute Gap 0
Primal Infeasibility 1.98952E-13
Bound Infeasibility 0
Integer Infeasibility 1E-6
   
Best Bound 130792.96054
Nodes 1
Iterations 132
Presolve Time 0.00
Solution Time 0.02

[1] expansion_depot ExpandCapacity
1 Birmingham 0
2 Birmingham 0
3 Manchester 1
4 Manchester 1
5 Plymouth 0

NumDamagedCarsRepaired
  0 1 2 3 4 5
Birmingham 20 20 20 20 20 20
Glasgow 0 0 0 0 0 0
Manchester 22 22 22 22 22 22
Plymouth 0 0 0 0 0 0

NumCars
955


Note that the resulting profit is higher than in Chapter 25. This result is expected because the repair capacity expansion options allow more flexibility. The optimal solution and objective value differ from Williams (2013), because of errors that will be corrected in a subsequent printing by Wiley.