PROC OPTMODEL Statements and Output
The following PROC OPTMODEL statements declare an index set and parameters and then read the input data:
proc optmodel;
set RETAILERS;
num region {RETAILERS};
num oil {RETAILERS};
num delivery {RETAILERS};
num spirit {RETAILERS};
str growth {RETAILERS};
read data retailer_data into RETAILERS=[_N_]
region oil delivery spirit growth;
The following statements declare parameters and index sets, which are initialized to be empty and then populated within a
FOR loop. Note that both RETAILERS_region and RETAILERS_group are sets that are indexed by other sets:
set REGIONS init {};
set RETAILERS_region {REGIONS} init {};
num r;
set <str> GROUPS init {};
set RETAILERS_group {GROUPS} init {};
str g;
for {retailer in RETAILERS} do;
r = region[retailer];
REGIONS = REGIONS union {r};
RETAILERS_region[r] = RETAILERS_region[r] union {retailer};
g = growth[retailer];
GROUPS = GROUPS union {g};
RETAILERS_group[g] = RETAILERS_group[g] union {retailer};
end;
set DIVISIONS;
num target {DIVISIONS};
read data division_data into DIVISIONS=[_N_] target;
num tolerance = &tolerance;
The following declarations are straightforward:
var Assign {RETAILERS, DIVISIONS} binary;
con Assign_con {retailer in RETAILERS}:
sum {division in DIVISIONS} Assign[retailer,division] = 1;
set CATEGORIES = {'delivery','spirit'}
union (setof {reg in REGIONS} 'oil'||reg)
union (setof {group in GROUPS} 'growth'||group);
var MarketShare {CATEGORIES, DIVISIONS};
var Surplus {CATEGORIES, DIVISIONS} >= 0 <= tolerance;
var Slack {CATEGORIES, DIVISIONS} >= 0 <= tolerance;
min Objective1 =
sum {category in CATEGORIES, division in DIVISIONS}
(Surplus[category,division] + Slack[category,division]);
con Delivery_con {division in DIVISIONS}:
MarketShare['delivery',division]
= (sum {retailer in RETAILERS} delivery[retailer] *
Assign[retailer,division])
/ (sum {retailer in RETAILERS} delivery[retailer]);
con Spirit_con {division in DIVISIONS}:
MarketShare['spirit',division]
= (sum {retailer in RETAILERS} spirit[retailer] *
Assign[retailer,division])
/ (sum {retailer in RETAILERS} spirit[retailer]);
con Oil_con {reg in REGIONS, division in DIVISIONS}:
MarketShare['oil'||reg,division]
= (sum {retailer in RETAILERS_region[reg]}
oil[retailer] * Assign[retailer,division])
/ (sum {retailer in RETAILERS_region[reg]} oil[retailer]);
con Growth_con {group in GROUPS, division in DIVISIONS}:
MarketShare['growth'||group,division]
= (sum {retailer in RETAILERS_group[group]} Assign[retailer,division])
/ card(RETAILERS_group[group]);
con Abs_dev_con {category in CATEGORIES, division in DIVISIONS}:
MarketShare[category,division]
- Surplus[category,division] + Slack[category,division]
= target[division];
The following NUM statements use the ABS function, the .sol
variable suffix, and the MAX aggregation operator to compute the two norms from the optimal solution:
num sum_abs_dev =
sum {category in CATEGORIES, division in DIVISIONS}
abs(MarketShare[category,division].sol - target[division]);
num max_abs_dev =
max {category in CATEGORIES, division in DIVISIONS}
abs(MarketShare[category,division].sol - target[division]);
The MILP solver is called twice, and each SOLVE statement includes the OBJ option to specify which objective to optimize.
The first PRINT statement after each SOLVE statement reports the values of both objectives even though only one objective
is optimized at a time:
solve obj Objective1;
print sum_abs_dev max_abs_dev;
print Assign;
print MarketShare Surplus Slack;
Figure 14.1 shows the output that results from the first SOLVE statement. The optimal objective value disagrees with the value 0.0453
presented in Williams, even after you account for the fact that the formulation here counts each deviation twice (once per
division).
The following statements declare the additional variable, objective, and constraints for problem (ii), with the tighter linearization
of the norm as in Chapter 12:
var MinMax >= 0 init max_abs_dev;
min Objective2 = MinMax;
con MinMax_con {category in CATEGORIES, division in DIVISIONS}:
MinMax >= Surplus[category,division] + Slack[category,division];
The following statements call the MILP solver to solve problem (ii). The PRIMALIN option is used to initialize the MILP solver
with the optimal solution that was obtained for problem (i):
solve obj Objective2 with MILP / primalin;
print sum_abs_dev max_abs_dev;
print Assign;
print MarketShare Surplus Slack;
quit;
Figure 14.2 shows the output that results from the second SOLVE statement.