A first look at Google or-tools' Java interface
The Java interface for Google or-tools was released some week ago, and I have now ported some of my Python models to Java (about 35 models). See my
Google CP Solver page / Java models for a list of them. Some of them has also been committed to the SVN repository of the or-tools project.
In A first look at Google CP Solver/Python (Google or-tools) I described the Python interface for Google or-tools. Since much of what is written there is applicable with the Java interface as well, I here concentrate on the differences between these two versions.
The porting was surprisingly easy to do for most models, since all the work of looking up the proper API call to use was already done for the Python models. See below for some of the differences.
I found very few unexpected things in the Java version. The Python interface (being slightly older) had some bugs when I first tested it, but they where fixed very fast by Laurent Perron and his colleagues.
For example: To get the result of two numbers (
Python also has niftier shortcuts for creating temporary arrays using array comprehensions. For example in the Python version of set_covering.py one can write the following:
Another example is the decomposition of the global constraint
I wondered if the Python version nqueens3.py that use the same constraints and labeling as the Java version should have any different result. Well, it has exactly the same failures and branches as the Java version (no surprise since it use the same solver engine). Also the wall times and memory usage are almost identical.
Maybe (repeat maybe) I will also have a take on Google or-tools' C++ interface.
In A first look at Google CP Solver/Python (Google or-tools) I described the Python interface for Google or-tools. Since much of what is written there is applicable with the Java interface as well, I here concentrate on the differences between these two versions.
The porting was surprisingly easy to do for most models, since all the work of looking up the proper API call to use was already done for the Python models. See below for some of the differences.
I found very few unexpected things in the Java version. The Python interface (being slightly older) had some bugs when I first tested it, but they where fixed very fast by Laurent Perron and his colleagues.
Example: SEND + MORE = MONEY
One difference between the Java interface and the Python interface is that the Python interface has some more syntactic sugar, e.g. it recognizes the arithmetic operators (+, -, /, *
), and (dis)equality: ==
, !=
. The Java interface has none of this which caused me some pain to translate.
For example: To get the result of two numbers (
a + b == c
) in Python:
solver.Add(a + b == c);In the Java interface one has to do use two methods (
addSum
and makeEquality
):
solver.addConstraint(solver.makeEquality(solver.makeSum(a, b), c));Here is a table of corresponding operations:
Python Java ---------------------------- a + b makeSum(a, b) a * b makeProd(a, b) a - b makeDifference(a, b) - a makeOpposite(a) a / b makeDiv(a, b) a == b makeEquality(a, b) a != b makeDifference(a, b)Example: the usual core of the SEND + MORE == MONEY problem can be written in a Python model (and similar in other constraint solver systems):
# define variables etc # ... # solver.Add( s*1000 + e*100 + n*10 + d + m*1000 + o*100 + r*10 + e == m*10000 + o*1000 + n*100 + e*10 + yHowever, using this approach is not really doable with the Java interface, since it would require a lot of
makeSum
and makeProd
, etc. A better way (and this is probably a general advice for the Python interface as well) is to instead use a scalar product: makeScalProd
or makeScalProdEquality
. Here is one way, where we also see how the decision variables is declared.
Solver solver = new Solver("SendMoreMoney"); int base = 10; IntVar s = solver.makeIntVar(0, base - 1, "s"); IntVar e = solver.makeIntVar(0, base - 1, "e"); IntVar n = solver.makeIntVar(0, base - 1, "n"); IntVar d = solver.makeIntVar(0, base - 1, "d"); IntVar m = solver.makeIntVar(0, base - 1, "m"); IntVar o = solver.makeIntVar(0, base - 1, "o"); IntVar r = solver.makeIntVar(0, base - 1, "r"); IntVar y = solver.makeIntVar(0, base - 1, "y"); IntVar[] x = {s,e,n,d,m,o,r,y}; IntVar[] eq = {s,e,n,d, m,o,r,e, m,o,n,e,y}; int[] coeffs = {1000, 100, 10, 1, // S E N D + 1000, 100, 10, 1, // M O R E -10000, -1000, -100, -10, -1 // == M O N E Y }; solver.addConstraint( solver.makeScalProdEquality(eq, coeffs, 0)); // s > 0 solver.addConstraint( solver.makeGreater(s, 0)); // m > 0 solver.addConstraint( solver.makeGreater(m, 0)); // all different solver.addConstraint( solver.makeAllDifferent(x, true));This approach, using scalar products, is implemented in SendMoreMoney.java. In SendMoreMoney2.java I experimented with some different approaches of coding this problem, some quite hairy.
Search, labeling, output
There is not really much difference how to do search and labeling in the Java version compared to the Python version. Here is the search section of SendMoreMoney.java:DecisionBuilder db = solver.makePhase(x, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT); solver.newSearch(db); while (solver.nextSolution()) { for(int i = 0; i < 8; i++) { System.out.print(x[i].toString() + " "); } System.out.println(); } solver.endSearch(); // Statistics System.out.println(); System.out.println("Solutions: " + solver.solutions()); System.out.println("Failures: " + solver.failures()); System.out.println("Branches: " + solver.branches()); System.out.println("Wall time: " + solver.wall_time() + "ms");One thing that is different, is how to define the variables for labeling. In Python it is quite easy to concatenate two arrays of decision variables like this:
solver.makePhase(x + y, ...);But this don't work as easy in Java (being Java). I tend to call such a labeling array for
all
and fill in all the IntVar
from
x
and y
; there are other way of doing this of course.
On the other hand, the Python interface cannot handle labeling on matrices
(multidimensional tuples), and in these models I also had to rely on a
flattened "view" of this matrix (often called x_flat
or something like that).
Python also has niftier shortcuts for creating temporary arrays using array comprehensions. For example in the Python version of set_covering.py one can write the following:
# ensure that all cities are covered for i in range(num_cities): b = [x[j] for j in range(num_cities) if distance[i][j] <= min_distance] solver.Add(solver.SumGreaterOrEqual(b, 1))This I have translated to Java code using an
ArrayList
instead (SetCovering.java):
// ensure that all cities are covered for(int i = 0; i < num_cities; i++) { ArrayListb = new ArrayList (); for(int j = 0; j < num_cities; j++) { if (distance[i][j] <= min_distance) { b.add(x[j]); } } solver.addConstraint( solver.makeSumGreaterOrEqual(b.toArray(new IntVar[1]), 1)); }
Decompositions, and somewhat under the hood
As far as I know, there is not (yet) support for modulo operations with decision variables (IntVar
's). Since modulo is a favorite operator of mine, I implemented a version in DivisibleBy9Through1.java which corresponds quite closely to the Python version divisible_by_9_through_1.py. Here is the Python version:
def my_mod(solver, x, y, r): if not isinstance(y, int): solver.Add(y != 0) lbx = x.Min() ubx = x.Max() ubx_neg = -ubx lbx_neg = -lbx min_x = min(lbx, ubx_neg) max_x = max(ubx, lbx_neg) d = solver.IntVar(min_x, max_x, 'd') if not isinstance(r, int): solver.Add(r >= 0) solver.Add(x*r >= 0) if not isinstance(r, int) and not isinstance(r, int): solver.Add(-abs(y) < r) solver.Add(r < abs(y)) solver.Add(min_x <= d) solver.Add(d <= max_x) solver.Add(x == y*d+r)The Java version is not very different, however I simplified it somewhat and requires that all arguments are
IntVar
's:
public static void my_mod(Solver solver, IntVar x, IntVar y, IntVar r) { long lbx = x.min(); long ubx = x.max(); long ubx_neg = -ubx; long lbx_neg = -lbx; int min_x = (int)Math.min(lbx, ubx_neg); int max_x = (int)Math.max(ubx, lbx_neg); IntVar d = solver.makeIntVar(min_x, max_x, "d"); // r >= 0 solver.addConstraint( solver.makeGreaterOrEqual(r,0)); // x*r >= 0 solver.addConstraint( solver.makeGreaterOrEqual( solver.makeProd(x,r).Var(), 0)); // -abs(y) < r solver.addConstraint( solver.makeLess( solver.makeOpposite( solver.makeAbs(y).Var()).Var(), r)); // r < abs(y) solver.addConstraint( solver.makeLess(r, solver.makeAbs(y).Var().Var())); // min_x <= d, i.e. d > min_x solver.addConstraint( solver.makeGreater(d, min_x)); // d <= max_x solver.addConstraint( solver.makeLessOrEqual(d, max_x)); // x == y*d+r solver.addConstraint( solver.makeEquality(x, solver.makeSum( solver.makeProd(y,d).Var(),r).Var())); }Note: This implementation is based on the ECLiPSe version mentioned in A Modulo propagator for ECLiPSE. Here is the ECLiPSe Prolog source code: modulo_propagator.ecl.
Another example is the decomposition of the global constraint
circuit
(from Circuit model) based on some observations about the "orbit" of x
.
public static void circuit(Solver solver, IntVar[] x) { int n = x.length; IntVar[] z = solver.makeIntVarArray(n, 0, n - 1, "z"); solver.addConstraint( solver.makeAllDifferent(x, true)); solver.addConstraint( solver.makeAllDifferent(z, true)); // put the orbit of x[0] in z[0..n-1] solver.addConstraint( solver.makeEquality(z[0], x[0])); for(int i = 1; i < n-1; i++) { solver.addConstraint( solver.makeEquality(z[i], solver.makeElement(x, z[i-1]).Var())); } // z may not be 0 for i < n-1 for(int i = 1; i < n - 1; i++) { solver.addConstraint( solver.makeNonEquality(z[i], 0)); } // when i = n-1 it must be 0 solver.addConstraint( solver.makeEquality(z[n - 1], 0)); }
Some benchmark: n-queens
Below is some statistics for the the n-queens problem, implemented in NQueens2 model. The constraint section is:IntVar[] q = solver.makeIntVarArray(n, 0, n-1, "q"); solver.addConstraint(solver.makeAllDifferent(q, true)); IntVar[] q1 = new IntVar[n]; IntVar[] q2 = new IntVar[n]; for(int i = 0; i < n; i++) { q1[i] = solver.makeSum(q[i], i).Var(); q2[i] = solver.makeSum(q[i], -i).Var(); } solver.addConstraint( solver.makeAllDifferent(q1, true)); solver.addConstraint( solver.makeAllDifferent(q2, true));Here is the timings (for one solution):
N Failures Branches Wall time Required memory ------------------------------------------------------- 8 3 9 2ms 10 5 16 3ms 50 15 70 4ms 100 12 120 10ms 200 1 198 22ms 500 2 496 126ms 501 6 503 128ms 1000 0 996 504ms 2000 0 1995 2164ms 3000 2 3001 5119ms 4000 0 3999 9493ms 5000 1 4994 15803ms 6000 1 5996 24511ms 7000 0 6997 34354ms 1.1Gb 8000 0 7996 46985ms 1.5Gb 9000 0 8996 62905ms 1.8Gb 10000 9 9999 81312ms 2.3GbI ran these tests on a Linux Ubuntu 10.4 Lynx, with 12Gb RAM, 8 cores, (Intel Core i7 930 @ 2.80GHz). Note: the "Wall time" is the time reported by the solver, and the "Required memory" is manually via the "top" command for the running process.
I wondered if the Python version nqueens3.py that use the same constraints and labeling as the Java version should have any different result. Well, it has exactly the same failures and branches as the Java version (no surprise since it use the same solver engine). Also the wall times and memory usage are almost identical.
Related
See also these blog posts, all about the Python interface:- A first look at Google CP Solver/Python (Google or-tools)
- Improvements of some Google CP Solver models
- Some new Google CP Solver/Python models
- Google CP Solver: Regular constraint, Nonogram solver, nurse rostering, etc
- Google CP Solver: A much faster Nonogram solver using DefaultSearch
Some kind of conclusion
As mentioned above, it was quite easy to port the Python models to Java since they share a lot of common API (with some difference in details). If there is no requirement about the language to use, I personally would probably prefer the Python interface since it's more high level and is less verbose than the Java interface (I like brevity).Maybe (repeat maybe) I will also have a take on Google or-tools' C++ interface.
My Google or-tools Java models
Here are my Java models. All of them where ported from existing Python models. They are also available at Google CP Solver page / Java models (the Python models is available from the same page). For implementation of these problems in other constraint programming systems, see Common constraint programming problems.- AllDifferentExcept0.java: A decomposition of the global constraint
alldifferent_except_0
- AllInterval.java: All interval problem (CSPLib #7)
- Circuit.java: A decomposition of the global constraint
circuit
- CoinsGrid.java: Tony Hurlimann's coin grid problem
- CoveringOpl.java: Set covering (OPL)
- Crossword.java: Simple crossword problem
- DeBruijn.java: "Arbitrary" de Bruijn sequences
- Diet.java: Simple diet optimization problem
- DivisibleBy9Through1.java: Divisible by 9 through 1 for a "truncated" number. Includes a
modulo
decomposition. - LeastDiff.java: Least difference problem (minimize the difference of ABCDE-FGHIJ, where all digits A..J are different)
- MagicSquare.java: Magic Square
- Map.java: Simple map coloring problem
- Map2.java: Simple map coloring problem, alternative version
- Minesweeper.java: Minesweeper solver (uses the same data file as for the Python model minesweeper.py, see above)
- NQueens.java: n-queens problem
- NQueens2.java: n-queens problem, faster version
- QuasigroupCompletion.java: Quasigroup completion (uses the same data file as for the Python model quasigroup_completion.py, see above)
- SendMoreMoney.java: Solve SEND+MORE=MONEY
- SendMoreMoney2.java: Solve SEND+MORE=MONEY (5 different encodings).
- SendMostMoney.java: Solve SEND+MOST=MONEY, and show all solutions with maximal value of MONEY.
- Seseman.java: Seseman convent problem.
- SetCovering.java: Set covering, placing of firestations (Winston)
- SetCovering2.java: Set covering, number of security telephons on a campus (Taha)
- SetCovering3.java: Set covering, senators making a committe (Murty)
- SetCovering4.java: Set covering (and set partiton), work scheduling (Lundgren, Roennqvist, Vaerbrand "Optimeringslaera")
- SetCoveringDeployment.java: Set covering deployment
- StableMarriage.java: Stable Marriage problem (from Van Hentenryck's OPL book and more problem instances)
- Strimko2.java: Strimko puzzle (Latin squares with "streams").
- Sudoku.java: Sudoku
- SurvoPuzzle.java: Survo puzzle (uses the same data file as for the Python model survo_puzzle.py, see above)
- ToNum.java: Converts inter to/from an array of digits
- WhoKilledAgatha.java: Who killed agatha? (The Dreadsbury Mansion Murder Mystery, an automated reasoning problem)
- Xkcd.java: Xkcd knapsack problem.
- YoungTableaux.java: Young tableaux and partitions