The first several index sets are one-dimensional, as in all the previous examples:
proc optmodel; set <str> WORKERS; num waste_new {WORKERS}; num waste_old {WORKERS}; num recruit_ub {WORKERS}; num redundancy_cost {WORKERS}; num overmanning_cost {WORKERS}; num shorttime_ub {WORKERS}; num shorttime_cost {WORKERS}; read data worker_data into WORKERS=[worker] waste_new waste_old recruit_ub redundancy_cost overmanning_cost shorttime_ub shorttime_cost; set PERIODS0; num demand {WORKERS, PERIODS0}; read data demand_data into PERIODS0=[period] {worker in WORKERS} <demand[worker,period]=col(worker)>; var NumWorkers {WORKERS, PERIODS0} >= 0; for {worker in WORKERS} fix NumWorkers[worker,0] = demand[worker,0]; set PERIODS = PERIODS0 diff {0}; var NumRecruits {worker in WORKERS, PERIODS} >= 0 <= recruit_ub[worker]; var NumRedundant {WORKERS, PERIODS} >= 0; var NumShortTime {worker in WORKERS, PERIODS} >= 0 <= shorttime_ub[worker]; var NumExcess {WORKERS, PERIODS} >= 0;
Both RETRAIN_PAIRS and DOWNGRADE_PAIRS are two-dimensional index sets, declared by using the optional <STR,STR> specification in the SET statement so that these sets contain pairs of strings. In general, a set can consist of tuples of any length and any combination of NUM and STR scalar-types.
set <str,str> RETRAIN_PAIRS; num retrain_ub {RETRAIN_PAIRS}; num retrain_cost {RETRAIN_PAIRS}; read data retrain_data into RETRAIN_PAIRS=[worker1 worker2] retrain_ub retrain_cost; var NumRetrain {RETRAIN_PAIRS, PERIODS} >= 0; for {<i,j> in RETRAIN_PAIRS: retrain_ub[i,j] ne .} for {period in PERIODS} NumRetrain[i,j,period].ub = retrain_ub[i,j]; set <str,str> DOWNGRADE_PAIRS; read data downgrade_data into DOWNGRADE_PAIRS=[worker1 worker2]; var NumDowngrade {DOWNGRADE_PAIRS, PERIODS} >= 0; con Demand_con {worker in WORKERS, period in PERIODS}: NumWorkers[worker,period] - (1 - &shorttime_frac) * NumShortTime[worker,period] - NumExcess[worker,period] = demand[worker,period];
The following Flow_balance_con constraint uses an implicit slice to express a few of the summations compactly:
con Flow_balance_con {worker in WORKERS, period in PERIODS}: NumWorkers[worker,period] = (1 - waste_old[worker]) * NumWorkers[worker,period-1] + (1 - waste_new[worker]) * NumRecruits[worker,period] + (1 - waste_old[worker]) * sum {<i,(worker)> in RETRAIN_PAIRS} NumRetrain[i,worker,period] + (1 - &downgrade_leave_frac) * sum {<i,(worker)> in DOWNGRADE_PAIRS} NumDowngrade[i,worker,period] - sum {<(worker),j> in RETRAIN_PAIRS} NumRetrain[worker,j,period] - sum {<(worker),j> in DOWNGRADE_PAIRS} NumDowngrade[worker,j,period] - NumRedundant[worker,period];
For example,
<i,(worker)> in RETRAIN_PAIRS
i in slice(<*,worker>,RETRAIN_PAIRS)
which is equivalent to
i in WORKERS: <i,worker> in RETRAIN_PAIRS
The remaining two constraints are straightforward:
con Semiskill_retrain_con {period in PERIODS}: NumRetrain['semiskilled','skilled',period] <= &semiskill_retrain_frac_ub * NumWorkers['skilled',period]; con Overmanning_con {period in PERIODS}: sum {worker in WORKERS} NumExcess[worker,period] <= &overmanning_ub;
This example uses two objectives, Redundancy
and Cost
, declared in the following MIN statements:
min Redundancy = sum {worker in WORKERS, period in PERIODS} NumRedundant[worker,period]; min Cost = sum {worker in WORKERS, period in PERIODS} ( redundancy_cost[worker] * NumRedundant[worker,period] + shorttime_cost[worker] * NumShorttime[worker,period] + overmanning_cost[worker] * NumExcess[worker,period]) + sum {<i,j> in RETRAIN_PAIRS, period in PERIODS} retrain_cost[i,j] * NumRetrain[i,j,period];
The LP 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 Redundancy; print Redundancy Cost; print NumWorkers NumRecruits NumRedundant NumShortTime NumExcess; print NumRetrain; print NumDowngrade; create data sol_data1 from [worker period] NumWorkers NumRecruits NumRedundant NumShortTime NumExcess; create data sol_data2 from [worker1 worker2 period] NumRetrain NumDowngrade; solve obj Cost; print Redundancy Cost; print NumWorkers NumRecruits NumRedundant NumShortTime NumExcess; print NumRetrain; print NumDowngrade; create data sol_data3 from [worker period] NumWorkers NumRecruits NumRedundant NumShortTime NumExcess; create data sol_data4 from [worker1 worker2 period] NumRetrain NumDowngrade; quit;
Figure 5.1 shows the output that results from the first SOLVE statement.
Figure 5.1: Output from First SOLVE Statement, Minimizing Redundancy
Problem Summary | |
---|---|
Objective Sense | Minimization |
Objective Function | Redundancy |
Objective Type | Linear |
Number of Variables | 63 |
Bounded Above | 0 |
Bounded Below | 39 |
Bounded Below and Above | 21 |
Free | 0 |
Fixed | 3 |
Number of Constraints | 24 |
Linear LE (<=) | 6 |
Linear EQ (=) | 18 |
Linear GE (>=) | 0 |
Linear Range | 0 |
Constraint Coefficients | 108 |
[1] | [2] | NumWorkers | NumRecruits | NumRedundant | NumShortTime | NumExcess |
---|---|---|---|---|---|---|
semiskilled | 0 | 1500 | ||||
semiskilled | 1 | 1443 | 0.00 | 0.00 | 50 | 17.969 |
semiskilled | 2 | 2000 | 649.30 | 0.00 | 0 | 0.000 |
semiskilled | 3 | 2500 | 676.97 | 0.00 | 0 | 0.000 |
skilled | 0 | 1000 | ||||
skilled | 1 | 1025 | 0.00 | 0.00 | 50 | 0.000 |
skilled | 2 | 1500 | 500.00 | 0.00 | 0 | 0.000 |
skilled | 3 | 2000 | 500.00 | 0.00 | 0 | 0.000 |
unskilled | 0 | 2000 | ||||
unskilled | 1 | 1157 | 0.00 | 442.97 | 50 | 132.031 |
unskilled | 2 | 675 | 0.00 | 166.33 | 50 | 150.000 |
unskilled | 3 | 175 | 0.00 | 232.50 | 50 | 150.000 |
Figure 5.2 shows the output that results from the second SOLVE statement.
Figure 5.2: Output from Second SOLVE Statement, Minimizing Cost
Problem Summary | |
---|---|
Objective Sense | Minimization |
Objective Function | Cost |
Objective Type | Linear |
Number of Variables | 63 |
Bounded Above | 0 |
Bounded Below | 39 |
Bounded Below and Above | 21 |
Free | 0 |
Fixed | 3 |
Number of Constraints | 24 |
Linear LE (<=) | 6 |
Linear EQ (=) | 18 |
Linear GE (>=) | 0 |
Linear Range | 0 |
Constraint Coefficients | 108 |
[1] | [2] | NumWorkers | NumRecruits | NumRedundant | NumShortTime | NumExcess |
---|---|---|---|---|---|---|
semiskilled | 0 | 1500 | ||||
semiskilled | 1 | 1400 | 0.000 | 0.00 | 0 | 0 |
semiskilled | 2 | 2000 | 800.000 | 0.00 | 0 | 0 |
semiskilled | 3 | 2500 | 800.000 | 0.00 | 0 | 0 |
skilled | 0 | 1000 | ||||
skilled | 1 | 1000 | 55.556 | 0.00 | 0 | 0 |
skilled | 2 | 1500 | 500.000 | 0.00 | 0 | 0 |
skilled | 3 | 2000 | 500.000 | 0.00 | 0 | 0 |
unskilled | 0 | 2000 | ||||
unskilled | 1 | 1000 | 0.000 | 812.50 | 0 | 0 |
unskilled | 2 | 500 | 0.000 | 257.62 | 0 | 0 |
unskilled | 3 | 0 | 0.000 | 353.60 | 0 | 0 |