Example 6.8 Migration to OPTMODEL: Production, Inventory, Distribution

The following example shows how to use PROC OPTMODEL to solve the example Production, Inventory, Distribution Problem in Chapter 6: The NETFLOW Procedure in SAS/OR 12.3 User's Guide: Mathematical Programming Legacy Procedures. The input data sets are the same as in that example.

```title 'Minimum Cost Flow Problem';
title2 'Production Planning/Inventory/Distribution';

data node0;
input _node_ \$ _supdem_ ;
datalines;
fact1_1 1000
fact2_1 850
fact1_2 1000
fact2_2 1500
shop1_1 -900
shop2_1 -900
shop1_2 -900
shop2_2 -1450
;
```
```data arc0;
input _tail_ \$ _head_ \$ _cost_ _capac_ _lo_
diagonal factory key_id \$10. mth_made \$ _name_&\$17.;
datalines;
fact1_1  f1_mar_1 127.9   500 50 19 1 production March prod f1 19 mar
fact1_1  f1_apr_1 78.6    600 50 19 1 production April prod f1 19 apl
fact1_1  f1_may_1 95.1    400 50 19 1 production May   .
f1_mar_1 f1_apr_1 15      50  .  19 1 storage    March .
f1_apr_1 f1_may_1 12      50  .  19 1 storage    April .
f1_apr_1 f1_mar_1 28      20  .  19 1 backorder  April back f1 19 apl
f1_may_1 f1_apr_1 28      20  .  19 1 backorder  May   back f1 19 may
f1_mar_1 f2_mar_1 11      .   .  19 . f1_to_2    March .
f1_apr_1 f2_apr_1 11      .   .  19 . f1_to_2    April .
f1_may_1 f2_may_1 16      .   .  19 . f1_to_2    May   .
f1_mar_1 shop1_1  -327.65 250 .  19 1 sales      March .
f1_apr_1 shop1_1  -300    250 .  19 1 sales      April .
f1_may_1 shop1_1  -285    250 .  19 1 sales      May   .
f1_mar_1 shop2_1  -362.74 250 .  19 1 sales      March .
f1_apr_1 shop2_1  -300    250 .  19 1 sales      April .
f1_may_1 shop2_1  -245    250 .  19 1 sales      May   .
fact2_1  f2_mar_1 88.0    450 35 19 2 production March prod f2 19 mar
fact2_1  f2_apr_1 62.4    480 35 19 2 production April prod f2 19 apl
fact2_1  f2_may_1 133.8   250 35 19 2 production May   .
f2_mar_1 f2_apr_1 18      30  .  19 2 storage    March .
f2_apr_1 f2_may_1 20      30  .  19 2 storage    April .
f2_apr_1 f2_mar_1 17      15  .  19 2 backorder  April back f2 19 apl
f2_may_1 f2_apr_1 25      15  .  19 2 backorder  May   back f2 19 may
f2_mar_1 f1_mar_1 10      40  .  19 . f2_to_1    March .
f2_apr_1 f1_apr_1 11      40  .  19 . f2_to_1    April .
f2_may_1 f1_may_1 13      40  .  19 . f2_to_1    May   .
f2_mar_1 shop1_1  -297.4  250 .  19 2 sales      March .
f2_apr_1 shop1_1  -290    250 .  19 2 sales      April .
f2_may_1 shop1_1  -292    250 .  19 2 sales      May   .
f2_mar_1 shop2_1  -272.7  250 .  19 2 sales      March .
f2_apr_1 shop2_1  -312    250 .  19 2 sales      April .
f2_may_1 shop2_1  -299    250 .  19 2 sales      May   .
fact1_2  f1_mar_2 217.9   400 40 25 1 production March prod f1 25 mar
fact1_2  f1_apr_2 174.5   550 50 25 1 production April prod f1 25 apl
fact1_2  f1_may_2 133.3   350 40 25 1 production May   .
f1_mar_2 f1_apr_2 20      40  .  25 1 storage    March .
f1_apr_2 f1_may_2 18      40  .  25 1 storage    April .
f1_apr_2 f1_mar_2 32      30  .  25 1 backorder  April back f1 25 apl
f1_may_2 f1_apr_2 41      15  .  25 1 backorder  May   back f1 25 may
f1_mar_2 f2_mar_2 23      .   .  25 . f1_to_2    March .
f1_apr_2 f2_apr_2 23      .   .  25 . f1_to_2    April .
f1_may_2 f2_may_2 26      .   .  25 . f1_to_2    May   .
f1_mar_2 shop1_2  -559.76 .   .  25 1 sales      March .
f1_apr_2 shop1_2  -524.28 .   .  25 1 sales      April .
f1_may_2 shop1_2  -475.02 .   .  25 1 sales      May   .
f1_mar_2 shop2_2  -623.89 .   .  25 1 sales      March .
f1_apr_2 shop2_2  -549.68 .   .  25 1 sales      April .
f1_may_2 shop2_2  -460.00 .   .  25 1 sales      May   .
fact2_2  f2_mar_2 182.0   650 35 25 2 production March prod f2 25 mar
fact2_2  f2_apr_2 196.7   680 35 25 2 production April prod f2 25 apl
fact2_2  f2_may_2 201.4   550 35 25 2 production May   .
f2_mar_2 f2_apr_2 28      50  .  25 2 storage    March .
f2_apr_2 f2_may_2 38      50  .  25 2 storage    April .
f2_apr_2 f2_mar_2 31      15  .  25 2 backorder  April back f2 25 apl
f2_may_2 f2_apr_2 54      15  .  25 2 backorder  May   back f2 25 may
f2_mar_2 f1_mar_2 20      25  .  25 . f2_to_1    March .
f2_apr_2 f1_apr_2 21      25  .  25 . f2_to_1    April .
f2_may_2 f1_may_2 43      25  .  25 . f2_to_1    May   .
f2_mar_2 shop1_2  -567.83 500 .  25 2 sales      March .
f2_apr_2 shop1_2  -542.19 500 .  25 2 sales      April .
f2_may_2 shop1_2  -461.56 500 .  25 2 sales      May   .
f2_mar_2 shop2_2  -542.83 500 .  25 2 sales      March .
f2_apr_2 shop2_2  -559.19 500 .  25 2 sales      April .
f2_may_2 shop2_2  -489.06 500 .  25 2 sales      May   .
;
```

