For completeness, all statements are shown. Statements that are new or changed from Chapter 1 are indicated.
proc optmodel; set <str> OILS; num hardness {OILS}; read data hardness_data into OILS=[oil] hardness; set PERIODS; num cost {OILS, PERIODS}; read data cost_data into PERIODS=[_N_] {oil in OILS} <cost[oil,_N_]=col(oil)>; var Buy {OILS, PERIODS} >= 0; var Use {OILS, PERIODS} >= 0; impvar Manufacture {period in PERIODS} = sum {oil in OILS} Use[oil,period]; num last_period = max {period in PERIODS} period; var Store {OILS, PERIODS union {0}} >= 0 <= &store_ub; for {oil in OILS} do; fix Store[oil,0] = &init_storage; fix Store[oil,last_period] = &init_storage; end; set VEG = {oil in OILS: substr(oil,1,3) = 'veg'}; set NONVEG = OILS diff VEG; impvar Revenue = sum {period in PERIODS} &revenue_per_ton * Manufacture[period]; impvar RawCost = sum {oil in OILS, period in PERIODS} cost[oil,period] * Buy[oil,period]; impvar StorageCost = sum {oil in OILS, period in PERIODS} &storage_cost_per_ton * Store[oil,period]; max Profit = Revenue - RawCost - StorageCost; con Veg_ub_con {period in PERIODS}: sum {oil in VEG} Use[oil,period] <= &veg_ub; con Nonveg_ub_con {period in PERIODS}: sum {oil in NONVEG} Use[oil,period] <= &nonveg_ub; con Flow_balance_con {oil in OILS, period in PERIODS}: Store[oil,period-1] + Buy[oil,period] = Use[oil,period] + Store[oil,period]; con Hardness_ub_con {period in PERIODS}: sum {oil in OILS} hardness[oil] * Use[oil,period] >= &hardness_lb * Manufacture[period]; con Hardness_lb_con {period in PERIODS}: sum {oil in OILS} hardness[oil] * Use[oil,period] <= &hardness_ub * Manufacture[period];
The remaining statements are new in this example. The BINARY option in the following VAR statement declares IsUsed
to be a binary variable:
var IsUsed {OILS, PERIODS} binary;
The .ub
variable suffix imposes an upper bound on the Use
variable, in preparation for the subsequent Link constraint. The validity of this upper bound follows from the Veg_ub_con
and Nonveg_ub_con constraints.
for {period in PERIODS} do; for {oil in VEG} Use[oil,period].ub = &veg_ub; for {oil in NONVEG} Use[oil,period].ub = &nonveg_ub; end;
The following Link constraint enforces the rule that implies that :
con Link {oil in OILS, period in PERIODS}: Use[oil,period] <= Use[oil,period].ub * IsUsed[oil,period];
The following Logical1, Logical2, and Logical3 constraints correspond directly to the three extra conditions in the problem statement:
con Logical1 {period in PERIODS}: sum {oil in OILS} IsUsed[oil,period] <= &max_num_oils_used; con Logical2 {oil in OILS, period in PERIODS}: Use[oil,period] >= &min_oil_used_threshold * IsUsed[oil,period]; con Logical3 {oil in {'veg1','veg2'}, period in PERIODS}: IsUsed[oil,period] <= IsUsed['oil3',period]; num hardness_sol {period in PERIODS} = (sum {oil in OILS} hardness[oil] * Use[oil,period].sol) / Manufacture[period].sol;
Because PROC OPTMODEL automatically recognizes that this model is a mixed integer linear programming problem, the following SOLVE statement calls the MILP solver, as shown in Figure 2.1:
solve;
Figure 2.1: Summaries from Mixed Integer Linear Programming Solver
Problem Summary | |
---|---|
Objective Sense | Maximization |
Objective Function | Profit |
Objective Type | Linear |
Number of Variables | 125 |
Bounded Above | 0 |
Bounded Below | 30 |
Bounded Below and Above | 85 |
Free | 0 |
Fixed | 10 |
Binary | 30 |
Integer | 0 |
Number of Constraints | 132 |
Linear LE (<=) | 66 |
Linear EQ (=) | 30 |
Linear GE (>=) | 36 |
Linear Range | 0 |
Constraint Coefficients | 384 |
Performance Information | |
---|---|
Execution Mode | Single-Machine |
Number of Threads | 1 |
Solution Summary | |
---|---|
Solver | MILP |
Algorithm | Branch and Cut |
Objective Function | Profit |
Solution Status | Optimal within Relative Gap |
Objective Value | 100278.7037 |
Relative Gap | 0.0000876456 |
Absolute Gap | 8.7897553311 |
Primal Infeasibility | 9.238204E-13 |
Bound Infeasibility | 8.033221E-14 |
Integer Infeasibility | 4.016611E-15 |
Best Bound | 100287.49346 |
Nodes | 483 |
Iterations | 4680 |
Presolve Time | 0.00 |
Solution Time | 0.37 |
The following PRINT statement creates the output shown in Figure 2.2:
print Buy Use Store IsUsed Manufacture hardness_sol Logical1.body;
The .body
constraint suffix accesses the left-hand side value of the Logical1 constraint. For each period, the solution uses no more
than three oils, as shown in Figure 2.2. The following CREATE DATA statements create multiple output data sets, as in Chapter 1:
create data sol_data1 from [oil period] Buy Use Store IsUsed; create data sol_data2 from [period] Manufacture; quit;
Figure 2.2: Output from Mixed Integer Linear Programming Solver
[1] | [2] | Buy | Use | Store | IsUsed |
---|---|---|---|---|---|
oil1 | 0 | 500.00 | |||
oil1 | 1 | 0.00 | 0.000 | 500.00 | 0 |
oil1 | 2 | 0.00 | 0.000 | 500.00 | 0 |
oil1 | 3 | 0.00 | 0.000 | 500.00 | 0 |
oil1 | 4 | 0.00 | 0.000 | 500.00 | 0 |
oil1 | 5 | 0.00 | 0.000 | 500.00 | 0 |
oil1 | 6 | 0.00 | -0.000 | 500.00 | -0 |
oil2 | 0 | 500.00 | |||
oil2 | 1 | 0.00 | 40.000 | 460.00 | 1 |
oil2 | 2 | 0.00 | 0.000 | 460.00 | 0 |
oil2 | 3 | 0.00 | 0.000 | 460.00 | 0 |
oil2 | 4 | 0.00 | 230.000 | 230.00 | 1 |
oil2 | 5 | 0.00 | 230.000 | 0.00 | 1 |
oil2 | 6 | 730.00 | 230.000 | 500.00 | 1 |
oil3 | 0 | 500.00 | |||
oil3 | 1 | 0.00 | 210.000 | 290.00 | 1 |
oil3 | 2 | 0.00 | 250.000 | 40.00 | 1 |
oil3 | 3 | 770.00 | 250.000 | 560.00 | 1 |
oil3 | 4 | 0.00 | 20.000 | 540.00 | 1 |
oil3 | 5 | 0.00 | 20.000 | 520.00 | 1 |
oil3 | 6 | 0.00 | 20.000 | 500.00 | 1 |
veg1 | 0 | 500.00 | |||
veg1 | 1 | 0.00 | 0.000 | 500.00 | 0 |
veg1 | 2 | 0.00 | 85.185 | 414.81 | 1 |
veg1 | 3 | 0.00 | 85.185 | 329.63 | 1 |
veg1 | 4 | 0.00 | 155.000 | 174.63 | 1 |
veg1 | 5 | 0.00 | 155.000 | 19.63 | 1 |
veg1 | 6 | 480.37 | -0.000 | 500.00 | 0 |
veg2 | 0 | 500.00 | |||
veg2 | 1 | 0.00 | 200.000 | 300.00 | 1 |
veg2 | 2 | 0.00 | 114.815 | 185.19 | 1 |
veg2 | 3 | 0.00 | 114.815 | 70.37 | 1 |
veg2 | 4 | 0.00 | 0.000 | 70.37 | 0 |
veg2 | 5 | 0.00 | 0.000 | 70.37 | 0 |
veg2 | 6 | 629.63 | 200.000 | 500.00 | 1 |
[1] | Manufacture | hardness_sol | Logical1.BODY |
---|---|---|---|
1 | 450 | 5.4178 | 3 |
2 | 450 | 6.0000 | 3 |
3 | 450 | 6.0000 | 3 |
4 | 405 | 6.0000 | 3 |
5 | 405 | 6.0000 | 3 |
6 | 450 | 5.0800 | 3 |
Note that the maximum profit of £100,279 is smaller than in Chapter 1. This result is expected because this model contains additional constraints.