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 within Relative Gap |
Objective Value | 108855 |
Relative Gap | 1.1326824E-6 |
Absolute Gap | 0.123298281 |
Primal Infeasibility | 2.850885E-13 |
Bound Infeasibility | 3.410605E-13 |
Integer Infeasibility | 1.054292E-15 |
Best Bound | 108855.1233 |
Nodes | 7 |
Iterations | 1330 |
Presolve Time | 0.01 |
Solution Time | 0.10 |
[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.