Skip to content

Commit 0a8a23c

Browse files
committed
Add subtour constraint as Gurobi lazy constraint
1 parent b73d5aa commit 0a8a23c

File tree

4 files changed

+116
-0
lines changed

4 files changed

+116
-0
lines changed

src/main/java/io/github/plastix/GraphUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package io.github.plastix;
22

3+
import com.graphhopper.routing.util.DefaultEdgeFilter;
34
import com.graphhopper.routing.util.FlagEncoder;
45
import com.graphhopper.storage.Graph;
6+
import com.graphhopper.util.EdgeExplorer;
57
import com.graphhopper.util.EdgeIterator;
68

79
public class GraphUtils {
@@ -30,4 +32,8 @@ public boolean isOneWay(EdgeIterator edgeIterator) {
3032
return edgeIterator.isForward(flagEncoder) != edgeIterator.isBackward(flagEncoder);
3133
}
3234

35+
public EdgeExplorer getEdgeExplorer() {
36+
return graph.createEdgeExplorer(new DefaultEdgeFilter(flagEncoder));
37+
}
38+
3339
}

src/main/java/io/github/plastix/Main.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ private static void setupSolver() throws GRBException {
104104
GRBVar startNode = vars.getVertex(START_NODE_ID);
105105
startNode.set(GRB.DoubleAttr.LB, 1);
106106
startNode.set(GRB.DoubleAttr.UB, 1);
107+
108+
// Must set LazyConstraints parameter when using lazy constraints
109+
model.set(GRB.IntParam.LazyConstraints, 1);
110+
model.setCallback(new SubtourConstraint(vars, START_NODE_ID, graphUtils));
107111
}
108112

109113
private static void runSolver() throws GRBException {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package io.github.plastix;
2+
3+
import com.carrotsearch.hppc.IntArrayDeque;
4+
import com.carrotsearch.hppc.IntHashSet;
5+
import com.carrotsearch.hppc.cursors.IntCursor;
6+
import com.graphhopper.util.EdgeExplorer;
7+
import com.graphhopper.util.EdgeIterator;
8+
import gurobi.GRB;
9+
import gurobi.GRBCallback;
10+
import gurobi.GRBException;
11+
import gurobi.GRBLinExpr;
12+
13+
public class SubtourConstraint extends GRBCallback {
14+
15+
private final int START_NODE_ID;
16+
private GraphUtils graphUtils;
17+
private Vars vars;
18+
19+
SubtourConstraint(Vars vars, int startNodeId, GraphUtils graphUtils) {
20+
this.vars = vars;
21+
this.START_NODE_ID = startNodeId;
22+
this.graphUtils = graphUtils;
23+
}
24+
25+
@Override
26+
protected void callback() {
27+
try {
28+
if(where == GRB.CB_MIPSOL) { // Found an integer feasible solution
29+
30+
IntHashSet vertices = getReachableVertexSubset(START_NODE_ID);
31+
int verticesVisited = numVisitedVertices();
32+
33+
// If the number of vertices we can reach from the start is not the number of vertices we
34+
// visit in the entire solution, we have a disconnected tour
35+
if(vertices.size() < verticesVisited) {
36+
vertices.remove(START_NODE_ID);
37+
38+
// Add sub-tour elimination constraint
39+
GRBLinExpr subtourConstraint = new GRBLinExpr();
40+
int sumVertexVisits = 0;
41+
int totalOutgoingEdges = 0;
42+
43+
for(IntCursor cursor : vertices) {
44+
int vertexId = cursor.value;
45+
EdgeIterator outgoing = graphUtils.outgoingEdges(vertexId);
46+
47+
while(outgoing.next()) {
48+
subtourConstraint.addTerm(1, vars.getArc(outgoing));
49+
totalOutgoingEdges += 1;
50+
}
51+
52+
sumVertexVisits += getSolution(vars.getVertex(vertexId));
53+
}
54+
55+
double rhs = ((double) sumVertexVisits) / ((double) totalOutgoingEdges);
56+
addLazy(subtourConstraint, GRB.GREATER_EQUAL, rhs);
57+
58+
}
59+
}
60+
} catch(GRBException e) {
61+
System.out.println("Error code: " + e.getErrorCode() + ". " +
62+
e.getMessage());
63+
e.printStackTrace();
64+
}
65+
}
66+
67+
private int numVisitedVertices() throws GRBException {
68+
double[] values = getSolution(vars.getVerts());
69+
70+
int visited = 0;
71+
for(double value : values) {
72+
if(value > 0) {
73+
visited++;
74+
}
75+
}
76+
77+
return visited;
78+
}
79+
80+
private IntHashSet getReachableVertexSubset(int startNode) throws GRBException {
81+
EdgeExplorer explorer = graphUtils.getEdgeExplorer();
82+
IntArrayDeque stack = new IntArrayDeque();
83+
stack.addLast(startNode);
84+
85+
IntHashSet explored = new IntHashSet();
86+
int current;
87+
while(stack.size() > 0) {
88+
current = stack.removeLast();
89+
if(!explored.contains(current)) {
90+
EdgeIterator iter = explorer.setBaseNode(current);
91+
while(iter.next()) {
92+
int connectedId = iter.getAdjNode();
93+
if(getSolution(vars.getArc(iter)) > 0) {
94+
stack.addLast(connectedId);
95+
}
96+
}
97+
explored.add(current);
98+
}
99+
}
100+
101+
return explored;
102+
}
103+
}

src/main/java/io/github/plastix/Vars.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,7 @@ public GRBVar getVertex(int id) {
8686
return verts[id];
8787
}
8888

89+
public GRBVar[] getVerts() {
90+
return verts;
91+
}
8992
}

0 commit comments

Comments
 (0)