" /> My Constraint Programming Blog: November 2010 Archives

« October 2010 | Main | December 2010 »

November 30, 2010

JaCoP version 3.0 (final) is released

JaCoP version 3.0 (final) is released. Download via Sourceforge. From the release message:

Dear users,

We have just released JaCoP 3.0 final. Since the previous release (RC2) we have fixed one rarely triggering bug in ElementInteger constraint, as well as added code supporting generation of data for CP-viz framework. Here is the list of most important changes since version 2.4.

1) The introduction of Network Flow constraint, which allows efficient modeling of problems with network flow structure. The constraint allows to specify minimum cost flow problem in terms of finite domain variables. It uses network simplex algorithm as an propagator for network-like structure. It is a very expressive constraint where arc flow and arc unit costs can be specified by variables.

2) The introduction of set package forced changes in the design of core interfaces. There are IntVar and SetVar classes which inherit from Var class. This change allowed to refactor and improve set package so it is designed in cleaner and more efficient manner.

3) The introduction of special domain representation SmallDenseDomain, which uses bits within a long primitive type to encode small domains of range not larger than 64.

4) The introduction of Soft-alldifferent as well as Soft-GCC global constraints. The soft-alldifferent constraint implements Variable and Decomposition based violation metrics. The soft-gcc constraint implements value based violation metric. Both are decomposed constraints based on network flow constraint.

5) Examples have been updated by moving multiple solving functionality from main function to test function, so user can easily see what is the best model just by looking at main function. BIBD example has been added. Examples with Set variables have been updated to reflect the changes.

6) A number of bug fixes and changes in flatzinc interface to better comply with minizinc 1.1. We have also added into minizinc predicates networkflow that uses newly introduced JaCoP Network Flow constraint.

7) A number of minor API changes to improve intuitiveness of use (e.g. order of arguments in constructors).

8) The JaCoP guide has been updated to reflect the changes and additions to the newest version.

best regards,

Core Developer Team

Also see:

November 24, 2010

MiniZinc version 1.2.1 released

MiniZinc version 1.2.1 released. Download.

From the NEWS:

G12 MiniZinc Distribution 1.2.1
-------------------------------
Bugs fixed in this release:

* Flattening of expressions containing the built-in operation log/2 no longer causes mzn2fzn to abort.

* We have fixed a number of bugs in mzn2fzn and flatzinc that caused them to abort when processing large array literals.

* We have fixed a bug in mzn2fzn that caused it to generate variable declarations in which the variable was initialised with an assignment to itself.