The following PROC OPTMODEL statements read the data sets, build the linear programming model, solve the model, and output the optimal solution to SAS data sets called `ARC1` and `NODE2`:

```proc optmodel;
set <str> NODES;
num _supdem_ {NODES} init 0;
read data node0 into NODES=[_node_] _supdem_;

set <str,str> ARCS;
num _lo_ {ARCS} init 0;
num _capac_ {ARCS} init .;
num _cost_ {ARCS};
num diagonal {ARCS};
num factory {ARCS};
str key_id {ARCS};
str _name_ {ARCS};
NODES = NODES union (union {<i,j> in ARCS} {i,j});

var Flow {<i,j> in ARCS} >= _lo_[i,j];
for {<i,j> in ARCS: _capac_[i,j] ne .} Flow[i,j].ub = _capac_[i,j];
min obj = sum {<i,j> in ARCS} _cost_[i,j] * Flow[i,j];
con balance {i in NODES}: sum {<(i),j> in ARCS} Flow[i,j]
- sum {<j,(i)> in ARCS} Flow[j,i] = _supdem_[i];

num infinity = min {r in {}} r;
num excess = sum {i in NODES} _supdem_[i];
if (excess > 0) then do;
/* change equality constraint to le constraint for supply nodes */
for {i in NODES: _supdem_[i] > 0} balance[i].lb = -infinity;
end;
else if (excess < 0) then do;
/* change equality constraint to ge constraint for demand nodes */
for {i in NODES: _supdem_[i] < 0} balance[i].ub = infinity;
end;

solve;

num _supply_ {<i,j> in ARCS} =
(if _supdem_[i] ne 0 then _supdem_[i] else .);
num _demand_ {<i,j> in ARCS} =
(if _supdem_[j] ne 0 then -_supdem_[j] else .);
num _fcost_ {<i,j> in ARCS} = _cost_[i,j] * Flow[i,j].sol;

create data arc1 from [_tail_ _head_]
_cost_ _capac_ _lo_ _name_ _supply_ _demand_ _flow_=Flow _fcost_
_rcost_ =
_status_ = Flow.status diagonal factory key_id mth_made;
create data node2 from [_node_]
_supdem_ = (if _supdem_[_node_] ne 0 then _supdem_[_node_] else .)
_dual_ = balance.dual;
quit;
```

