For completeness, all statements are shown. Statements that are new or changed from Chapter 3 are indicated.
proc optmodel;
set <str> PRODUCTS;
num profit {PRODUCTS};
read data product_data into PRODUCTS=[product] profit;
set PERIODS;
num demand {PRODUCTS, PERIODS};
read data demand_data into PERIODS=[_N_]
{product in PRODUCTS} <demand[product,_N_]=col(product)>;
set <str> MACHINE_TYPES;
num num_machines {MACHINE_TYPES};
The following statements declare and populate the num_machines_needing_maintenance parameter:
num num_machines_needing_maintenance {MACHINE_TYPES};
read data machine_type_data into MACHINE_TYPES=[machine_type]
num_machines num_machines_needing_maintenance;
The following statements are the same as in Chapter 3:
num production_time {PRODUCTS, MACHINE_TYPES};
read data machine_type_product_data into [machine_type]
{product in PRODUCTS}
<production_time[product,machine_type]=col(product)>;
var Make {PRODUCTS, PERIODS} >= 0;
var Sell {product in PRODUCTS, period in PERIODS} >= 0
<= demand[product,period];
num last_period = max {period in PERIODS} period;
var Store {PRODUCTS, PERIODS} >= 0 <= &store_ub;
for {product in PRODUCTS}
fix Store[product,last_period] = &final_storage;
impvar StorageCost =
sum {product in PRODUCTS, period in PERIODS}
&storage_cost_per_unit * Store[product,period];
max TotalProfit =
sum {product in PRODUCTS, period in PERIODS}
profit[product] * Sell[product,period]
- StorageCost;
Most of the remaining statements are new or modified from Chapter 3. The INTEGER option in the following VAR statement declares NumMachinesDown to be an integer variable:
var NumMachinesDown {MACHINE_TYPES, PERIODS} >= 0 integer;
con Machine_hours_con {machine_type in MACHINE_TYPES, period in PERIODS}:
sum {product in PRODUCTS}
production_time[product,machine_type] * Make[product,period]
<= &num_hours_per_period *
(num_machines[machine_type] - NumMachinesDown[machine_type,period]);
con Maintenance_con {machine_type in MACHINE_TYPES}:
sum {period in PERIODS} NumMachinesDown[machine_type,period]
= num_machines_needing_maintenance[machine_type];
con Flow_balance_con {product in PRODUCTS, period in PERIODS}:
(if period - 1 in PERIODS then Store[product,period-1] else 0)
+ Make[product,period]
= Sell[product,period] + Store[product,period];
Because the problem contains integer variables, the SOLVE statement automatically invokes the MILP solver:
solve; print Make Sell Store; print NumMachinesDown; create data sol_data1 from [product period] Make Sell Store; create data sol_data2 from [machine_type period] NumMachinesDown; quit;
The solver determines when machines should be down and obtains a total profit of £108,855, as shown in Figure 4.1. This objective value represents an increase of £15,140 from the optimal objective in Chapter 3.
Figure 4.1: Output from Mixed Integer Linear Programming Solver
| Problem Summary | |
|---|---|
| Objective Sense | Maximization |
| Objective Function | TotalProfit |
| Objective Type | Linear |
| Number of Variables | 156 |
| Bounded Above | 0 |
| Bounded Below | 72 |
| Bounded Below and Above | 71 |
| Free | 0 |
| Fixed | 13 |
| Binary | 0 |
| Integer | 30 |
| Number of Constraints | 77 |
| Linear LE (<=) | 30 |
| Linear EQ (=) | 47 |
| Linear GE (>=) | 0 |
| Linear Range | 0 |
| Constraint Coefficients | 341 |
| Solution Summary | |
|---|---|
| Solver | MILP |
| Algorithm | Branch and Cut |
| Objective Function | TotalProfit |
| Solution Status | Optimal |
| Objective Value | 108855 |
| Relative Gap | 0 |
| Absolute Gap | 0 |
| Primal Infeasibility | 1.136868E-13 |
| Bound Infeasibility | 1.69778E-16 |
| Integer Infeasibility | 2.671474E-16 |
| Best Bound | 108855 |
| Nodes | 1 |
| Iterations | 122 |
| Presolve Time | 0.02 |
| Solution Time | 0.03 |
| [1] | [2] | Make | Sell | Store |
|---|---|---|---|---|
| prod1 | 1 | 500 | 500 | 0 |
| prod1 | 2 | 600 | 600 | 0 |
| prod1 | 3 | 400 | 300 | 100 |
| prod1 | 4 | 0 | 100 | 0 |
| prod1 | 5 | 0 | 0 | 0 |
| prod1 | 6 | 550 | 500 | 50 |
| prod2 | 1 | 1000 | 1000 | 0 |
| prod2 | 2 | 500 | 500 | 0 |
| prod2 | 3 | 700 | 600 | 100 |
| prod2 | 4 | 0 | 100 | 0 |
| prod2 | 5 | 100 | 100 | 0 |
| prod2 | 6 | 550 | 500 | 50 |
| prod3 | 1 | 300 | 300 | 0 |
| prod3 | 2 | 200 | 200 | 0 |
| prod3 | 3 | 100 | 0 | 100 |
| prod3 | 4 | 0 | 100 | 0 |
| prod3 | 5 | 500 | 500 | 0 |
| prod3 | 6 | 150 | 100 | 50 |
| prod4 | 1 | 300 | 300 | 0 |
| prod4 | 2 | 0 | 0 | 0 |
| prod4 | 3 | 100 | 0 | 100 |
| prod4 | 4 | 0 | 100 | 0 |
| prod4 | 5 | 100 | 100 | 0 |
| prod4 | 6 | 350 | 300 | 50 |
| prod5 | 1 | 800 | 800 | 0 |
| prod5 | 2 | 400 | 400 | 0 |
| prod5 | 3 | 600 | 500 | 100 |
| prod5 | 4 | 0 | 100 | 0 |
| prod5 | 5 | 1000 | 1000 | 0 |
| prod5 | 6 | 1150 | 1100 | 50 |
| prod6 | 1 | 200 | 200 | 0 |
| prod6 | 2 | 300 | 300 | 0 |
| prod6 | 3 | 400 | 400 | 0 |
| prod6 | 4 | 0 | 0 | 0 |
| prod6 | 5 | 300 | 300 | 0 |
| prod6 | 6 | 550 | 500 | 50 |
| prod7 | 1 | 100 | 100 | 0 |
| prod7 | 2 | 150 | 150 | 0 |
| prod7 | 3 | 200 | 100 | 100 |
| prod7 | 4 | 0 | 100 | 0 |
| prod7 | 5 | 0 | 0 | 0 |
| prod7 | 6 | 110 | 60 | 50 |
As expected, the optimal numbers of machines down differ from the num_machines_down_per_period parameter values in Chapter 3.