Factory Planning 1: What to Make, On What Machines, and When


PROC OPTMODEL Statements and Output

This example uses both one-dimensional and dense two-dimensional data, as in Chapter 1 and Chapter 2:

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};
   read data machine_type_data into MACHINE_TYPES=[machine_type] num_machines;

But this problem also has sparse two-dimensional data: for most $(\text {machine}\mr{\_ }\text {type},\text {period})$ pairs, the number of machines down is 0. In the following statements, the INIT option in the second NUM statement initializes num_machines_down_per_period to 0. The read of the sparse data set machine_type_period_data populates only the nonzero values. The subsequent computation of num_machines_per_period[machine_type,period] then uses the initial value of num_machines_down_per_period[machine_type,period] when no other value has been supplied:

   num num_machines_per_period {machine_type in MACHINE_TYPES, PERIODS}
      init num_machines[machine_type];
   num num_machines_down_per_period {MACHINE_TYPES, PERIODS} init 0;
   read data machine_type_period_data into [machine_type period]
      num_machines_down_per_period=num_down;
   for {machine_type in MACHINE_TYPES, period in PERIODS}
      num_machines_per_period[machine_type,period] =
      num_machines_per_period[machine_type,period]
    - num_machines_down_per_period[machine_type,period];
   print num_machines_per_period;

Figure 3.1 shows the resulting values of num_machines_per_period.

Figure 3.1: num_machines_per_period Parameter

The OPTMODEL Procedure

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



The following statements declare and read dense two-dimensional data:

   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)>;

The following statements are straightforward:

   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;

   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_per_period[machine_type,period];

The following Flow_balance_con constraint uses an IF-THEN/ELSE expression to handle the boundary conditions: if a previous period exists, the units in storage at the end of the previous period are available to be sold in the current period. (Because ELSE 0 is the default, you could use just an IF-THEN expression instead.)

   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];

   solve;
   print Make Sell Store;

The maximum total profit is £93,715, as shown in Figure 3.2.

Figure 3.2: Output from Linear Programming Solver

Problem Summary
Objective Sense Maximization
Objective Function TotalProfit
Objective Type Linear
   
Number of Variables 126
Bounded Above 0
Bounded Below 42
Bounded Below and Above 71
Free 0
Fixed 13
   
Number of Constraints 72
Linear LE (<=) 30
Linear EQ (=) 42
Linear GE (>=) 0
Linear Range 0
   
Constraint Coefficients 281

Performance Information
Execution Mode Single-Machine
Number of Threads 1

Solution Summary
Solver LP
Algorithm Dual Simplex
Objective Function TotalProfit
Solution Status Optimal
Objective Value 93715.178571
   
Primal Infeasibility 1.421085E-14
Dual Infeasibility 2.220446E-16
Bound Infeasibility 0
   
Iterations 32
Presolve Time 0.00
Solution Time 0.00

[1] [2] Make Sell Store
prod1 1 500.00 500.00 0.0
prod1 2 700.00 600.00 100.0
prod1 3 0.00 100.00 0.0
prod1 4 200.00 200.00 0.0
prod1 5 0.00 0.00 0.0
prod1 6 550.00 500.00 50.0
prod2 1 888.57 888.57 0.0
prod2 2 600.00 500.00 100.0
prod2 3 0.00 100.00 0.0
prod2 4 300.00 300.00 0.0
prod2 5 100.00 100.00 0.0
prod2 6 550.00 500.00 50.0
prod3 1 382.50 300.00 82.5
prod3 2 117.50 200.00 0.0
prod3 3 0.00 0.00 0.0
prod3 4 400.00 400.00 0.0
prod3 5 600.00 500.00 100.0
prod3 6 0.00 50.00 50.0
prod4 1 300.00 300.00 0.0
prod4 2 0.00 0.00 0.0
prod4 3 0.00 0.00 0.0
prod4 4 500.00 500.00 0.0
prod4 5 100.00 100.00 0.0
prod4 6 350.00 300.00 50.0
prod5 1 800.00 800.00 0.0
prod5 2 500.00 400.00 100.0
prod5 3 0.00 100.00 0.0
prod5 4 200.00 200.00 0.0
prod5 5 1100.00 1000.00 100.0
prod5 6 0.00 50.00 50.0
prod6 1 200.00 200.00 0.0
prod6 2 300.00 300.00 0.0
prod6 3 400.00 400.00 0.0
prod6 4 0.00 0.00 0.0
prod6 5 300.00 300.00 0.0
prod6 6 550.00 500.00 50.0
prod7 1 0.00 0.00 0.0
prod7 2 250.00 150.00 100.0
prod7 3 0.00 100.00 0.0
prod7 4 100.00 100.00 0.0
prod7 5 100.00 0.00 100.0
prod7 6 0.00 50.00 50.0



You can use the .dual constraint suffix to access the optimal dual variables returned by the solver:

   print Machine_hours_con.dual;
   create data sol_data1 from [product period] Make Sell Store;
quit;

These values, shown in Figure 3.3, suggest the change in optimal objective value if the factory acquires an additional machine in that period. For this test instance, it turns out that the positive dual variables all correspond to machines that are down.

Figure 3.3: Optimal Dual Variables

Machine_hours_con.DUAL
  1 2 3 4 5 6
borer 0.0000 0.0000 200.0000 0.0000 0.0000 0.0000
grinder 8.5714 0.0000 0.0000 0.0000 0.0000 0.0000
hdrill 0.0000 0.6250 0.0000 0.0000 0.0000 0.0000
planer 0.0000 0.0000 0.0000 0.0000 0.0000 800.0000
vdrill 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000