Although you could find the connected components of the support graph of each intermediate solution by using OPTMODEL’s programming language capabilities as in Chapter 23, the following SAS macro instead uses the SOLVE WITH NETWORK statement together with the CONCOMP option:
%macro findConnectedComponents; if card(ARCS_SOL) > 0 then do; solve with NETWORK / links = (include=ARCS_SOL) subgraph = (nodes=NODES_SOL) concomp out = (concomp=component_id); COMPONENT_IDS = setof {i in NODES_SOL} component_id[i]; for {c in COMPONENT_IDS} COMPONENT[c] = {}; for {i in NODES_SOL} do; ci = component_id[i]; COMPONENT[ci] = COMPONENT[ci] union {i}; end; end; else COMPONENT_IDS = {}; %mend findConnectedComponents;
The following SAS macro contains a DO UNTIL loop that implements dynamic generation of subtour elimination constraints (“row generation”), as in Chapter 23:
%macro subtourEliminationLoop; /* loop until each vehicle's support graph is connected */ do until (and {v in VEHICLES} num_components[v] <= 1); solve; /* find connected components for each vehicle */ for {v in VEHICLES} do; NODES_SOL = {i in NODES: UseNode[i,v].sol > 0.5}; ARCS_SOL = {<i,j> in ARCS: UseArc[i,j,v].sol > 0.5}; %findConnectedComponents; num_components[v] = card(COMPONENT_IDS); /* create subtour from each component not containing depot node */ for {k in COMPONENT_IDS: depot not in COMPONENT[k]} do; num_subtours = num_subtours + 1; SUBTOUR[num_subtours] = COMPONENT[k]; put SUBTOUR[num_subtours]=; end; end; print UseVehicle TimeUsed num_components; end; %mend subtourEliminationLoop;
The previous two macros are used within the main PROC OPTMODEL call. The first several PROC OPTMODEL statements declare index sets and parameters and then read the input data:
proc optmodel; num num_vehicles init &num_vehicles; set VEHICLES = 1..num_vehicles; str depot = "&depot"; set <str> NODES; read data time_data into NODES=[location]; set ARCS init NODES cross NODES; num travel_time {ARCS}; read data time_data into [i=location] {j in NODES} <travel_time[i,j]=col(j)>;
The following statements make the travel times symmetric, except that travel time back to the depot is set to 0:
for {<i,j> in ARCS: travel_time[i,j] = .} travel_time[i,j] = travel_time[j,i]; /* ignore travel time back to depot */ for {i in NODES} travel_time[i,depot] = 0; /* remove self-loops */ ARCS = ARCS diff setof {i in NODES} <i,i>; print travel_time;
The PRINT statement results in the first section of output, shown in Figure 27.1.
Figure 27.1: travel_time Parameter
travel_time | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Barking | Battersea | Bromley | Dartford | Ealing | Greenwich | Hammersmith | Harrow | Heathrow | Holborn | Islington | Kingston | Richmond | Sutton | Woolwich | |
Barking | 61 | 25 | 25 | 65 | 25 | 65 | 90 | 0 | 47 | 45 | 70 | 72 | 75 | 13 | |
Battersea | 61 | 27 | 43 | 24 | 20 | 7 | 37 | 0 | 12 | 30 | 12 | 14 | 15 | 40 | |
Bromley | 25 | 27 | 15 | 55 | 17 | 41 | 57 | 0 | 53 | 45 | 25 | 33 | 15 | 30 | |
Dartford | 25 | 43 | 15 | 70 | 15 | 45 | 55 | 0 | 60 | 63 | 65 | 53 | 46 | 70 | |
Ealing | 65 | 24 | 55 | 70 | 50 | 10 | 15 | 0 | 30 | 20 | 25 | 15 | 50 | 90 | |
Greenwich | 25 | 20 | 17 | 15 | 50 | 40 | 85 | 0 | 55 | 30 | 34 | 32 | 45 | 10 | |
Hammersmith | 65 | 7 | 41 | 45 | 10 | 40 | 25 | 0 | 12 | 15 | 20 | 8 | 25 | 25 | |
Harrow | 90 | 37 | 57 | 55 | 15 | 85 | 25 | 0 | 35 | 20 | 35 | 30 | 60 | 40 | |
Heathrow | 86 | 44 | 85 | 90 | 25 | 80 | 25 | 20 | 35 | 35 | 35 | 20 | 65 | 82 | |
Holborn | 47 | 12 | 53 | 60 | 30 | 55 | 12 | 35 | 0 | 10 | 22 | 20 | 45 | 21 | |
Islington | 45 | 30 | 45 | 63 | 20 | 30 | 15 | 20 | 0 | 10 | 45 | 34 | 25 | 27 | |
Kingston | 70 | 12 | 25 | 65 | 25 | 34 | 20 | 35 | 0 | 22 | 45 | 5 | 11 | 65 | |
Richmond | 72 | 14 | 33 | 53 | 15 | 32 | 8 | 30 | 0 | 20 | 34 | 5 | 19 | 56 | |
Sutton | 75 | 15 | 15 | 46 | 50 | 45 | 25 | 60 | 0 | 45 | 25 | 11 | 19 | 25 | |
Woolwich | 13 | 40 | 30 | 70 | 90 | 10 | 25 | 40 | 0 | 21 | 27 | 65 | 56 | 25 |
The following model declaration statements correspond directly to the mathematical programming formulation that is described earlier:
var UseNode {NODES, VEHICLES} binary; var UseArc {ARCS, VEHICLES} binary; var UseVehicle {VEHICLES} binary; var TimeUsed {v in VEHICLES} >= 0 <= &time_limit; min NumVehiclesUsed = sum {v in VEHICLES} UseVehicle[v]; con NodeCover {i in NODES diff {depot}}: sum {v in VEHICLES} UseNode[i,v] = 1; con Outflow {i in NODES, v in VEHICLES}: sum {<(i),j> in ARCS} UseArc[i,j,v] = UseNode[i,v]; con Inflow {j in NODES, v in VEHICLES}: sum {<i,(j)> in ARCS} UseArc[i,j,v] = UseNode[j,v]; con UseVehicle_con1 {i in NODES, v in VEHICLES}: UseNode[i,v] <= UseVehicle[v]; con UseVehicle_con2 {v in VEHICLES}: UseVehicle[v] <= UseNode[depot,v]; con TimeUsed_con {v in VEHICLES}: TimeUsed[v] = sum {<i,j> in ARCS} travel_time[i,j] * UseArc[i,j,v];
The following statements declare optional symmetry-breaking constraints to reduce the number of essentially identical branch-and-bound nodes that are explored by the mixed integer linear programming solver:
/* several alternatives for symmetry-breaking constraints */ con Symmetry {v in VEHICLES diff {1}}: sum {i in NODES} UseNode[i,v] <= sum {i in NODES} UseNode[i,v-1]; * con Symmetry {v in VEHICLES diff {1}}: UseVehicle[v] <= UseVehicle[v-1]; * con Symmetry {v in VEHICLES diff {1}}: TimeUsed[v] <= TimeUsed[v-1];
Williams (2013) breaks symmetry by using the first alternative, but you could use one of the other alternatives instead.
In SAS/OR 13.1, the mixed integer linear programming solver automatically detects and exploits symmetry without you having to explicitly declare such symmetry-breaking constraints. You can control the aggressiveness of symmetry detection by using the SYMMETRY= option in the SOLVE WITH MILP statement.
The following statements declare the subtour elimination constraints:
num num_subtours init 0; /* subset of nodes not containing depot node */ set <str> SUBTOUR {1..num_subtours}; /* if node k in SUBTOUR[s] is used by vehicle v, then must use at least two arcs across partition induced by SUBTOUR[s] */ con Subtour_elimination {s in 1..num_subtours, k in SUBTOUR[s], v in VEHICLES}: sum {i in NODES diff SUBTOUR[s], j in SUBTOUR[s]: <i,j> in ARCS} UseArc[i,j,v] + sum {i in SUBTOUR[s], j in NODES diff SUBTOUR[s]: <i,j> in ARCS} UseArc[i,j,v] >= 2 * UseNode[k,v];
The following statements declare the index sets and parameters that are needed to detect violated subtour elimination constraints:
num num_components {VEHICLES}; set <str> NODES_SOL; set <str,str> ARCS_SOL; num component_id {NODES_SOL}; set COMPONENT_IDS; set <str> COMPONENT {COMPONENT_IDS}; num ci;
The following statements call the %subtourEliminationLoop macro to minimize the number of vehicles used and then use the .sol
objective suffix to update the num_vehicles parameter to the resulting minimum value:
%subtourEliminationLoop; num_vehicles = round(NumVehiclesUsed.sol);
Changing the value of num_vehicles automatically updates the VEHICLES index set and consequently all the model declarations that depend on VEHICLES.
The following statements declare the additional variables, objective, and constraints that are needed to minimize the makespan, given the minimum number of vehicles already found:
var MaxTimeUsed >= 0 <= &time_limit; min Makespan = MaxTimeUsed; con MaxTimeUsed_con {v in VEHICLES}: MaxTimeUsed >= TimeUsed[v];
The following statements call the %subtourEliminationLoop macro again to minimize the makespan and then print the nodes and arcs that are used by each vehicle in the final solution:
%subtourEliminationLoop; for {v in VEHICLES: UseVehicle[v].sol > 0.5} do; print v; print {<i,j> in ARCS: UseArc[i,j,v].sol > 0.5} travel_time[i,j]; end; quit;
Figure 27.2 through Figure 27.7 show the output from each iteration of subtour elimination.
Figure 27.2: Output from Subtour Elimination, Iteration 1
[1] | UseVehicle | TimeUsed | num_components |
---|---|---|---|
1 | 1 | 105 | 5 |
2 | 1 | 110 | 2 |
3 | 0 | 0 | 0 |
4 | 0 | 0 | 0 |
5 | 0 | 0 | 0 |
6 | 0 | 0 | 0 |
Figure 27.3: Output from Subtour Elimination, Iteration 2
[1] | UseVehicle | TimeUsed | num_components |
---|---|---|---|
1 | 1 | 91 | 2 |
2 | 1 | 113 | 3 |
3 | 0 | 0 | 0 |
4 | 0 | 0 | 0 |
5 | 0 | 0 | 0 |
6 | 0 | 0 | 0 |
Figure 27.4: Output from Subtour Elimination, Iteration 3
[1] | UseVehicle | TimeUsed | num_components |
---|---|---|---|
1 | 1 | 118 | 1 |
2 | 1 | 112 | 1 |
3 | 0 | 0 | 0 |
4 | 0 | 0 | 0 |
5 | 0 | 0 | 0 |
6 | 0 | 0 | 0 |
Figure 27.5: Output from Subtour Elimination, Iteration 4
[1] | UseVehicle | TimeUsed | num_components |
---|---|---|---|
1 | 1 | 96 | 2 |
2 | 1 | 98 | 2 |
Figure 27.6: Output from Subtour Elimination, Iteration 5
[1] | UseVehicle | TimeUsed | num_components |
---|---|---|---|
1 | 1 | 93 | 2 |
2 | 1 | 98 | 2 |
Figure 27.7: Output from Subtour Elimination, Iteration 6
[1] | UseVehicle | TimeUsed | num_components |
---|---|---|---|
1 | 1 | 99 | 1 |
2 | 1 | 100 | 1 |
Figure 27.8 shows the final problem and solution summaries from the mixed integer linear programming solver.
Figure 27.8: Final Problem and Solution Summaries from Mixed Integer Linear Programming Solver
Problem Summary | |
---|---|
Objective Sense | Minimization |
Objective Function | Makespan |
Objective Type | Linear |
Number of Variables | 455 |
Bounded Above | 0 |
Bounded Below | 0 |
Bounded Below and Above | 455 |
Free | 0 |
Fixed | 0 |
Binary | 452 |
Integer | 0 |
Number of Constraints | 171 |
Linear LE (<=) | 33 |
Linear EQ (=) | 76 |
Linear GE (>=) | 62 |
Linear Range | 0 |
Constraint Coefficients | 5488 |
Performance Information | |
---|---|
Execution Mode | Single-Machine |
Number of Threads | 1 |
Solution Summary | |
---|---|
Solver | MILP |
Algorithm | Branch and Cut |
Objective Function | Makespan |
Solution Status | Optimal |
Objective Value | 100 |
Relative Gap | 0 |
Absolute Gap | 0 |
Primal Infeasibility | 1.705303E-13 |
Bound Infeasibility | 1.199041E-14 |
Integer Infeasibility | 1.199041E-14 |
Best Bound | 100 |
Nodes | 105 |
Iterations | 3410 |
Presolve Time | 0.02 |
Solution Time | 2.65 |
Figure 27.9 shows the travel times for the arcs that are used by each vehicle in the final solution.
Figure 27.9: Travel Times for Arcs Used by Each Vehicle in the Final Solution
v |
---|
1 |
[1] | [2] | |
---|---|---|
Bromley | Dartford | 15 |
Dartford | Heathrow | 0 |
Ealing | Hammersmith | 10 |
Hammersmith | Richmond | 8 |
Harrow | Ealing | 15 |
Heathrow | Harrow | 20 |
Kingston | Sutton | 11 |
Richmond | Kingston | 5 |
Sutton | Bromley | 15 |
v |
---|
2 |
[1] | [2] | |
---|---|---|
Barking | Heathrow | 0 |
Battersea | Greenwich | 20 |
Greenwich | Woolwich | 10 |
Heathrow | Islington | 35 |
Holborn | Battersea | 12 |
Islington | Holborn | 10 |
Woolwich | Barking | 13 |