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 that is 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.
In SAS/OR 13.1, you can instead solve the same set of problems in parallel by replacing the DO loop with the following COFOR loop:
cofor {kk in GARAGES} do; k = kk; 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;
After the DO or COFOR 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 22.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 22.1: efficiency_number Parameter
[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 (1999):
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 22.2:
proc sort data=efficiency_data; by descending efficiency_number; run; proc print; run;
Figure 22.2: efficiency_data
Data Set
Obs | garage | garage_name | efficiency_number |
---|---|---|---|
1 | 25 | Maidenhead | 1.00000 |
2 | 24 | Henley | 1.00000 |
3 | 3 | Basingstoke | 1.00000 |
4 | 6 | Newbury | 1.00000 |
5 | 7 | Portsmouth | 1.00000 |
6 | 8 | Alresford | 1.00000 |
7 | 9 | Salisbury | 1.00000 |
8 | 11 | Alton | 1.00000 |
9 | 15 | Weymouth | 1.00000 |
10 | 16 | Portland | 1.00000 |
11 | 18 | Petersfield | 1.00000 |
12 | 22 | Southampton | 1.00000 |
13 | 23 | Bournemouth | 1.00000 |
14 | 26 | Fareham | 1.00000 |
15 | 27 | Romsey | 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 22.3:
proc sort data=weight_data_dense; by descending efficiency_number; run; proc print; run;
Figure 22.3: weight_data_dense
Data Set
Obs | inefficient_garage | garage_name | efficiency_number | w3 | w6 | w7 | w8 | w9 | w11 | w15 | w16 | w18 | w22 | w23 | w24 | w25 | w26 | w27 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 19 | Petworth | 0.98824 | . | 0.066345 | . | . | . | . | . | . | 0.015212 | . | . | . | 0.03409 | 0.67493 | . |
2 | 21 | Reading | 0.98205 | 1.26862 | . | . | . | . | . | 0.54441 | 1.19914 | . | . | . | 2.86247 | 0.13753 | . | . |
3 | 14 | Bridport | 0.97101 | 0.03278 | . | . | . | . | . | . | 0.46969 | . | . | . | 0.78310 | 0.19489 | . | . |
4 | 2 | Andover | 0.91738 | . | . | . | . | . | . | 0.85714 | . | . | . | . | . | 0.21429 | . | . |
5 | 28 | Ringwood | 0.87587 | 0.00771 | . | . | . | . | . | . | 0.31973 | . | . | . | 0.14649 | . | . | . |
6 | 5 | Woking | 0.86732 | . | . | . | 0.95253 | . | 0.021078 | . | . | . | .009092662 | . | . | 0.14838 | . | . |
7 | 4 | Poole | 0.86189 | 0.32859 | . | . | . | . | . | . | 0.75733 | . | . | . | 0.43442 | 0.34463 | . | . |
8 | 12 | Weybridge | 0.85435 | . | . | . | . | . | . | 0.79656 | . | . | . | . | . | 0.14524 | 0.01773 | . |
9 | 1 | Winchester | 0.84017 | . | . | 0.00528 | 0.41627 | 0.40328 | . | 0.33333 | 0.09614 | . | . | . | . | . | . | . |
10 | 13 | Dorchester | 0.83920 | 0.13436 | . | . | 0.10448 | . | . | 0.11929 | 0.75163 | . | . | . | 0.03532 | . | 0.47905 | . |
11 | 20 | Midhurst | 0.82928 | . | . | . | . | 0.05957 | . | 0.06651 | 0.47189 | 0.043482 | . | . | . | 0.00894 | . | . |
12 | 17 | Chichester | 0.82434 | 0.05825 | . | . | 0.09682 | . | . | 0.33543 | 0.16523 | . | . | . | 0.23637 | . | 0.15424 | . |
13 | 10 | Guildford | 0.81417 | 0.42459 | . | 0.14961 | 0.62272 | . | . | 0.19180 | 0.16807 | . | . | . | . | . | . | . |
The weight_data_sparse
data set contains the same information in sparse format, as shown in Figure 22.4:
proc print data=weight_data_sparse; run;
Figure 22.4: weight_data_sparse
Data Set
Obs | inefficient_garage | efficient_garage | weight_sol |
---|---|---|---|
1 | 1 | 7 | 0.00528 |
2 | 1 | 8 | 0.41627 |
3 | 1 | 9 | 0.40328 |
4 | 1 | 15 | 0.33333 |
5 | 1 | 16 | 0.09614 |
6 | 2 | 15 | 0.85714 |
7 | 2 | 25 | 0.21429 |
8 | 4 | 3 | 0.32859 |
9 | 4 | 16 | 0.75733 |
10 | 4 | 24 | 0.43442 |
11 | 4 | 25 | 0.34463 |
12 | 5 | 8 | 0.95253 |
13 | 5 | 11 | 0.02108 |
14 | 5 | 22 | 0.00909 |
15 | 5 | 25 | 0.14838 |
16 | 10 | 3 | 0.42459 |
17 | 10 | 7 | 0.14961 |
18 | 10 | 8 | 0.62272 |
19 | 10 | 15 | 0.19180 |
20 | 10 | 16 | 0.16807 |
21 | 12 | 15 | 0.79656 |
22 | 12 | 25 | 0.14524 |
23 | 12 | 26 | 0.01773 |
24 | 13 | 3 | 0.13436 |
25 | 13 | 8 | 0.10448 |
26 | 13 | 15 | 0.11929 |
27 | 13 | 16 | 0.75163 |
28 | 13 | 24 | 0.03532 |
29 | 13 | 26 | 0.47905 |
30 | 14 | 3 | 0.03278 |
31 | 14 | 16 | 0.46969 |
32 | 14 | 24 | 0.78310 |
33 | 14 | 25 | 0.19489 |
34 | 17 | 3 | 0.05825 |
35 | 17 | 8 | 0.09682 |
36 | 17 | 15 | 0.33543 |
37 | 17 | 16 | 0.16523 |
38 | 17 | 24 | 0.23637 |
39 | 17 | 26 | 0.15424 |
40 | 19 | 6 | 0.06635 |
41 | 19 | 18 | 0.01521 |
42 | 19 | 25 | 0.03409 |
43 | 19 | 26 | 0.67493 |
44 | 20 | 9 | 0.05957 |
45 | 20 | 15 | 0.06651 |
46 | 20 | 16 | 0.47189 |
47 | 20 | 18 | 0.04348 |
48 | 20 | 25 | 0.00894 |
49 | 21 | 3 | 1.26862 |
50 | 21 | 15 | 0.54441 |
51 | 21 | 16 | 1.19914 |
52 | 21 | 24 | 2.86247 |
53 | 21 | 25 | 0.13753 |
54 | 28 | 3 | 0.00771 |
55 | 28 | 16 | 0.31973 |
56 | 28 | 24 | 0.14649 |