The PROC OPTMODEL statements use both single-dimensional (NODES) and multiple-dimensional (ARCS) index sets, which are populated from the corresponding data set variables in the READ DATA statements. The _SUPDEM_, _LO_, and _CAPAC_ parameters are given initial values, and the NOMISS option in the READ DATA statement tells PROC OPTMODEL to read only the nonmissing values from the input data set. The balance constraint is initially declared as an equality, but depending on the total supply or demand, the sense of this constraint is changed to or by relaxing the constraint’s lower or upper bound, respectively. The `ARC1` output data set contains most of the same information as in the NETFLOW example, including reduced cost, basis status, and dual values. The _ANUMB_ and _TNUMB_ values do not apply here.

The PROC PRINT statements are similar to the PROC NETFLOW example:

```options ls=80 ps=54;
var _tail_ _head_ _cost_ _capac_ _lo_ _name_
_supply_ _demand_ _flow_ _fcost_;
sum _fcost_;
run;
var _rcost_ _status_ diagonal factory key_id mth_made;
run;
proc print data=node2;
run;
```

The output data sets are displayed in Output 6.8.1.

Output 6.8.1: Output Data Sets

Obs _tail_ _head_ _cost_ _capac_ _lo_ _name_ _supply_ _demand_ _flow_ _fcost_
1 fact1_1 f1_mar_1 127.90 500 50 prod f1 19 mar 1000 . 345 44125.50
2 fact1_1 f1_apr_1 78.60 600 50 prod f1 19 apl 1000 . 600 47160.00
3 fact1_1 f1_may_1 95.10 400 50   1000 . 50 4755.00
4 f1_mar_1 f1_apr_1 15.00 50 0   . . 0 0.00
5 f1_apr_1 f1_may_1 12.00 50 0   . . 50 600.00
6 f1_apr_1 f1_mar_1 28.00 20 0 back f1 19 apl . . 20 560.00
7 f1_may_1 f1_apr_1 28.00 20 0 back f1 19 may . . 0 0.00
8 f1_mar_1 f2_mar_1 11.00 . 0   . . 0 0.00
9 f1_apr_1 f2_apr_1 11.00 . 0   . . 30 330.00
10 f1_may_1 f2_may_1 16.00 . 0   . . 100 1600.00
11 f1_mar_1 shop1_1 -327.65 250 0   . 900 155 -50785.75
12 f1_apr_1 shop1_1 -300.00 250 0   . 900 250 -75000.00
13 f1_may_1 shop1_1 -285.00 250 0   . 900 0 0.00
14 f1_mar_1 shop2_1 -362.74 250 0   . 900 250 -90685.00
15 f1_apr_1 shop2_1 -300.00 250 0   . 900 250 -75000.00
16 f1_may_1 shop2_1 -245.00 250 0   . 900 0 0.00
17 fact2_1 f2_mar_1 88.00 450 35 prod f2 19 mar 850 . 290 25520.00
18 fact2_1 f2_apr_1 62.40 480 35 prod f2 19 apl 850 . 480 29952.00
19 fact2_1 f2_may_1 133.80 250 35   850 . 35 4683.00
20 f2_mar_1 f2_apr_1 18.00 30 0   . . 0 0.00
21 f2_apr_1 f2_may_1 20.00 30 0   . . 15 300.00
22 f2_apr_1 f2_mar_1 17.00 15 0 back f2 19 apl . . 0 0.00
23 f2_may_1 f2_apr_1 25.00 15 0 back f2 19 may . . 0 0.00
24 f2_mar_1 f1_mar_1 10.00 40 0   . . 40 400.00
25 f2_apr_1 f1_apr_1 11.00 40 0   . . 0 0.00
26 f2_may_1 f1_may_1 13.00 40 0   . . 0 0.00
27 f2_mar_1 shop1_1 -297.40 250 0   . 900 250 -74350.00
28 f2_apr_1 shop1_1 -290.00 250 0   . 900 245 -71050.00
29 f2_may_1 shop1_1 -292.00 250 0   . 900 0 0.00
30 f2_mar_1 shop2_1 -272.70 250 0   . 900 0 0.00
31 f2_apr_1 shop2_1 -312.00 250 0   . 900 250 -78000.00
32 f2_may_1 shop2_1 -299.00 250 0   . 900 150 -44850.00
33 fact1_2 f1_mar_2 217.90 400 40 prod f1 25 mar 1000 . 400 87160.00
34 fact1_2 f1_apr_2 174.50 550 50 prod f1 25 apl 1000 . 550 95975.00
35 fact1_2 f1_may_2 133.30 350 40   1000 . 40 5332.00
36 f1_mar_2 f1_apr_2 20.00 40 0   . . 0 0.00
37 f1_apr_2 f1_may_2 18.00 40 0   . . 0 0.00
38 f1_apr_2 f1_mar_2 32.00 30 0 back f1 25 apl . . 30 960.00
39 f1_may_2 f1_apr_2 41.00 15 0 back f1 25 may . . 15 615.00
40 f1_mar_2 f2_mar_2 23.00 . 0   . . 0 0.00
41 f1_apr_2 f2_apr_2 23.00 . 0   . . 0 0.00
42 f1_may_2 f2_may_2 26.00 . 0   . . 0 0.00
43 f1_mar_2 shop1_2 -559.76 . 0   . 900 0 0.00
44 f1_apr_2 shop1_2 -524.28 . 0   . 900 0 0.00
45 f1_may_2 shop1_2 -475.02 . 0   . 900 25 -11875.50
46 f1_mar_2 shop2_2 -623.89 . 0   . 1450 455 -283869.95
47 f1_apr_2 shop2_2 -549.68 . 0   . 1450 535 -294078.80
48 f1_may_2 shop2_2 -460.00 . 0   . 1450 0 0.00
49 fact2_2 f2_mar_2 182.00 650 35 prod f2 25 mar 1500 . 645 117390.00
50 fact2_2 f2_apr_2 196.70 680 35 prod f2 25 apl 1500 . 680 133756.00
51 fact2_2 f2_may_2 201.40 550 35   1500 . 35 7049.00
52 f2_mar_2 f2_apr_2 28.00 50 0   . . 0 0.00
53 f2_apr_2 f2_may_2 38.00 50 0   . . 0 0.00
54 f2_apr_2 f2_mar_2 31.00 15 0 back f2 25 apl . . 0 0.00
55 f2_may_2 f2_apr_2 54.00 15 0 back f2 25 may . . 15 810.00
56 f2_mar_2 f1_mar_2 20.00 25 0   . . 25 500.00
57 f2_apr_2 f1_apr_2 21.00 25 0   . . 0 0.00
58 f2_may_2 f1_may_2 43.00 25 0   . . 0 0.00
59 f2_mar_2 shop1_2 -567.83 500 0   . 900 500 -283915.00
60 f2_apr_2 shop1_2 -542.19 500 0   . 900 375 -203321.25
61 f2_may_2 shop1_2 -461.56 500 0   . 900 0 0.00
62 f2_mar_2 shop2_2 -542.83 500 0   . 1450 120 -65139.60
63 f2_apr_2 shop2_2 -559.19 500 0   . 1450 320 -178940.80
64 f2_may_2 shop2_2 -489.06 500 0   . 1450 20 -9781.20
-1281110.35

