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 implies that :
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
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 |
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 | 3.694822E-13 |
Bound Infeasibility | 2.220446E-16 |
Integer Infeasibility | 1E-6 |
Best Bound | 130792.96054 |
Nodes | 1 |
Iterations | 139 |
Presolve Time | 0.00 |
Solution Time | 0.03 |
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.