* A bug that caused mzn2fzn to abort if it encountered of an empty array of Boolean, integer or float decision variables as a predicate application argument has been fixed. [Bug #187]

* Some bugs in mzn2fzn's optimisation pass that resulted in dangling variable references in the generated FlatZinc have been fixed.


November 14, 2010

MiniZinc version 1.2: More about CP-Viz and some models changed

In MiniZinc version 1.2 released I cited the news in MiniZinc version 1.2. Here I describe little more about the support for CP-Viz (visualization MiniZinc models), and also my MiniZinc models that was changed to comply with this version.

CP-Viz

Ever since I watched Helmut Simonis' excellent ECLiPSe ELearning videos some year ago, I have wanted to been able to play with the kind of visualizations shown in these videos: i.e. that shows both the search tree and - above all - how/when the variables where assigned/removed from domains etc.

MiniZinc version 1.2 has now (a limited) support for CP-Viz, a Java program supporting different methods of visualization constraint programming models. CP-Viz is presented in the paper A Generic Visualization Platform for CP by Helmut Simonis, Paul Davern, Jacob Feldman, Deepak Mehta, Luis Quesada, and Mats Carlsson. Slides from the CP-Viz presentation at CP 2010.

Here is a visualization of 8-queens problem (requires that the web browser has support for SVG files), using an array of 8 variables (the rows), each having of domain 1..8 (columns). To see the progress, click either on Forward a couple of times manually, or Animate for a nice animation.

The last event of this progress is shown in the picture below (it's a screen shot, click on it to get a larger version). The left pane shows the search tree, and the right pane is the grid of variables and their assigned/removed/etc values. Explanation of the colors is below (type vector).



Here is the MiniZinc model used for the visualization, queens_viz.mzn, slightly edited:
include "globals.mzn";
int: n = 8;
array[1..n] of var 1..n :: is_output: queens :: viz([
                                              viztype("vector"),
                                              vizpos(0,2),
                                              vizdisplay("expanded"),
                                              vizwidth(n),
                                              vizheight(n),
                                              vizrange(1,n)
                                              ]);

solve :: int_search(
        queens, 
        first_fail, 
        indomain_median, 
        complete) 
    satisfy;

constraint
    forall(i, j in 1..n where i < j) (
         queens[i] != queens[j] /\
         queens[i] + i != queens[j] + j /\
         queens[i] - i != queens[j] - j
    );
output [ show(queens) ++ "\n" ];
Supported types
There is a couple of visualization types supported. Descriptions from Mark Brown Visualizing MiniZinc models with CP-Viz Version 1.2 (file doc/pdf/mzn-viz.pdf in the distribution):
  • vector
    This is the type shown above.
    Shows a grid with one column per variable and one row per domain value. The variable (column) selected at the current search node is enclosed in a rectangle. Squares are color coded as follows:
    • red: failed or assigned value
    • pale green: value in domain
    • dark cyan: value just removed from domain
    • white: value earlier removed from domain
  • vector_waterfall
    Shows a grid with one column per variable and one row per search level.
  • vector_size
    Shows a graph of domain size versus depth for the current derivation.
  • binary_vector
    Shows a row with one square per variable
  • alldifferent
    This visualization type is used in the same way as vector.
Known limitations
The last section in Visualizing MiniZinc models with CP-Viz Version 1.2 describes the current known limitations:
The following limitations of minizinc-viz will be addressed in future releases.
  • Only the fd backend is supported.
  • Not many visualization types are supported.
  • MiniZinc should choose sensible default values for the optional parameters.
  • Not much error checking is currently performed. Bad annotations may be silently ignored, or may lead to bad input being given to CP-Viz. MiniZinc does not try to validate the XML it generates.
I'll wait eagerly for further support on this...

Changed models

For some change in version 1.2, I had to change my existing MiniZinc models. The changes varied, but here are the most common (I also did some other small fixes):
  • Changed global_cardinality/2 to global_cardinality_old
    Although global_cardinality_old is deprecated, I choose that since right now only the MiniZinc's solvers supports the new global_cardinality/3 version. When other FlatZinc solvers support the new version I will change to that.
  • limit to limitx
    limit is an annotation now.
  • time to timex
    time is an annotation now.
These models have also been updated at the G12 SVN repository www.g12.cs.mu.oz.au/mzn/hakank/.

November 12, 2010

MiniZinc version 1.2 released

MiniZinc version 1.2 has been released (download). From NEWS:


G12 MiniZinc Distribution 1.2
-----------------------------
* CP-Viz support

We have added support for visualizing MiniZinc models using CP-Viz to
the FlatZinc interpreter's FD backend.

See the ``Visualizing MiniZinc models with CP-Viz'' guide in the doc
directory for further details.


* New MiniZinc tutorial

We have added a new MiniZinc tutorial. It introduces the MiniZinc language
in much greater depth than the old tutorial and includes chapters on
predicates, search, and effective modelling practices.


* XML-FlatZinc redesigned

We have redesigned the XML representation of FlatZinc. The new version
is much less verbose than previous version of XML-FlatZinc. The conversion
tools, fzn2xml and xml2fzn, have been updated to work with new version.

Note that the new version of XML-FlatZinc is *not* compatible with previous
versions of XML-FlatZinc.


* FlatZinc to XCSP converter

We have added a new tool, fzn2xcsp, that converts FlatZinc model instances
into XCSP 2.1 format. The MiniZinc globals library contains a new set of
solver-specific constraints in the directory "xcsp" for use with models that
are going to be converted into XCSP.


Changes to the MiniZinc language:

* The following built-in operation has been removed from MiniZinc:

int: dom_size(array[$T] of var int)


Changes to the FlatZinc language:

* The following built-in constraints have been removed from FlatZinc:

bool_clause_reif/3
bool_ge/2
bool_ge_reif/2
bool_gt/2
bool_gt_reif/2
bool_left_imp/3
bool_right_imp/3
bool_ne/2
bool_ne_reif/3

float_ge/2
float_ge_reif/3
float_gt/2
float_gt_reif/3
float_lin_ge/3
float_lin_ge_reif/4
float_lin_gt/3
float_lin_gt_reif/4
float_minus/2
float_negate/2

int_ge/2
int_ge_reif/3
int_gt/2
int_gt_reif/3
int_lin_ge/3
int_lin_ge_reif/4
int_lin_gt/3
int_lin_gt_reif/4
int_lin_lt/3
int_lin_lt_reif/4
int_minus/3
int_negate/2

set_ge/2
set_ge_reif/3
set_gt/2
set_gt_reif/3
set_superset/2
set_superset_reif/3

* Constrained type-insts for parameters are no longer supported in FlatZinc.
For example, the following parameter declarations are no longer allowed:

1..10: p = 4;
1.0..10.0: f = 5.0;
set of {2, 5, 6} = {2, 5};
array[1..2] of set of 1..5 = [{}, {3}];

Constrained type-insts may still appear in variable declarations and
also as the argument types in predicate declarations.


Changes to the G12 MiniZinc-to-FlatZinc converter:

* We have added a FlatZinc optimisation pass to mzn2fzn. This pass is
enabled by default. Turning the optimiser off (see the '--no-optimise'
option) results in faster conversion, but may leave certain obvious
simplifications for the backend to handle. In particular, unoptimised
FlatZinc models are likely to contain many intermediate variables with
known values.

* mzn2fzn supports a new command line option that allows model data to
be specified directly on the command line. The new option is
'--cmdline-data', or '-D 'for short. An example of its use is:

mzn2fzn -D "n = 4;" queens.mzn

The above causes the parameter assignment "n = 4;" to be included
when flattening queens.mzn.


Changes to the G12 MiniZinc interpreter:

* The deprecated 1-pass MiniZinc interpreter, minizinc, has been removed from
the distribution.


Changes to the G12 FlatZinc interpreter:

* "indomain_random" is now supported as a value choice method for integer
search in the FD backend.

* We have significantly improved the worst-case complexity of the element
constraint in G12/FD.


Other changes in this release:

* The problems from the 2010 MiniZinc challenge are now included in the
MiniZinc benchmark suite.

* The following new global constraints have been added to the MiniZinc
library:

bin_packing
bin_packing_capa
bin_packing_load

* We have modified the interface to the global_cardinality constraint
so that it conforms more closely to the description in the Global
Constraint Catalog. The new interface is:

global_cardinality(array[int] of var int: x,
array[int] of int: cover,
array[int] of var int: counts);

The old definition of the global_cardinality constraint is still
available under the name global_cardinality_old, but it is now
deprecated and will be removed in a future release.

* We have added "closed" versions of the global_cardinality and
global_cardinality_low_up constraints. In the closed versions
the decision variables are restricted to taking their values from
the cover. The closed forms are named:

global_cardinality_closed
global_cardinality_low_up_closed

Bugs fixed in this release:

* Flattening of expressions containing the built-in operation dom_size/1
is now supported. [Bug #158]

* A bug that caused mzn2fzn to erroneously report that model was inconsistent
if the condition of an if-then-else was false has been fixed. [Bug #158]

* Output annotations are now attached to decision variables that only occur
in the output expression in the where clause of a comprehension. [Bug #160]

* mzn2fzn now outputs all parameter declarations before any variable
declarations, as the FlatZinc specification requires.

* A bug that caused mzn2fzn to erroneously treat assignments to string
parameters as a source of model unsatisfiability has been fixed. [Bug #170]

* The FlatZinc interpreter now emits an error if overloaded predicate
declarations are encountered.

Note: There are a few changes in this version which may break existing model, for example that
global_cardinality now has 3 arguments instead of 2 (one may use global_cardinality_old instead, but it is deprecated). During the next days I will update my MiniZinc models to comply to this version.

November 11, 2010

Google CP Solver: A much faster Nonogram solver using DefaultSearch

This is an extension of the comparison done in Comparison of some Nonogram solvers: Google CP Solver vs Gecode and two MiniZinc solvers with includes a new and much faster variant in Google CP Solver.

The day after the above blog post was written, Laurent Perron and I discussed alternative ways of solving these Nonogram problems. Laurent suggested another solving strategy: DefaultSearch (solver.DefaultPhase) which gave excellent overall result. Then I did some more experiment and tweaking which gave the results shown in the table below.

The new Nonogram model

Model: nonogram_default_search.py

The changes compared to the previous version nonogram_table2.py was very small in the Python program (there where also some changes in the C++ code). Here are the changes:
parameters = pywrapcp.DefaultPhaseParameters()
parameters.heuristic_period = 200000
db = solver.DefaultPhase(board_label, parameters)  

DefaultSearch

To be honest, I am not exactly sure why DefaultSearch is so fast for the Nonogram problems. It is a variant of Impact-Based Search (see Philippe Refalo: Impact-Based Search Strategies for Constraint Programming, 2004) combined with periodic heuristics. The source code is here: constraint_solver/default_search.cc.

heuristic_period

The value of 200000 for parameters.heuristic_period was found by experimenting. For quite long I used the value of 11000 and later found that even though increasing this value didn't do much difference for the simpler problems (same time and same number of failures), for the harder problems - such as Lion, Marley, and Nature - the number of failures decreased and the time was improved a bit. Larger values above that, such as 2000000, decreased the number of failures somewhat for the Nature problem, but the time seemed to be slightly worse. So I settled with 200000.

This may or may not be a good value for general Nonogram problems. Doing this kind of testing on a limited - albeit mixed - problem set may have biased the search in some unfortunate way.

After deciding on the above heuristics, I finally tested (as a verification set) with some other problem instances and found that they still was easy solved:
  • P200: 0.25s / 1637 failures
  • P199: 0.06s / 242 failures
  • Dragonfly: 0.03s / 0 failures
P200 was slightly worse in this version: the previous version solved it in 0.14s and with 520 failures. The other two problem was better or about the same with the current version.

As an experiment, I changed the order of variables to board_flat (instead of board_label), then Lion was solved in 2 seconds (compared to 4.45 seconds) and Marley in 11 seconds (compared to 7:18 minutes). On the down side, 9-Dom then took 9 seconds (compared to 3.5 seconds) and Nature timed out (compared to 20:25 minutes), and other problems took slightly longer time. So this is probably not a good way to go.

I also experimented with restarts (which is reported to work well with Impact-Based Search), and found some initial promising results. I may come back to this later.

I should also note that the current model is quite different from my first model nonogram_regular.py presented in Google CP Solver: Regular constraint, Nonogram solver, nurse rostering, etc. Laurent has done a great job of optimizing all sort of things, both in the Python code and in the C++ code. For example, he added the "regular like" constraint solver.TransitionConstraint which obsoleted my decomposition of regular for this problem.

A comment on the results

None of the Nonogram solvers that Jan Wolter had tested in his excellent Survey of Paint-by-Number Puzzle Solvers has solved all the problem instances under the 30 minutes time limit. The table below shows that the DefaultSearch variant solves all the problems. At least on my computer, and hopefully also on Wolter's benchmark computer if he want to test this solver. All problems - except Marley and Nature - where solved well under 10 seconds, including the infamous Lion problem (4.46s).

In the previous version of the Nonogram solver a couple of the problems timed out, marked + in the table. Some of these, such as Hot and Light, is now solved under 2 seconds. All these "remarkable" improvements in bold.

Test machine

The machine used for the test is the same as in the former test: Linux Ubuntu 10.4 LTS with 12Gb RAM, 8 core (Intel i7/930, 2.80GHz). All programs ran on a single processor. Python version 2.6. Google CP Solver used was revision 276.

Comparison - table

Here is the full table with the new version (DefaultSearch) in the last column. The number in parenthesis is Wolter's times. The second row in each entry is my own timing. The number of failures where applicable; LazyFD always returned 0 choice points so these are skipped. A + indicates time out (30 minutes).

The links for the puzzle is to Wolter's puzzle pages.
Puzzle Size Notes Gecode MiniZinc
Gecode/fz
MiniZinc
LazyFD
Google
CP Solver
Google
CP Solver
DefaultSearch
#1: Dancer* 5 x 10 Line (0.01s)
0.01s/0 failures
(0,01s)
0.08s/0 failures
(0.04s)
0.1s
0.02s
0 failures
0.02s
0 failures
#6: Cat* 20 x 20 Line (0.01s)
0.1s/0 failures
(0.02s)
0.1s/0 failures
(0.44s)
0.95s
0.12s
0 failures
0.03s
0 failures
#21: Skid* 14 x 25 Line, Blanks (0.01s)
0.01s/0 failures
(0,02s)
0.09s/13 failures
(0.64s)
0.9s
0.03s
0 failures
0.03s
0 failures
#27: Bucks* 27 x 23 Blanks (0.01s)
0.2s/2 failures
(0.02s)
0.1s/3 failures
(1.1s)
1.6s
0.05s
3 failures
0.04s
6 failures
#23: Edge* 10 x 11   (0.01s)
0.01s/15 failures
(0.01s)
0.09s/25 failures
(0.08s)
0.16s
0.03s
25 failures
0.03s
58 failures
#2413: Smoke 20 x 20   (0.01s)
N/A
(0.02s)
0.11s/5 failures
(0.60s)
1.18s
0.05s
8 failures
0.04s
20 failures
#16: Knot* 34 x 34 Line (0.01s)
0.01s/0 failures
(0.02s)
0.14s/0 failures
(5.5s)
5.6s
0.12s
0 failures
0.06s
0 failures
#529: Swing* 45 x 45 Line (0.02s)
0.02s/0 failures
(0.04s)
0.24s/0 failures
(13.2s)
22.3s
0.3s
0 failures
0.11s
0 failures
#65: Mum* 34 x 40   (0.02s)
0.02s/17
(0.04s)
0.18s/20 failures
(11.0s)
12.4s
0.15s
22 failures
0.10s
46 failures
#7604: DiCap 52 x 63   (0.02s)
N/A
(0.06s)
0.29s/0 failures
(+)
1:48m
0.45s
0 failures
0.18s
0 failures
#1694: Tragic 45 x 50   (0.14s)
N/A
(3.6m)
2:14m/394841 failures
(32.1s)
34.57s
2:33m
394841 failures
0.35s
1862 failures
#1611: Merka* 55 x 60 Blanks (0.03s)
N/A
(+)
+
(1.1m)
1:10m
0.47s
27 failures
0.20s
96 failures
#436: Petro* 40 x 35   (1.4m[s?])
0.05s/48 failures
(1.6s)
1.15s/1738 failures
(6.2s)
9.3s
1.19s
1738 failures
0.245s
770 failures
#4645: M&M 50 x 70 Blanks (0.93s)
N/A
(0.28s)
0.41s/89 failures
(48.1s)
1:14m
0.48s
82 failures
0.46s
376 failures
#3541: Signed 60 x 50   (0.57s)
N/A
(0.56s)
0.61s/929 failures
(1.0m)
1:02m
11.7s
6484 failures
0.92s
3821 failures
#803: Light* 50 x 45 Blanks (+)
N/A
(+)
+
(4.7s)
18.32s
+ 1.5s
4052 failures
#6574: Forever* 25 x 25   (4.7s)
N/A
(2.3s)
1.5s/17143 failures
(1,7s)
1.99s
1.6s
17143 failures
0.26s
2421 failures
#2040: Hot 55 x 60   (+)
N/A
(+)
+
(2.6m)
1:05m
+ 0.62s
2394 failures
#6739: Karate 40 x 40 Multiple (56.0s)
N/A
(57.8s)
38.0s/215541 failures
(13.6s)
22.52s
56.1s
215541 failures
0.2s
1361 failures
#8098: 9-Dom* 19 x 19   (12.6m)
N/A
(3.8s)
2.59s/45226 failures
(1.8s)
3.11s
3.9s
45226 failures
3.54s
27650 failures
#2556: Flag 65 x 45 Multiple, Blanks (3.4s)
N/A
(+)
+
(4.2s)
16.18s
1.6s
14859 failures
0.15s
442 failures
#2712: Lion 47 x 47 Multiple (+)
N/
(+)
+
(+)
8:54m
+ 4.34s
15429 failures
#10088: Marley 52 x 63 Multiple (+)
N/A
(+)
+
(2.9m)
+
+ 7:18m
309293 failures
#9892: Nature 50 x 40 Multiple (+)
N/A
(+)
+
(2.4m)
25.29s
+ 20:25m
7743891 failures

November 08, 2010

Comparison of some Nonogram solvers: Google CP Solver vs Gecode and two MiniZinc solvers

After writing Google CP Solver: Regular constraint, Nonogram solver, nurse rostering, etc yesterday, I thought it would be interesting to benchmark the new Nonogram solver written in Google CP Solver with some other solvers. And the benchmark is of course from Jan Wolter's great Survey of Paint-by-Number Puzzle Solvers, though I compare only with Gecode, and two MiniZinc solvers: Gecode/fz (Gecode's FlatZinc solver), and MiniZinc's LazyFD since I know them best and can test them my self.

In the table below, I have kept Wolter's original value is in parentheses to get a feeling for the differences in our machines and to check with my missing problems with Gecode.

System notes:
  • The timeout is 30 minutes as in Wolter's test.
  • My machine is a Linux Ubuntu 10.4 LTS with 12Gb RAM, 8 core (Intel i7/930, 2.80GHz), though all programs ran on a single processor.
  • Also, I was "working" (Spotify, web surfing, running other tests in parallel, etc) on the machine as the test ran.
  • Due to copyrights issues, I cannot distribute the examples. They can be downloaded from Wolter's Sample Paint-by-Number Puzzles Download.
And here are some comments about the different solvers.

Google CP Solver

Google CP Solver revision 259, with revision 259 of the Python solver nonogram_table2.py, which includes a lot of nice improvements, thanks by Laurent Perron.

Gecode

Gecode and Gecode/fz is version 3.4.2 (latest svn revision: 11517)
Please note that I have just tested the problem instances in the existing files for Gecode. Hence a lot of instances where not tested on my machine (they are marked N/A).

Also, I think that Wolter's time for Petro should be 1.4s (not 1.4m).

MiniZinc

MiniZinc version: 64bit Linux, snapshot version per 2010-11-05.

Note: In contrast to Wolter's result, the times for Gecode/fz and LazyFD includes generating the FlatZinc file, which may be considerable for large problem instances. Hence some of my results are larger for these solvers.

Some comments about Google CP Solver / Python solver

Wolter has done a great job analyzing the three other solvers (Gecode, Gecode/fz, and LazyFD), so I just comment on Google CP Solver solver.

It looks like Google CP Solver/Python solver is quite similar to Gecode/fz solver, and in some case (e.g. 9-Dom, Bucks, Tragic, Petro, etc) it has exactly the same number of failures. There are some exceptions, though:
  • Solved Merka and Dicap quite fast. Gecode/fz timed out for both these problems
  • Also solved Flag fast where Gecode/fz timed out. Here it is also faster than LazyFD and Gecode (note: Wolter's time).
  • Slower than Gecode/fz on Karate, Signed
  • Slightly slower than Gecode/fz on Tragic, with the same number of failures

Comparison - table

Here is the full table. The number in parenthesis is Wolter's times. The second row is my own timing. I also added the number of failures where applicable; LazyFD always returned 0 choice points so I skipped that. A + indicates time out (30 minutes).

The links for the puzzle is to Wolter's puzzle pages.
Puzzle Size Notes Gecode MiniZinc
Gecode/fz
MiniZinc
LazyFD
Google
CP Solver
#1: Dancer* 5 x 10 Line (0.01s)
0.01s/0 failures
(0,01s)
0.08s/0 failures
(0.04s)
0.1s
0.02s
0 failures
#6: Cat* 20 x 20 Line (0.01s)
0.1s/0 failures
(0.02s)
0.1s/0 failures
(0.44s)
0.95s
0.12s
0 failures
#21: Skid* 14 x 25 Line, Blanks (0.01s)
0.01s/0 failures
(0,02s)
0.09s/13 failures
(0.64s)
0.9s
0.03s
0 failures
#27: Bucks* 27 x 23 Blanks (0.01s)
0.2s/2 failures
(0.02s)
0.1s/3 failures
(1.1s)
1.6s
0.05s
3 failures
#23: Edge* 10 x 11   (0.01s)
0.01s/15 failures
(0.01s)
0.09s/25 failures
(0.08s)
0.16s
0.03s
25 failures
#2413: Smoke 20 x 20   (0.01s)
N/A
(0.02s)
0.11s/5 failures
(0.60s)
1.18s
0.05s
8 failures
#16: Knot* 34 x 34 Line (0.01s)
0.01s/0 failures
(0.02s)
0.14s/0 failures
(5.5s)
5.6s
0.12s
0 failures
#529: Swing* 45 x 45 Line (0.02s)
0.02s/0 failures
(0.04s)
0.24s/0 failures
(13.2s)
22.3s
0.3s
0 failures
#65: Mum* 34 x 40   (0.02s)
0.02s/17
(0.04s)
0.18s/20 failures
(11.0s)
12.4s
0.15s
22 failures
#7604: DiCap 52 x 63   (0.02s)
N/A
(0.06s)
0.29s/0 failures
(+)
1:48m
0.45s
0 failures
#1694: Tragic 45 x 50   (0.14s)
N/A
(3.6m)
2:14m/394841 failures
(32.1s)
34.57s
2:33m
394841 failures
#1611: Merka* 55 x 60 Blanks (0.03s)
N/A
(+)
+
(1.1m)
1:10m
0.47s
27 failures
#436: Petro* 40 x 35   (1.4m[s?])
0.05s/48 failures
(1.6s)
1.15s/1738 failures
(6.2s)
9.3s
1.19s
1738 failures
#4645: M&M 50 x 70 Blanks (0.93s)
N/A
(0.28s)
0.41s/89 failures
(48.1s)
1:14m
0.48s
82 failures
#3541: Signed 60 x 50   (0.57s)
N/A
(0.56s)
0.61s/929 failures
(1.0m)
1:02m
11.7s
6484 failures
#803: Light* 50 x 45 Blanks (+)
N/A
(+)
+
(4.7s)
18.32s
+
#6574: Forever* 25 x 25   (4.7s)
N/A
(2.3s)
1.5s/17143 failures
(1,7s)
1.99s
1.6s
17143 failures
#2040: Hot 55 x 60   (+)
N/A
(+)
+
(2.6m)
1:05m
+
#6739: Karate 40 x 40 Multiple (56.0s)
N/A
(57.8s)
38.0s/215541 failures
(13.6s)
22.52s
56.1s
215541 failures
#8098: 9-Dom* 19 x 19   (12.6m)
N/A
(3.8s)
2.59s/45226 failures
(1.8s)
3.11s
3.9s
45226 failures
#2556: Flag 65 x 45 Multiple, Blanks (3.4s)
N/A
(+)
+
(4.2s)
16.18s
1.6s
14859 failures
#2712: Lion 47 x 47 Multiple (+)
N/
(+)
+
(+)
8:54m
+
#10088: Marley 52 x 63 Multiple (+)
N/A
(+)
+
(2.9m)
+
+
#9892: Nature 50 x 40 Multiple (+)
N/A
(+)
+
(2.4m)
25.29s
+

November 07, 2010

Google CP Solver: Regular constraint, Nonogram solver, nurse rostering, etc

Models mentioned: After some sweat and confusions, I have now implemented a Nonogram solver in Google CP Solver using a (decomposition of) regular constraint. The model is here: nonogram_regular.py. Also, a test model for the regular constraint is in regular.py

The hardest part by far was to translate the regular constraint from Comet to Google CP solver. The Comet version (nonogram_regular.co) was described in a sequence of blog posts: As we will see below, other problems where implemented by the regular constraint.

Update: Laurent Perron suggested an improvement of the regular constraint, which makes some of the models much faster. I have commented this below.

Translation of regular

As mentioned above, the hardest part was the translation of the regular constraint from Comet (from nonogram_regular.co) to Google CP solver (the Comet version was a translated from the MiniZinc version). I started with the simpler example regular.py) which basically consists of two parts also included in the nonogram model:

make_transition_matrix

The function translates an array of numbers to an finite state automaton which representing and regular expression of 0's and 1's. E.g. the array [3,2,1] represents the regular expression 0*1{3}0+1{2}0+1{1}0* and is translated into the automaton (the valid states) which must start in state 1 and end in state 9 (0 is a failing state):
1 2
0 3
0 4
5 0
5 6
0 7
8 0
8 9
9 0
The hardest part here was that the Comet constraint is 1-based, but Python is 0-based. There are many things that can - and will - go wrong when translating from a 1-based system to a 0-base system. And it don't help that state 0 is used as a fail state. regular constraint

Then, given the automaton (the valid states), the regular constraint transforms the states into an array of decision variables. The domains are 1..2 (representing the values of 0 and 1).

Update Laurent Perron suggested an improvement using the table constraint (called solver.AllowedAssignments in Google CP Solver). I have changed to this version here.

This was his first improvement (last in the method):
#
# Global constraint regular
#
# This is a translation of MiniZinc's regular constraint (defined in
# lib/zinc/globals.mzn), via the Comet code refered above.
# All comments are from the MiniZinc code.
# '''
# The sequence of values in array 'x' (which must all be in the range 1..S)
# is accepted by the DFA of 'Q' states with input 1..S and transition
# function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'
# (which must be in 1..Q) and accepting states 'F' (which all must be in
# 1..Q).  We reserve state 0 to be an always failing state.
# '''
#
# x : IntVar array
# Q : number of states
# S : input_max
# d : transition matrix
# q0: initial state
# F : accepting states
def regular(x, Q, S, d, q0, F):

  solver = x[0].solver()

  assert Q > 0, 'regular: "Q" must be greater than zero'
  assert S > 0, 'regular: "S" must be greater than zero'

  # d2 is the same as d, except we add one extra transition for
  # each possible input;  each extra transition is from state zero
  # to state zero.  This allows us to continue even if we hit a
  # non-accepted input.

  d2 = []
  for i in range(Q + 1):
    for j in range(S):
      if i == 0:
        d2.append((0, j, 0))
      else:
        d2.append((i, j, d[i - 1][j]))

  # If x has index set m..n, then a[m-1] holds the initial state
  # (q0), and a[i+1] holds the state we're in after processing
  # x[i].  If a[n] is in F, then we succeed (ie. accept the
  # string).
  x_range = range(0, len(x))
  m = 0
  n = len(x)

  a = [solver.IntVar(0, Q + 1, 'a[%i]' % i) for i in range(m, n + 1)]

    # Check that the final state is in F
  solver.Add(solver.MemberCt(a[-1], F))
    # First state is q0
  solver.Add(a[m] == q0)
  for i in x_range:
    solver.Add(x[i] >= 1)
    solver.Add(x[i] <= S)
    # Determine a[i+1]: a[i+1] == d2[a[i], x[i]]
    # Laurent Perron suggested using this: 
    solver.Add(solver.AllowedAssignments((a[i], x[i] - 1, a[i + 1]), d2))
Update 2: Some hour later, Laurent Perron added C++ support for simplifying this by adding solver.TransitionConstraint, so the definition of regular is now much neater:
def regular(x, Q, S, d, q0, F):

  solver = x[0].solver()

  assert Q > 0, 'regular: "Q" must be greater than zero'
  assert S > 0, 'regular: "S" must be greater than zero'

  # d2 is the same as d, except we add one extra transition for
  # each possible input;  each extra transition is from state zero
  # to state zero.  This allows us to continue even if we hit a
  # non-accepted input.

  d2 = []
  for i in range(Q + 1):
    for j in range(1, S + 1):
      if i == 0:
        d2.append((0, j, 0))
      else:
        d2.append((i, j, d[i - 1][j - 1]))

  solver.Add(solver.TransitionConstraint(x, d2, q0, F))
This improvement is in the model: regular_table2.py and the Nonogram model: nonogram_table2.py

Nonogram

After some more problems was fixed it was really a simple matter of translating the rest of the Nonogram model (nonogram_regular.py).

Labeling: I has the same labeling, or as approximate as possible, as in the Comet model:
# This labeling was inspired by a suggestion from
# Pascal Van Hentenryck about my Comet nonogram model.
board_label = []
if rows * row_rule_len < cols * col_rule_len:
    for i in range(rows):
        for j in range(cols):
            board_label.append(board[i,j])
else:
    for j in range(cols):        
        for i in range(rows):
            board_label.append(board[i,j])
Update: Here are Laurent Perron's versions with the improved regular: nonogram_table.py and nonogram_table2.py

Testing and problem instances for Nonogram

I have compared two problem instances, p200 (nonogram_p200.py) and p199 (nonogram_p199.py), with some of my other Nonogram solver: the Comet model nonogram_regular.co (with my decomposition of regular), the MiniZinc model nonogram_create_automaton2.mzn with Gecode/fz, using Gecode's built in regular.

Update: I have added Laurent Perron's version using table (solver.AllowedAssignments) as an separate entry for comparison:

Problem Google CP Solver Google CP Solver with table Comet Gecode/fz
P200 1.8s/11615 failures 0.2s/520 failures 0.4s/794 failures 0.2s/520 failures
P199 17.607s/104239 failures 0.3s/772 failures 0.6s/1175 failures 0.1s/772 failures

The result for P200 is not too bad for Google CP Solver, but I am a little surprised by the large times/failures for the P199, almost 18 seconds. However, one could note that using a plain ordering of the variables for this problem instance, i.e.
board_label = [board[i,j] for i in range(rows) for j in range(cols)]
then P199 is solved in 0.4 seconds with 2005 failures. However, with this ordering, P200 takes very long time.

Update: It's interesting to see that the improved table version has exactly the same number of failures as the version using Gecode/fz.
Update 2:The second improvement of Laurent Perron didn't change the number of failures, but the time was slightly improved with a couple of ms's: 169ms vs 209ms for P200, and 264ms vs 296ms for P199.

Thanks for the improvements, Laurent!

Here are some other problem instances:

Nurse rostering

Model: nurse_rostering.py

A standard application of regular constraint is in different types of scheduling, e.g. nurse rostering. The model nurse_rostering.py is a small example based on the model in the MiniZinc Tutorial (PDF), page 35f (the graph of the DFA is shown at page 37).

The example is for 7 nurses and 14 days using three states: day shift, night shift, and day off. There are two kind of constraints:

* Rules using the regular constraint:
  • - one day off every 4 days
  • - no 3 nights in a row.
These are translated as the following transition function:
transition_fn =  [
   # d,n,o
    [2,3,1], # state 1
    [4,4,1], # state 2
    [4,5,1], # state 3 
    [6,6,1], # state 4
    [6,0,1], # state 5
    [0,0,1]  # state 6
    ]
Note: This transition function can be used for any number of nurses and days. * Other rules
Here I went wild and required some extra rules for this specific number of nurses and days:
  • Each nurse must work between 7 and 10 days during the period (14 days)
  • On weekends there must be 2 nurses on day shift, 1 on night shift, and 4 has day off
  • On working days: 3 nurses on day shift, 2 on night shift, and 2 has day off
Below is one solution, including statistics for each nurse and day, respectively:
Nurse0:  d d d o d d d o d d d o d o  day_stat: [('d', 10), ('o', 4)] total: 10 workdays
Nurse1:  d d d o d d d o d d d o d o  day_stat: [('d', 10), ('o', 4)] total: 10 workdays
Nurse2:  d d o d d n o d d d o d n o  day_stat: [('d', 8), ('o', 4), ('n', 2)] total: 10 workdays
Nurse3:  n n o d n o n d n o d d o d  day_stat: [('d', 5), ('o', 4), ('n', 5)] total: 10 workdays
Nurse4:  n o d n n o o d n n o n o o  day_stat: [('d', 2), ('o', 6), ('n', 6)] total: 8 workdays
Nurse5:  o n n d o o o n o o n n o d  day_stat: [('d', 2), ('o', 7), ('n', 5)] total: 7 workdays
Nurse6:  o o n n o o o n o n n d o n  day_stat: [('d', 1), ('o', 7), ('n', 6)] total: 7 workdays

Statistics per day:
        d n o
Day 0:  3 2 2
Day 1:  3 2 2
Day 2:  3 2 2
Day 3:  3 2 2
Day 4:  3 2 2
Day 5:  2 1 4
Day 6:  2 1 4
Day 7:  3 2 2
Day 8:  3 2 2
Day 9:  3 2 2
Day10:  3 2 2
Day11:  3 2 2
Day12:  2 1 4
Day13:  2 1 4

Solving 3 jugs problem using regular

Model: 3_jugs_regular.py
Model: 3_jugs_regular2.py

This is a model solving the classic 3 jugs problem (a.k.a. water jugs problem), based on Taha's network flow model (Introduction to Operations Research, page 245f) but using regular instead. Compare, for example, with the Comet model 3_jugs.co using a network flow approach.

The different possible states in the problem is represented as nodes, and we start at state 1 (node 1). The name of the nodes represent the amount of water in each water jug; e.g. 8,0,0 means that 8 liter/gallon is in the first jug, and 0 in both jug 2 and 3. The goal is then to go from state 8,0,0 to 4,4,0. Here are all the nodes:
nodes = [
    '8,0,0', # 1 start
    '5,0,3', # 2
    '5,3,0', # 3 
    '2,3,3', # 4 
    '2,5,1', # 5
    '7,0,1', # 6
    '7,1,0', # 7
    '4,1,3', # 8
    '3,5,0', # 9
    '3,2,3', # 10
    '6,2,0', # 11
    '6,0,2', # 12
    '1,5,2', # 13
    '1,4,3', # 14
    '4,4,0'  # 15 goal
    ]
The valid transitions between these states are as follows.
states = [
    [2,9],  # state 1
    [3],    # state 2
    [4, 9], # state 3
    [5],    # state 4
    [6,9],  # state 5
    [7],    # state 6
    [8,9],  # state 7
    [15],   # state 8
    [10],   # state 9
    [11],   # state 10
    [12],   # state 11
    [13],   # state 12
    [14],   # state 13
    [15]    # state 14
    ]
And from this adjacency lists we can build the DFA:
transition_fn =  [
   # 1  2  3  4  5  6  7  8  9  0  1  2  3  4  5 
    [0, 2, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0],  # 1
    [0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # 2
    [0, 0, 0, 4, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0],  # 3
    [0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # 4
    [0, 0, 0, 0, 0, 6, 0, 0, 9, 0, 0, 0, 0, 0, 0],  # 5
    [0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0],  # 6
    [0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 0, 0, 0],  # 7
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15], # 8
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0], # 9
    [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0], # 10
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0], # 11
    [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0], # 12
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0], # 13
    [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15], # 14
                                                    # 15
    ]
As you may see this is very much like the representation in the network flow model, but the connections is a state number instead of just 0/1 (connected/not connected).

Running this program gives the following solution where x contains the chosen states, and then the nodes of the states are shown.
x: [1, 9, 10, 11, 12, 13, 14, 15]
8,0,0 -> 3,5,0
3,5,0 -> 3,2,3
3,2,3 -> 6,2,0
6,2,0 -> 6,0,2
6,0,2 -> 1,5,2
1,5,2 -> 1,4,3
1,4,3 -> 4,4,0

num_solutions: 1
failures: 1
branches: 2
wall_time: 0 ms

Found a solution of length 8: [1, 9, 10, 11, 12, 13, 14, 15]
Note: This model don't have any explicit Minimization objective to minimize the number of states (which is implied in the problem). Instead it calls the main function with the length of the array x, ranging from length 1 to 15, and thus find the first, minimal, solution of length 8.

3 jugs problem, alternative solution

Model: 3_jugs_regular2.py

However, I was not satisfied with this calling of main repeatedly. Instead an alternative solution was created which use an extra (last) state which may be looped into (15->15->15...):
# ....
[15],   # state 14
[15]    # state 15: loop ("fix-point")
With this state we can fix the length of the array of decision variables (x) to 15, the number of states, and add an objective variable cost which is then minimized. This variable counts the number of elements in x that is not in the final state (state 15):
cost = solver.IntVar(0, input_max, 'cost')
# ...
sum_b = [solver.IsLessCstVar(x[i], input_max-1) for i in range(input_max)]
solver.Add(cost == solver.Sum(sum_b))

objective = solver.Minimize(cost, 1)

Global contiguity

Model: contiguity_regular.py

The definition of the global constraint global_contiguity from Global Constraint Catalog is:
Enforce all variables of the VARIABLES collection to be assigned to 0 or 1. In addition, all variables assigned to value 1 appear contiguously.

Example:
(<0, 1, 1, 0>)

The global_contiguity constraint holds since the sequence 0 1 1 0 contains no more than one group of contiguous 1.
The regular expression for this is ^0*1*0*$ (or maybe ^0*1+0*$ if there is an assumption that there must be at least one 1). The transition function is thus:
transition_fn =  [
    [1,2], # state 1: 0*
    [3,2], # state 2: 1* 
    [3,0], # state 3: 0* 
    ]
For an array of length 4 there are 11 solutions:
[0, 0, 0, 0]
[0, 0, 0, 1]
[0, 0, 1, 0]
[0, 0, 1, 1]
[0, 1, 0, 0]
[0, 1, 1, 0]
[0, 1, 1, 1]
[1, 0, 0, 0]
[1, 1, 0, 0]
[1, 1, 1, 0]
[1, 1, 1, 1]

num_solutions: 11
failures: 0
branches: 20
wall_time: 0 ms
For an array of length 100 there are 5051 solutions, still with 0 failures (and 10100 branches). It takes about 4 seconds to generate and print all solutions; without printing it takes 0.2 seconds.

Regular expressions

Model: regexp.py

The history behind this is that my last name (Kjellerstrand) is quite often misspelled, in ways according to this regular expression:
k(je|ä)ll(er|ar)?(st|b)r?an?d
Note: In Swedish the two constructs "kje(ll)" and "kä(ll)" sounds alike.

This model generates all the words that can be construed by this regular expression. As with the other models, we create a DFA. Note that the states are groups of characters (maybe it should be just single characters):
transition_fn =  [
   # 1 2 3 4 5 6 7 8 9 0 1 2     # 
    [0,2,3,0,0,0,0,0,0,0,0,0],   #  1 k 
    [0,0,0,4,0,0,0,0,0,0,0,0],   #  2 je
    [0,0,0,4,0,0,0,0,0,0,0,0],   #  3 ä
    [0,0,0,0,5,6,7,8,0,0,0,0],   #  4 ll
    [0,0,0,0,0,0,7,8,0,0,0,0],   #  5 er
    [0,0,0,0,0,0,7,8,0,0,0,0],   #  6 ar
    [0,0,0,0,0,0,0,0,9,10,0,0],  #  7 st 
    [0,0,0,0,0,0,0,0,9,10,0,0],  #  8 b
    [0,0,0,0,0,0,0,0,0,10,0,0],  #  9 r
    [0,0,0,0,0,0,0,0,0,0,11,12], # 10 a
    [0,0,0,0,0,0,0,0,0,0,0,12],  # 11 n
                                 # 12 d 
    ]

s = ['k','je','ä','ll','er','ar','st','b','r','a','n','d']
Running the program generates all the 48 variants of my last name. Though, I have to confess that I have never been called "Kjellbad", "Källbad" or "Kjellbrad". Yet.
kjellstad
kjellbad
källstad
källbad
kjellerstad
kjellerbad
kjellarstad
kjellarbad
kjellstrad
kjellstand
kjellbrad
kjellband
källerstad
källerbad
källarstad
källarbad
källstrad
källstand
källbrad
källband
kjellerstrad
kjellerstand
kjellerbrad
kjellerband
kjellarstrad
kjellarstand
kjellarbrad
kjellarband
kjellstrand
kjellbrand
källerstrad
källerstand
källerbrad
källerband
källarstrad
källarstand
källarbrad
källarband
källstrand
källbrand
kjellerstrand
kjellerbrand
kjellarstrand
kjellarbrand
källerstrand
källerbrand
källarstrand
källarbrand
Compare with the Gecode model all_regexp.cpp, and was described in Regular expressions in Gecode. Here I also describe my fascination with regular expressions.