Obs _rcost_ _status_ diagonal factory key_id mth_made
1 . B 19 1 production March
2 -0.65 U 19 1 production April
3 0.85 L 19 1 production May
4 63.65 L 19 1 storage March
5 -3.00 U 19 1 storage April
6 -20.65 U 19 1 backorder April
7 43.00 L 19 1 backorder May
8 50.90 L 19 . f1_to_2 March
9 . B 19 . f1_to_2 April
10 . B 19 . f1_to_2 May
11 . B 19 1 sales March
12 -21.00 U 19 1 sales April
13 9.00 L 19 1 sales May
14 -46.09 U 19 1 sales March
15 -32.00 U 19 1 sales April
16 38.00 L 19 1 sales May
17 . B 19 2 production March
18 -27.85 U 19 2 production April
19 23.55 L 19 2 production May
20 15.75 L 19 2 storage March
21 . B 19 2 storage April
22 19.25 L 19 2 backorder April
23 45.00 L 19 2 backorder May
24 -29.90 U 19 . f2_to_1 March
25 22.00 L 19 . f2_to_1 April
26 29.00 L 19 . f2_to_1 May
27 -9.65 U 19 2 sales March
28 . B 19 2 sales April
29 18.00 L 19 2 sales May
30 4.05 L 19 2 sales March
31 -33.00 U 19 2 sales April
32 . B 19 2 sales May
33 -45.16 U 25 1 production March
34 -14.35 U 25 1 production April
35 2.11 L 25 1 production May
36 94.21 L 25 1 storage March
37 75.66 L 25 1 storage April
38 -42.21 U 25 1 backorder April
39 -16.66 U 25 1 backorder May
40 104.06 L 25 . f1_to_2 March
41 13.49 L 25 . f1_to_2 April
42 28.96 L 25 . f1_to_2 May
43 47.13 L 25 1 sales March
44 8.40 L 25 1 sales April
45 . B 25 1 sales May
46 . B 25 1 sales March
47 . B 25 1 sales April
48 32.02 L 25 1 sales May
49 . B 25 2 production March
50 -1.66 U 25 2 production April
51 73.17 L 25 2 production May
52 11.64 L 25 2 storage March
53 108.13 L 25 2 storage April
54 47.36 L 25 2 backorder April
55 -16.13 U 25 2 backorder May
56 -61.06 U 25 . f2_to_1 March
57 30.51 L 25 . f2_to_1 April
58 40.04 L 25 . f2_to_1 May
59 -42.00 U 25 2 sales March
60 . B 25 2 sales April
61 10.50 L 25 2 sales May
62 . B 25 2 sales March
63 . B 25 2 sales April
64 . B 25 2 sales May

