PROC OPTMODEL Statements and Output

The first several PROC OPTMODEL statements declare index sets and parameters and then read the input data:

proc optmodel;
   set <str> INPUTS;
   read data inputs into INPUTS=[input];

   set <str> OUTPUTS;
   read data outputs into OUTPUTS=[output];

   set <num> GARAGES;
   str garage_name {GARAGES};
   num input  {INPUTS, GARAGES};
   num output {OUTPUTS, GARAGES};
   read data garage_data into GARAGES=[_N_] garage_name
      {i in INPUTS}  <input[i,_N_]=col(i)>
      {i in OUTPUTS} <output[i,_N_]=col(i)>;

   num k;
   num efficiency_number {GARAGES};
   num weight_sol {GARAGES, GARAGES};

The following statements correspond directly to the mathematical programming formulation described earlier:

   var Weight {GARAGES} >= 0;
   var Inefficiency >= 0;

   max Objective = Inefficiency;

   con Input_con {i in INPUTS}:
      sum {j in GARAGES} input[i,j] * Weight[j] <= input[i,k];

   con Output_con {i in OUTPUTS}:
      sum {j in GARAGES} output[i,j] * Weight[j] >= output[i,k] * Inefficiency;

The following statements loop over all garages, call the linear programming solver once per garage, and store the results in the parameters efficiency_number and weight_sol:

   do k = GARAGES;
      solve;
      efficiency_number[k] = 1 / Inefficiency.sol;
      for {j in GARAGES}
         weight_sol[k,j] = (if Weight[j].sol > 1e-6 then Weight[j].sol else .);
   end;

Note that the DO loop contains no declaration statements. As the value of k changes, the SOLVE statement automatically updates the constraints to use the values of input[i,k] and output[i,k]. The approach shown here is much more efficient than the following alternatives:

  • Calling PROC OPTMODEL once per garage

  • Enlarging the set of decision variables by including an additional index, resulting in variables Weight[k,j] and Inefficiency[k]. Within the DO loop, you must then fix most of the variables to 0 and rely on the presolver to remove them, but that approach uses much more memory and computational time.

After the DO loop terminates, the following statements partition the garages into two sets by using a threshold on the resulting efficiency numbers:

   set EFFICIENT_GARAGES = {j in GARAGES: efficiency_number[j] >= 1};
   set INEFFICIENT_GARAGES = GARAGES diff EFFICIENT_GARAGES;

The following statements print the efficiency numbers, as shown in Figure 23.1, and write them to the efficiency_data data set:

   print garage_name efficiency_number;
   create data efficiency_data from [garage] garage_name efficiency_number;

Figure 23.1: efficiency_number Parameter

The OPTMODEL Procedure

[1] garage_name efficiency_number
1 Winchester 0.84017
2 Andover 0.91738
3 Basingstoke 1.00000
4 Poole 0.86189
5 Woking 0.86732
6 Newbury 1.00000
7 Portsmouth 1.00000
8 Alresford 1.00000
9 Salisbury 1.00000
10 Guildford 0.81417
11 Alton 1.00000
12 Weybridge 0.85435
13 Dorchester 0.83920
14 Bridport 0.97101
15 Weymouth 1.00000
16 Portland 1.00000
17 Chichester 0.82434
18 Petersfield 1.00000
19 Petworth 0.98824
20 Midhurst 0.82928
21 Reading 0.98205
22 Southampton 1.00000
23 Bournemouth 1.00000
24 Henley 1.00000
25 Maidenhead 1.00000
26 Fareham 1.00000
27 Romsey 1.00000
28 Ringwood 0.87587


The following CREATE DATA statements write the inefficient garages and the corresponding multiples of efficient garages to SAS data sets (in both dense and sparse form), as in Table 14.8 in Williams:

   create data weight_data_dense from [inefficient_garage]=INEFFICIENT_GARAGES
      garage_name
      efficiency_number
      {efficient_garage in EFFICIENT_GARAGES} <col('w'||efficient_garage)
         =weight_sol[inefficient_garage,efficient_garage]>;
   create data weight_data_sparse from
      [inefficient_garage efficient_garage]=
   {g1 in INEFFICIENT_GARAGES, g2 in EFFICIENT_GARAGES: weight_sol[g1,g2] ne .}
      weight_sol;
quit;

The following statements sort the efficiency_data data set by efficiency and print the results, shown in Figure 23.2:

proc sort data=efficiency_data;
   by descending efficiency_number;
run;

proc print; 
run;

Figure 23.2: efficiency_data Data Set

