Factory Planning 2: When Should Machines Be Down for Maintenance


PROC OPTMODEL Statements and Output

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

The OPTMODEL Procedure

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

Performance Information
Execution Mode Single-Machine
Number of Threads 4

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

NumMachinesDown
  1 2 3 4 5 6
borer 0 -0 0 1 0 0
grinder 0 0 0 0 2 0
hdrill 1 0 2 0 0 0
planer 0 0 -0 1 0 0
vdrill 0 1 0 0 1 0



As expected, the optimal numbers of machines down differ from the num_machines_down_per_period parameter values in Chapter 3.