Obs _node_ _supdem_ _dual_
1 fact1_1 1000 0.00
2 fact2_1 850 0.00
3 fact1_2 1000 0.00
4 fact2_2 1500 0.00
5 shop1_1 -900 199.75
6 shop2_1 -900 188.75
7 shop1_2 -900 343.83
8 shop2_2 -1450 360.83
9 f1_mar_1 . -127.90
10 f1_apr_1 . -79.25
11 f1_may_1 . -94.25
12 f2_mar_1 . -88.00
13 f2_apr_1 . -90.25
14 f2_may_1 . -110.25
15 f1_mar_2 . -263.06
16 f1_apr_2 . -188.85
17 f1_may_2 . -131.19
18 f2_mar_2 . -182.00
19 f2_apr_2 . -198.36
20 f2_may_2 . -128.23

The log is displayed in Output 6.8.2.

Output 6.8.2: OPTMODEL Log

 NOTE: There were 8 observations read from the data set WORK.NODE0. NOTE: There were 64 observations read from the data set WORK.ARC0. NOTE: Problem generation will use 4 threads. NOTE: The problem has 64 variables (0 free, 0 fixed). NOTE: The problem has 20 linear constraints (4 LE, 16 EQ, 0 GE, 0 range). NOTE: The problem has 128 linear constraint coefficients. NOTE: The problem has 0 nonlinear constraints (0 LE, 0 EQ, 0 GE, 0 range). NOTE: The OPTMODEL presolver is disabled for linear problems. NOTE: The LP presolver value AUTOMATIC is applied. NOTE: The LP presolver removed 0 variables and 0 constraints. NOTE: The LP presolver removed 0 constraint coefficients. NOTE: The presolved problem has 64 variables, 20 constraints, and 128 constraint coefficients. NOTE: The LP solver is called. NOTE: The Dual Simplex algorithm is used. Objective Phase Iteration        Value         Time D 1          1    0.000000E+00         0 D 2          2   -4.020320E+06         0 D 2         33   -1.281110E+06         0 NOTE: Optimal. NOTE: Objective = -1281110.35. NOTE: The Dual Simplex solve time is 0.05 seconds. NOTE: The data set WORK.ARC1 has 64 observations and 16 variables. NOTE: The data set WORK.NODE2 has 20 observations and 3 variables.