Obs garage garage_name efficiency_number
1 3 Basingstoke 1.00000
2 6 Newbury 1.00000
3 8 Alresford 1.00000
4 9 Salisbury 1.00000
5 11 Alton 1.00000
6 15 Weymouth 1.00000
7 16 Portland 1.00000
8 18 Petersfield 1.00000
9 22 Southampton 1.00000
10 23 Bournemouth 1.00000
11 24 Henley 1.00000
12 25 Maidenhead 1.00000
13 26 Fareham 1.00000
14 27 Romsey 1.00000
15 7 Portsmouth 1.00000
16 19 Petworth 0.98824
17 21 Reading 0.98205
18 14 Bridport 0.97101
19 2 Andover 0.91738
20 28 Ringwood 0.87587
21 5 Woking 0.86732
22 4 Poole 0.86189
23 12 Weybridge 0.85435
24 1 Winchester 0.84017
25 13 Dorchester 0.83920
26 20 Midhurst 0.82928
27 17 Chichester 0.82434
28 10 Guildford 0.81417


The following statements sort the weight_data_dense data set by efficiency and print the results, shown in Figure 23.3:

proc sort data=weight_data_dense;
   by descending efficiency_number;
run;

proc print; 
run;

Figure 23.3: weight_data_dense Data Set

Obs inefficient_garage garage_name efficiency_number w3 w6 w8 w9 w11 w15 w16 w18 w22 w23 w24 w25 w26 w27
1 7 Portsmouth 1.00000 . . . . . . . . . . . . . .
2 19 Petworth 0.98824 . 0.066345 . . . . . 0.015212 . . . 0.03409 0.67493 .
3 21 Reading 0.98205 1.26862 . . . . 0.54441 1.19914 . . . 2.86247 0.13753 . .
4 14 Bridport 0.97101 0.03278 . . . . . 0.46969 . . . 0.78310 0.19489 . .
5 2 Andover 0.91738 . . . . . 0.85714 . . . . . 0.21429 . .
6 28 Ringwood 0.87587 0.00771 . . . . . 0.31973 . . . 0.14649 . . .
7 5 Woking 0.86732 . . 0.95253 . 0.021078 . . . .009092662 . . 0.14838 . .
8 4 Poole 0.86189 0.32859 . . . . . 0.75733 . . . 0.43442 0.34463 . .
9 12 Weybridge 0.85435 . . . . . 0.79656 . . . . . 0.14524 0.01773 .
10 1 Winchester 0.84017 . . 0.41627 0.40328 . 0.33333 0.09614 . . . . . . .
11 13 Dorchester 0.83920 0.13436 . 0.10448 . . 0.11929 0.75163 . . . 0.03532 . 0.47905 .
12 20 Midhurst 0.82928 . . . 0.05957 . 0.06651 0.47189 0.043482 . . . 0.00894 . .
13 17 Chichester 0.82434 0.05825 . 0.09682 . . 0.33543 0.16523 . . . 0.23637 . 0.15424 .
14 10 Guildford 0.81417 0.42459 . 0.62272 . . 0.19180 0.16807 . . . . . . .


The weight_data_sparse data set contains the same information in sparse format, as shown in Figure 23.4:

proc print data=weight_data_sparse;
run;

Figure 23.4: weight_data_sparse Data Set

Obs inefficient_garage efficient_garage weight_sol
1 1 8 0.41627
2 1 9 0.40328
3 1 15 0.33333
4 1 16 0.09614
5 2 15 0.85714
6 2 25 0.21429
7 4 3 0.32859
8 4 16 0.75733
9 4 24 0.43442
10 4 25 0.34463
11 5 8 0.95253
12 5 11 0.02108
13 5 22 0.00909
14 5 25 0.14838
15 10 3 0.42459
16 10 8 0.62272
17 10 15 0.19180
18 10 16 0.16807
19 12 15 0.79656
20 12 25 0.14524
21 12 26 0.01773
22 13 3 0.13436
23 13 8 0.10448
24 13 15 0.11929
25 13 16 0.75163
26 13 24 0.03532
27 13 26 0.47905
28 14 3 0.03278
29 14 16 0.46969
30 14 24 0.78310
31 14 25 0.19489
32 17 3 0.05825
33 17 8 0.09682
34 17 15 0.33543
35 17 16 0.16523
36 17 24 0.23637
37 17 26 0.15424
38 19 6 0.06635
39 19 18 0.01521
40 19 25 0.03409
41 19 26 0.67493
42 20 9 0.05957
43 20 15 0.06651
44 20 16 0.47189
45 20 18 0.04348
46 20 25 0.00894
47 21 3 1.26862
48 21 15 0.54441
49 21 16 1.19914
50 21 24 2.86247
51 21 25 0.13753
52 28 3 0.00771
53 28 16 0.31973
54 28 24 0.14649