Skip to content

Commit 803b7cc

Browse files
committed
Use Gurobi for our integer program solver
1 parent cb5ad5f commit 803b7cc

File tree

3 files changed

+62
-92
lines changed

3 files changed

+62
-92
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# osm-integer-programming
2-
A program for calculating optimal cycling routes modelled as an integer program (known as the *Cycle Trip Planning Problem*). Mapping data is provided by [OpenStreetMaps](https://www.openstreetmap.org/#) and parsed by [GraphHopper](https://github.com/graphhopper/graphhopper). The integer program is solved using [Google Optimization Tools](https://developers.google.com/optimization/).
2+
A program for calculating optimal cycling routes modelled as an integer program (known as the *Cycle Trip Planning Problem*). Mapping data is provided by [OpenStreetMaps](https://www.openstreetmap.org/#) and parsed by [GraphHopper](https://github.com/graphhopper/graphhopper). The integer program is solved using [Gurobi](http://www.gurobi.com/)
33

44
Mathemtical formulation of the Cycle Trip Planning Problem is from the following [paper](https://www.sciencedirect.com/science/article/pii/S1366554514000751):
55
```

build.gradle

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ repositories {
1313
}
1414

1515
dependencies {
16-
compile fileTree(dir: 'libs', include: '*.jar')
16+
compile fileTree(dir: System.getenv('GUROBI_HOME') + '/lib', include: '*.jar')
1717
testCompile group: 'junit', name: 'junit', version: '4.12'
1818
compile group: 'com.graphhopper', name: 'graphhopper-reader-osm', version: '0.9.0'
19-
}
20-
21-
run {
22-
systemProperty "java.library.path", "libs"
2319
}
Lines changed: 60 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package io.github.plastix;
22

3-
import com.google.ortools.linearsolver.MPConstraint;
4-
import com.google.ortools.linearsolver.MPObjective;
5-
import com.google.ortools.linearsolver.MPSolver;
6-
import com.google.ortools.linearsolver.MPVariable;
3+
import com.carrotsearch.hppc.IntHashSet;
74
import com.graphhopper.GraphHopper;
85
import com.graphhopper.reader.osm.GraphHopperOSM;
96
import com.graphhopper.routing.util.AllEdgesIterator;
@@ -15,10 +12,12 @@
1512
import com.graphhopper.storage.index.QueryResult;
1613
import com.graphhopper.util.EdgeIterator;
1714
import com.graphhopper.util.shapes.GHPoint;
15+
import gurobi.*;
16+
17+
import java.util.Arrays;
1818

1919
public class Main {
2020

21-
private static final String SOLVER_TYPE = "GLPK_MIXED_INTEGER_PROGRAMMING";
2221
// TODO (Aidan) Read these params from file
2322
private static final int MAX_COST = 40_000; // In meters
2423
private static final int MIN_COST = 20_000; // In meters
@@ -33,34 +32,14 @@ public class Main {
3332
private static Weighting score;
3433
private static FlagEncoder flagEncoder;
3534

36-
// Optimization variables
37-
private static MPSolver solver;
38-
private static MPVariable[] arcsFwd;
39-
private static MPVariable[] arcsBwd;
35+
private static GRBModel model;
36+
private static GRBVar[] arcsFwd;
37+
private static GRBVar[] arcsBwd;
4038
private static int[] arcBaseIds;
4139

42-
static {
43-
// Load Google Optimization tools native libs
44-
System.loadLibrary("jniortools");
45-
}
46-
47-
private static MPSolver createSolver(String solverType) {
48-
try {
49-
return new MPSolver("osm-integer-programming",
50-
MPSolver.OptimizationProblemType.valueOf(solverType));
51-
} catch(java.lang.IllegalArgumentException e) {
52-
return null;
53-
}
54-
}
55-
56-
private static void setupSolver() {
57-
solver = createSolver(SOLVER_TYPE);
58-
if(solver == null) {
59-
System.out.println("Could not create solver " + SOLVER_TYPE);
60-
System.exit(1);
61-
}
62-
63-
double infinity = MPSolver.infinity();
40+
private static void setupSolver() throws GRBException {
41+
GRBEnv env = new GRBEnv("osm.log");
42+
model = new GRBModel(env);
6443

6544
AllEdgesIterator edges = graph.getAllEdges();
6645
int numEdges = edges.getMaxId();
@@ -69,87 +48,98 @@ private static void setupSolver() {
6948
// Make a decision variable "x_i" for every arc in our graph
7049
// x_i: =1 if arc is travelled, 0 otherwise
7150
// Since every edge can be 2 arcs (forward + backward), we keep two lists
72-
arcsFwd = solver.makeBoolVarArray(numEdges);
73-
arcsBwd = solver.makeBoolVarArray(numEdges);
51+
arcsFwd = model.addVars(numEdges, GRB.BINARY);
52+
arcsBwd = model.addVars(numEdges, GRB.BINARY);
7453
arcBaseIds = new int[numEdges];
7554

7655
// Make a variable for every node in the graph
7756
// verts[i]: = the number of times vertex i is visited
78-
MPVariable[] verts = solver.makeIntVarArray(numNodes, 0, infinity);
57+
char[] types = new char[numNodes];
58+
Arrays.fill(types, GRB.INTEGER);
59+
GRBVar[] verts = model.addVars(null, null, null, types,
60+
null, 0, numNodes);
7961

8062
// (1a)
8163
// Objective maximizes total collected score of all roads
82-
MPObjective objective = solver.objective();
83-
objective.setMaximization();
64+
GRBLinExpr objective = new GRBLinExpr();
8465

8566
// (1b)
8667
// Limit length of path
87-
MPConstraint maxCost = solver.makeConstraint(-infinity, MAX_COST);
68+
GRBLinExpr maxCost = new GRBLinExpr();
8869

8970
while(edges.next()) {
9071
int edgeId = edges.getEdge();
9172
arcBaseIds[edgeId] = edges.getBaseNode(); // Record the original base ID to keep direction
92-
MPVariable fwd = arcsFwd[edgeId];
93-
MPVariable bwd = arcsBwd[edgeId];
73+
GRBVar fwd = arcsFwd[edgeId];
74+
GRBVar bwd = arcsBwd[edgeId];
9475
double edgeScore = score.calcWeight(edges, false, edgeId);
9576
double edgeDist = edges.getDistance();
9677

97-
9878
if(edges.isForward(flagEncoder)) {
99-
objective.setCoefficient(fwd, edgeScore);
100-
maxCost.setCoefficient(fwd, edgeDist);
79+
objective.addTerm(edgeScore, fwd);
80+
maxCost.addTerm(edgeDist, fwd);
10181
} else {
102-
fwd.setInteger(false);
82+
fwd.set(GRB.DoubleAttr.UB, 0);
10383
}
10484

10585
if(edges.isBackward(flagEncoder)) {
106-
objective.setCoefficient(bwd, edgeScore);
107-
maxCost.setCoefficient(bwd, edgeDist);
86+
objective.addTerm(edgeScore, bwd);
87+
maxCost.addTerm(edgeDist, bwd);
10888
} else {
109-
bwd.setInteger(false);
89+
bwd.set(GRB.DoubleAttr.UB, 0);
11090
}
11191

11292
// (1j)
113-
MPConstraint arcConstraint = solver.makeConstraint(-infinity, 1);
114-
arcConstraint.setCoefficient(fwd, 1);
115-
arcConstraint.setCoefficient(bwd, 1);
93+
GRBLinExpr arcConstraint = new GRBLinExpr();
94+
arcConstraint.addTerm(1, fwd);
95+
arcConstraint.addTerm(1, bwd);
96+
model.addConstr(arcConstraint, GRB.LESS_EQUAL, 1, "arc_constraint");
11697
}
11798

99+
model.setObjective(objective, GRB.MAXIMIZE);
100+
model.addConstr(maxCost, GRB.LESS_EQUAL, MAX_COST, "max_cost");
101+
118102
for(int i = 0; i < numNodes; i++) {
119103
// (1d)
120-
MPConstraint edgeCounts = solver.makeConstraint(0, 0);
104+
GRBLinExpr edgeCounts = new GRBLinExpr();
105+
IntHashSet incomingIds = new IntHashSet();
121106
EdgeIterator incoming = graphUtils.incomingEdges(i);
122107
while(incoming.next()) {
123-
edgeCounts.setCoefficient(getArc(incoming), 1);
108+
incomingIds.add(incoming.getEdge());
109+
edgeCounts.addTerm(1, getArc(incoming));
124110
}
125111

126112
EdgeIterator outgoing = graphUtils.outgoingEdges(i);
127113
while(outgoing.next()) {
128-
MPVariable arc = getArc(outgoing);
114+
GRBVar arc = getArc(outgoing);
129115
// Check if we already recorded it as an incoming edge
130-
if(edgeCounts.getCoefficient(arc) == 1) {
131-
edgeCounts.setCoefficient(arc, 0);
116+
if(incomingIds.contains(outgoing.getEdge())) {
117+
edgeCounts.remove(arc);
132118
} else {
133-
edgeCounts.setCoefficient(arc, -1);
119+
edgeCounts.addTerm(-1, arc);
134120
}
135121
}
136122

123+
model.addConstr(edgeCounts, GRB.EQUAL, 0, "edge_counts");
124+
137125
// (1e)
138-
MPConstraint vertexVisits = solver.makeConstraint(0, 0);
126+
GRBLinExpr vertexVisits = new GRBLinExpr();
139127
outgoing = graphUtils.outgoingEdges(i);
140128
while(outgoing.next()) {
141-
vertexVisits.setCoefficient(getArc(outgoing), 1);
129+
vertexVisits.addTerm(1, getArc(outgoing));
142130
}
143-
vertexVisits.setCoefficient(verts[i], -1);
131+
vertexVisits.addTerm(-1, verts[i]);
132+
model.addConstr(vertexVisits, GRB.EQUAL, 0, "vertex_visits");
144133
}
145134

146135
// (1h)/(1i)
147136
// Start vertex can only be visited once
148-
MPVariable startNode = verts[START_NODE_ID];
149-
startNode.setBounds(1, 1);
137+
GRBVar startNode = verts[START_NODE_ID];
138+
startNode.set(GRB.DoubleAttr.LB, 1);
139+
startNode.set(GRB.DoubleAttr.UB, 1);
150140
}
151141

152-
private static MPVariable getArc(EdgeIterator edge) {
142+
private static GRBVar getArc(EdgeIterator edge) {
153143
int edgeId = edge.getEdge();
154144
int baseNode = edge.getBaseNode();
155145
if(baseNode == arcBaseIds[edgeId]) {
@@ -159,32 +149,11 @@ private static MPVariable getArc(EdgeIterator edge) {
159149
}
160150
}
161151

162-
private static void runSolver() {
152+
private static void runSolver() throws GRBException {
163153
System.out.println("Max cost: " + MAX_COST);
164154
System.out.println("Start position: " + START_POSITION + " (Node " + START_NODE_ID + ")");
165-
System.out.println("Number of constraints: " + solver.numConstraints());
166-
System.out.println("Number of variables: " + solver.numVariables());
167-
168-
final MPSolver.ResultStatus resultStatus = solver.solve();
169-
170-
// Check that the problem has an optimal solution.
171-
if(resultStatus != MPSolver.ResultStatus.OPTIMAL) {
172-
System.err.println("The problem does not have an optimal solution!");
173-
return;
174-
}
175155

176-
// Verify that the solution satisfies all constraints (when using solvers
177-
// others than GLOP_LINEAR_PROGRAMMING, this is highly recommended!).
178-
if(!solver.verifySolution(/*tolerance=*/1e-7, /*logErrors=*/true)) {
179-
System.err.println("The solution returned by the solver violated the"
180-
+ " problem constraints by at least 1e-7");
181-
return;
182-
}
183-
184-
System.out.println("Problem solved in " + solver.wallTime() + " milliseconds");
185-
186-
// The objective value of the solution.
187-
System.out.println("Optimal objective value = " + solver.objective().value());
156+
model.optimize();
188157
}
189158

190159
private static void loadOSM() {
@@ -233,8 +202,13 @@ public static void main(String[] args) {
233202

234203
// Solve integer programming problem
235204
System.out.println("---- Running integer programming optimizer ----");
236-
setupSolver();
237-
runSolver();
205+
try {
206+
setupSolver();
207+
runSolver();
208+
} catch(GRBException e) {
209+
System.out.println("Error code: " + e.getErrorCode() + ". " +
210+
e.getMessage());
211+
}
238212
}
239213

240214
}

0 commit comments

Comments
 (0)