" /> My Constraint Programming Blog: May 2009 Archives

« April 2009 | Main | June 2009 »

May 30, 2009

New constraint programming blog: Be-cool Blog

Be-cool Blog is a new blog about constraint programming (there are very few of these kind of blogs). It is written by the Belgian Constraints Group Be-cool @UCLouvain, Belgium, among them Jean-Noël Monette and Pierre Schaus.

The first blog post New Be-Cool Blog states

Upon popular request, here is the new blog of the Be-Cool team. This blog will give the team the opportunity to share its thoughts and ideas about Constraints and a lot of related topics.

The post Solving a Fifteen Puzzle with Lightsolver implements a '"technology-neutral' Solver" in Comet (available from Dynadec).

Good luck with the blog!

May 29, 2009

Global Constraint Catalog, update

I have completely missed to blog about the update of the great online Global Constraint Catalog. It was done 2009-04-03 and had the following changes:
  • gccat_systems.xml: references to global constraints implemented within the constraint systems Choco and Gecode. (For now, the references appear in this XML file only; they will soon be integrated within the catalog.)
  • gccat_schema.xml: the XML Schema of the catalog schema.xsd with a stylesheet
  • navigation bar (prev/next) added
  • google tools added: search and analytics
I really like the comparison of the constraint names in Choco and Gecode, it's very helpful. Maybe other systems will be added later, say JaCoP, MiniZinc and Comet?

MiniZinc Challenge 2009

From MiniZinc Challenge 2009:
The aim of the challenge is to start to compare various constraint solving technology on the same problems sets. The focus is on finite domain propagation solvers. An auxiliary aim is to build up a library of interesting problem models, which can be used to compare solvers and solving technologies.
Announcement of the results will be at CP2009: 20 September 2009.

Also see
* MiniZinc Challenge 2009 -- Rules
* MiniZinc Challenge 2008 and Results.

Report from SweConsNet2009, including my presentation

This Wednesday I attended SweConsNet2009 (SweConsNet is the Network for Sweden-based researchers and practitioners of Constraint programming), co located with SAIS Worksshop 2009 (SAIS, Swedish AI Society, which was held both Wednesday and Thursday).

Since this is a blog about constraint programming I will just focus on the SweConsNet part. But first I want to thank the arrangers of the workshops for two very well organized and interesting days. I'm looking forward to the next year.

SweConsNet2009

It was great fun to actually meet and talk to persons I only have had mail communication with before, e.g. Mikael Zayenz Lagerkvist (KTH, Gecode), Christian Schulte (KTH, Gecode), Justin Pearson (Uppsala), Andreas Launila (Gecode/R), Magnus Ågren (SICS, SICStus Prolog), Krzysztof Kuchcinski (Lund, JaCoP) and others I've only read about. All very nice people and helpful to a newcomber of this field like me. It was - of course - also very interesting to listen to the talks (some where somewhat over my head, though). The workshop was held in an informal environment that I really like.

My talk

As blogged before in My talk at SweConsNet Workshop 2009: "Learning Constraint Programming (MiniZinc, JaCoP, Choco, Gecode/R, Comet, Gecode): Some Lessons Learned", I was one of the talkers. The talk was about my experience and the findings after programmed the same (about) 17 models in the following six systems (a lot more models in some systems though): MiniZinc, JaCoP, Choco, Gecode/R, Comet, Gecode, and was focused on learning these systems. See My Constraint Programming Page for more information about each systems.

The Powerpoint of the talk is here: Learning Constraint Programming: Some lessons learned (PPT).

Some comments:
* The slides for the actual talk is from slide #1 to the "Thank You" slide (#15). The slides after that are from earlier drafts; I kept them in case of time shortage (which was not likely) or for the Q/A part of the talk. The talk was about 20 minutes and 10 minutes Q/A.

* I originally planned to do show a comparison of two small implementations: decompositions of the global constraint all_different_except_0 and one example of how to implement Element for a integer variables matrix in the Word square problem, but these simply took too long time so I skipped them.

* Much of the talk was about comparing different features of the systems (in regard of learning the systems). Slide 14 is a comparison table of the features I talked about.

* Slide 22 "Introduction in Constraint Programming: Books" was in the context of me missing a general introduction book of modelling in constraints programming (and not just about the theory of constraints and search trees). The listed publications are examples of approaches I have liked, for either a specific system (Mozart/Oz and OPL) or in operations research.

"Single missed feature"

During the Q/A-part Christian asked me to name one thing that I considered the biggest hurdle/problem etc for each system, and the following (very subjectively) list where presented. I realize that different systems has very different goal of what is important to pursue, but these where the obstacles I had when learning and using the systems:
  • MiniZinc: That it is "closed", i.e. there is not possible to extend it with one owns search strategies, propagators etc, except in Mercury (which I don't know and can't even compile on my computer).
  • Comet: Shortage of documentation of the language. The constraints are shortly documented but not the language itself. Fortunately I have the OPL book which is of great help, but the object model (very C++ like) etc is not documented. Possible more examples would help this. Update 2009-09-05: Version 2.0 (released 2009-09-02) includes a very good tutorial explaining (with full examples) both Comet and the three different modeling paradigms of Comet: constraint programming, constraint-based local search, and mathematical programming.
  • Choco: One of the biggest problem I have with Choco is its class structure which makes it very hard to find constraints or other functions.
  • JaCoP: Shortage of documentation. (I respect the "minimalistic approach" of not want to implement a lot of convenience constraints etc, and even if it can make the code more verbose I very much respect this decision.) Update: With version 2.4 (released 2009-09-16) there is an good tutorial describing the constraints and search heuristice.
  • Gecode: Make it possible to state Element for matrices of integer variables more easy. I have blogged about this for example in Learning constraint programming - part II: Modeling with the Element constraint. Update 2009-10-13: Version 3.2 (released 2009-10-05) has now support for this.
  • Gecode/R: For Gecode/R I regard the biggest problem that some of the Gecode constraint (e.g. global cardinality) was not implemented.

Full presentation

The early drafts was actually done as a "full sentenced" version and I plan to publish that version as well, but first after some serious reworking. This will cover all (or at least some) of the parts I skipped.

May 21, 2009

Gecode version 3.1 released

Gecode version 3.1 is released. The changes are:
Changes in Version 3.1.0 (2009-05-20)
This release introduces parallel search, features improved memory management (can double efficiency on MacOS X), and provides a reusable command line driver upon popular request. And, of course, some this and that.
  • Kernel
    • Performance improvements
      • Cache memory blocks from deleted spaces. This hardly increases peak memory consumption. It improves performance on Windows and Linux only by up to 5%, but on MacOS by 50% in some cases (this improvement is absolutely essential for parallel execution). (major)
  • Search engines
    • Additions
      • Added parallel search engines for DFS, BAB, and Restart (but not LDS). Please make sure to read the section "Parallel search" in "Modeling with Gecode". (major) Details
    • Other changes
      • The stop function of stop objects now also takes a second argument of type Search::Options. This is in particular useful for decisions that involve the number of threads used for search. (minor)
  • Finite domain integers
    • Additions
      • Added a wait propagator: executes a function when a variable (or variables) become(s) assigned. (minor)
    • Other changes
      • The INT_VAL_MED value selection now consistently selects the greatest element in the domain not greater than the median. (minor)
  • Finite integer sets
    • Additions
      • Added a wait propagator: executes a function when a variable (or variables) become(s) assigned. (minor)
    • Other changes
      • The set constraint sequentialUnion has been renamed to sequence. (minor)
  • Script commandline driver
    • Additions
      • Added a new module "driver" as a commandline driver for scripts. This is due to popular request: most people have been using the support functionality for examples anyway. This function is now wrapped into a proper module (and Example is now called Script to be more general). See "Modeling with Gecode" for documentation. (major)
    • Other changes
      • If Gist is not available, -mode gist is the same as -mode solution. Invocation with -help also prints information about how Gecode has been configured. (minor)
  • Support algorithms and datastructures
    • Additions
      • Added a tiny portable thread package specifically tailored to the needs for parallel search. Unfortunately, other portable thread packages have just too many issues. (minor)
      • Support::quicksort and Support::insertion support using the less than operator for the sort order by leaving out the comparator object. (minor)
  • Example scripts
    • Additions
      • Added new example for equidistant frequency permutation arrays (EFPA). (minor)
    • Other changes
      • The parameters of the Hamming Codes example are now configurable through command line options (instead of hard-coded). (minor)
  • Gist
    • Other changes
      • Small user interface changes: disable search from hidden nodes, add depth information to status bar, and add statistics for subtrees (available from the node context menu and the Node menu). (minor)
      • Easily add multiple inspectors to Gist. Inspectors are not exclusive any longer, you can select any combination of them to respond to clicks or solutions simultaneously. (minor)
    • Bug fixes
      • Fixed a dead lock that could occur when closing the Gist main window while search is still running. (minor)
      • The inspectors are finalized before Gist exits. This fixes a bug where (at least on Mac OS) some memory was not freed in the correct order. (minor)
      • Gist now correctly centers the current node after search has finished. (minor)
Also, version 1.6 of Gecode/FlatZinc is released to support for MiniZinc version 1.0. See MiniZinc version 1.0 released! for more about MiniZinc version 1.0.

Update: Some comments and findings
I have now converted all my Gecode models at My Gecode page to version 3.1.The only thing I has to care about was that the Example class is incorporated properly in the distribution and is now called Script. Also see the updated introduction document Modeling with Gecode (PDF) for more information.

Here are the changes I did, examplified with the model debruijn.cpp.

Remove
   #include "examples/support.hh"
and add the following (#include <minimodel.hh> is shown here for completion).
   #include <gecode/driver.hh>
   #include <gecode/int.hh>
   #include <gecode/minimodel.hh>

   using namespace Gecode;
The base class Example should also be replaced with Script. This means that the following old code:
   class DeBruijn : public Example {
was replaced with:
   class DeBruijn : public Script {
as where all the other occurrences of Example

Accordingly the optimization variants should also be replaced:
   MinimizeExample -> MinimizeScript
   MaximizeExample -> MaximizeScript
The Options has been incorporated in the Driver class so I had to change
   UnsignedIntOption _base_option;
   StringOption _int_var_option; 
to
   Driver::UnsignedIntOption _base_option;
   Driver::StringOption _int_var_option; 
And now I will check out the other new features...

May 20, 2009

MiniZinc version 1.0 released!

MiniZinc version is now official, i.e. version 1.0. Congratulations to the G12 team!

This version can be downloaded from here.

Comparing to the beta version 0.9 (released Christmas last year) the following has changed:


G12 MiniZinc Distribution version 1.0
-------------------------------------

* Licence change

The source code in the G12 MiniZinc distribution has now been released
under a BSD-style licence. See the files README and COPYING in the
distribution for details.

The MiniZinc examples, global constraint definitions and libraries
have been placed in the public domain.

* XML-FlatZinc

We have defined an XML representation for FlatZinc called XML-FlatZinc.
Two new tools, fzn2xml and xml2fzn, can be used to convert between FlatZinc
and XML-FlatZinc.

* FlatZinc Conformance Test Suite

We have added a suite of conformance tests for FlatZinc implementations.
It includes tests for built-in constraints, output and the behaviour of the
standard search annotations.

Changes to the MiniZinc language:

* Reification has been fixed. It is now a top-down process that correctly
handles partial functions such as integer division. Users can now also
supply alternative definitions for reified forms of predicates (this is
useful if a backend does not provide reified forms of all predicates).

* Users can supply alternative definitions for FlatZinc built-in constraints
(e.g., one can force the generated FlatZinc to use just int_lt rather than
int_lt and int_gt).

* A new variable annotation has been added: is_output is used to indicate
variables to be printed as part of the solution if no output item is
supplied. This annotation is converted to output_var or output_array as
appropriate by mzn2fzn.

Changes to the FlatZinc language:

* The outdomain_min and outdomain_max value choice methods are now supported
in the finite-domain solver backend.

* A new search annotation, seq_search, allows a sequential ordering to
be imposed on search annotations.

* The standard solve annotations now use nested annotations instead of
strings to describe variable selection strategies, value choice methods,
and exploration strategies.

* FlatZinc model instances may now contain bodyless predicate declarations.
This is to allow tools to type check FlatZinc that contains non-standard
built-in predicates.

* Two new annotations have been added that allow functional relationships
between variables to be defined: is_view_var on a variable declaration
states that this var is defined as a function of some other variables by
a constraint; defines_view_var(x) on a constraint states that the
constraint provides a definition for the view variable x.

* The FlatZinc specification now specifies how multiple solutions should be
output.

* Two new variable annotations have been added to indicate which variables
should be printed if a solution is found: output_var is used
for non-array variables; output_array([IndexSet1, IndexSet2, ...]) is used
for array variables.

* Output items are no longer supported in FlatZinc. The built-in string
operations, show/1 and show_cond/3 have also been removed from the
language.

Changes to the G12 MiniZinc-to-FlatZinc converter:

* The converter now outputs array_xxx_element constraints instead of
array_var_xxx_element constraints when the array argument is a
constant.

* An error is now reported if a variable is defined in a let expression
in a negated or reified context.

* The ZINC_STD_SEARCH_DIRS environment variable is no longer supported.
The new environment variable MZN_STDLIB_DIR or the command line option
``--stdlib-dir'' can be used to set the MiniZinc library directory.

* String array lookups are now supported.

* Comparison of fixed string expressions is now supported.

* Bodyless predicates in MiniZinc are now emitted at the head of the
generated FlatZinc. For backwards compatibility this behaviour
can be disabled using the ``--no-output-pred-decls'' command line
option.


Changes to the G12 MiniZinc and FlatZinc interpreters:

* There is a new solver backend for the FlatZinc interpreter based upon
the G12 lazy clause generation solver. This backend is selected with
the ``lazy'' or ``lazyfd'' argument to the interpreter's ``--backend''
option.

* The implementation of the int_negate/2 builtin constraint has been
fixed.

* The interpreters now take a flag (--solver-statistics or --solver-stats)
causing any statistical information gathered by the solver to be appended to
the output in the form of a Zinc comment.

* The interpreters now take a flag (-a or --all-solutions) that causes
then to return all solutions.

* The interpreters now take a flag (-n or --num-solutions) taking an
integer argument giving the maximum number of solutions to display.

* The MiniZinc interpreter behaviour has changed for models with no
output item: now only the values of variables annotated with
is_output are printed.

Other Changes:

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

at_most1
nvalue
precedence
table

For developers of FlatZinc solvers there is a transition guide to the changes.

Some comments

One of the greatest news is that it is now possible to state the number of solutions: -a for all solutions, or -n or --num-solutions for some fixed number of solutions. I have missed that feature for the distributed solvers since day one. Other solvers, e.g. Gecode/FlatZinc, ECLiPSe's, and, SICStus Prolog supported this feature already.

I'm also excited that string array lookups is supported. And there are other stuff which I will now test more in detail.

My MiniZinc page

On My MiniZinc page there are over 540 MiniZinc models which right now are for MiniZinc version 0.9. I have just started to convert these to the new version and it will take some time. Some models don't have to be changed at all, though.


Update some hours later
All models has now been converted to version 1.0. Here are some findings while doing this conversion:

* If there are any file in the current directory with the same name as in lib/minizinc/std/ (etc) then there will be a conflict with message structure error: more than one solve item

* solve: the search strategies should not have quotes. Also the search strategy occurrences is not available for the minizinc/flatzinc solvers.

* if there is no output clause, then define some (or all) decision variables as :: is_output and they will be printed.

* it seems that it is just the minizinc solver that handles the output section. For other solvers in the MiniZinc distribution (e.g. mip) the output is done via :: is_output annotation.

* always add "\n" last in the output clause.


May 09, 2009

Learning Constraint Programming IV: Logical constraints: Who killed Agatha? revisited

Here is a comparison of how different constraint programming systems implements the logical constraints in the problem Who Killed Agatha?, also known as The Dreadsbury Mansion Murder Mystery.. In Learning constraint programming - part II: Modeling with the Element constraint the problem was presented and showed how the systems implements the Element constraints.

Problem formulation from The h1 Tool Suite
Someone in Dreadsbury Mansion killed Aunt Agatha. Agatha, the butler, and Charles live in Dreadsbury Mansion, and are the only ones to live there. A killer always hates, and is no richer than his victim. Charles hates noone that Agatha hates. Agatha hates everybody except the butler. The butler hates everyone not richer than Aunt Agatha. The butler hates everyone whom Agatha hates. Noone hates everyone. Who killed Agatha?
Originally from F. J. Pelletier: Seventy-five problems for testing automatic theorem provers., Journal of Automated Reasoning, 2: 191-216, 1986.

Here we see compare how different constraint programming systems implement the three emphasized conditions in the problem formulation above:
  • the concept of richer
  • Charles hates noone that Agatha hates
  • No one hates everyone
All models defines the concepts of hates and richer as two matrices. The matrix declarations are omitted in the code snippets below.

Models

Here are the different models for the Who killed Agatha? problem. JaCoP and Choco has two version for how to implement the Element constraint, see the link above. Also, there is no Gecode/R version of this problem.

Defining the concept of richer

First we define the concept of richer, which consists of two parts:
  • No one is richer than him-/herself
  • if i is richer than j then j is not richer than i
This is an antisymmetric relation which is explored somewhat more (with an alternative predicate of the concept) in the MiniZinc model antisymmetric.mzn.

The logical concept used here is equivalence (if and only of).

MiniZinc

% define the concept of richer: no one is richer than him-/herself
forall(i in r) (
   richer[i,i] = 0
)

/\ % if i is richer than j then j is not richer than 
forall(i, j in r where i != j) (
   richer[i,j] = 1 <-> richer[j,i] = 0
)

Comet

Note that Comet don't have support for equivalence directly, instead we have to use two implications. Update: equivalence in Comet is written as == (I tested <=> which didn't work). Thanks to Pascal Van Hentenryck for pointing this out.
// no one is richer than him-/herself
forall(i in r)
  m.post(richer[i,i] == 0);

// if i is richer than j then j is not richer than i
forall(i in r, j in r : i != j) {
  /* earlier version: two implications
  m.post(richer[i,j] == 1 => richer[j,i] == 0);
  m.post(richer[j,i] == 0 => richer[i,j] == 1);
  */
  // equivalence
  m.post((richer[i,j] == 1) == (richer[j,i] == 0));
}

JaCoP

//  no one is richer than him-/herself
for(int i = 0; i < n; i++) {
    store.impose(new XeqC(richer[i][i], 0));
}

//  if i is richer than j then j is not richer than i
for(int i = 0; i < n; i++) {
    for(int j = 0; j < n; j++) {
        if (i != j) {
             store.impose(
                          new Eq(
                                 new XeqC(richer[i][j], 1),
                                 new XeqC(richer[j][i], 0)
                                 )
                          );

        }
    }
}

Choco

//   a) no one is richer than him-/herself
for(int i = 0; i < n; i++) {
    m.addConstraint(eq(richer[i][i], 0));
}

//   b) if i is richer than j then j is not richer than i
for(int i = 0; i < n; i++) {
    for(int j = 0; j < n; j++) {
        if (i != j) {
             m.addConstraint(
                             ifOnlyIf(
                                    eq(richer[i][j], 1),
                                    eq(richer[j][i], 0)
                                      )
                             );
        }
    }
}

Gecode

// no one is richer than him-/herself
for(int i = 0; i < n; i++) {
  rel(*this, richer_m(i,i), IRT_EQ, 0, opt.icl());
}

// if i is richer than j then j is not richer than i
for(int i = 0; i < n; i++) {
  for(int j = 0; j < n; j++) {
    if (i != j) {
      post(*this, tt(
              eqv(
                 richer_m(j,i) == 1, // <=>
                 richer_m(i,j) == 0)), 
                 opt.icl());
    }
  }
}

Charles hates noone that Agatha hates

Here is the definitions of the condition Charles hates noone that Agatha hates, which simply mean the implication:
  if Agatha hates X then Charles don't hate X

MiniZinc

When starting to modeling these kind of problems, I tend to follow the order of the conditions, which here means that the Charles part is before the Agatha part. When remodeling in another system the order tends to be fixed (cf the Comet version).
forall(i in r) (
   hates[charles, i] = 0 <- hates[agatha, i] = 1
)

Comet

forall(i in r)
  m.post(hates[agatha, i] == 1 => hates[charles, i] == 0);

JaCoP

for(int i = 0; i < n; i++) {
    store.impose(
                 new IfThen(
                            new XeqC(hates[agatha][i], 1),
                            new XeqC(hates[charles][i], 0)
                            )
                 );
}

Choco

I tend to copy/paste the models for Choco and JaCoP and just change the functions that are different. A consequence of this is that some special feature in one of these two systems is not used.
for(int i = 0; i < n; i++) {
    m.addConstraint(
                    implies(
                            eq(hates[agatha][i], 1),
                            eq(hates[charles][i], 0)
                            )
                    );
}

Gecode

for(int i = 0; i < n; i++) {
   post(*this, tt(
                  imp(
                        hates_m(i,agatha) == 1, 
                        // =>
                        hates_m(i,charles) == 0)), 
                        opt.icl());
}

No one hates everyone

This is the last condition to compare: No one hates everyone. It is implemented by a sum of the number of persons that each person hates, and this sum must be 2 or less. Note that it is possible to hate oneself.

MiniZinc

forall(i in r) (
  sum(j in r) (hates[i,j]) <= 2  
)

Comet

  forall(i in r) 
    m.post(sum(j in r) (hates[i,j]) <= 2);

JaCoP

Note: We could save the XlteqC constraint by restrict the domain of a_sum to 0..2 instead of 0..n (=3) but this explicit use of XlteqC is more clear.
for(int i = 0; i < n; i++) {
    FDV a[] = new FDV[n];
    for (int j = 0; j < n; j++) {
        a[j] = new FDV(store, "a"+j, 0, 1);
        a[j] = hates[i][j];
    }
    FDV a_sum = new FDV(store, "a_sum"+i, 0, n);
    store.impose(new Sum(a, a_sum));
    store.impose(new XlteqC(a_sum, 2));
}

Choco

Note: sum is an operator, which makes the condition somewhat easier to state than in JaCoP.
for(int i = 0; i < n; i++) {
    IntegerVariable a[] = makeIntVarArray("a", n, 0, 1);
    for (int j = 0; j < n; j++) {
        a[j] = hates[i][j];
    }
    m.addConstraint(leq(sum(a), 2));
}

Gecode

In Gecode this condition is quite easy to state by using linear. In order to use this there is a Matrix "view" of the hates matrix hates_m.
Matrix hates_m(hates, n, n);
// ...
for(int i = 0; i < n; i++) {
  linear(*this, hates_m.row(i), IRT_LQ, 2, opt.icl());
}

End comment

The mandatory end comment: There are probably better ways of modeling the problem than shown above, either by changing some details or by model the problem completely different. Maybe this will be done sometime...

May 08, 2009

Learning Constraint Programming III: decomposition of a global constraint: alldifferent_except_0

The series Learning Constraint Programming is a preparation for My talk at SweConsNet Workshop 2009: "Learning Constraint Programming (MiniZinc, JaCoP, Choco, Gecode/R, Comet, Gecode): Some Lessons Learned". Confusingly, the entries is not numbered in any logical order. Sorry about that.

Here are the previous entries:

Introduction

The global constraint alldifferent_except_0 (or the more general variant alldifferent_except_c) is one of my favorite global constraints. It is very handy to use when 0 (or any constant c) is coded as an unknown or irrelevant value. Then we can constraint the rest to be all distinct.

The great Global Constraint Catalog entry alldifferent_except_0) explains this constraint as:
Purpose
Enforce all variables of the collection VARIABLES to take distinct values, except those variables that are assigned to 0.

Example
​(<5,​0,​1,​9,​0,​3>)​
The alldifferent_except_0 constraint holds since all the values (that are different from 0) 5, 1, 9 and 3 are distinct.

Models

I have modeled a decomposition of alldifferent_except_0 in the following models, where the constraint is just tested perhaps combined with some other constraint, e.g. sorted or that there must be at least some zeros:

- MiniZinc alldifferent_except_0.mzn
- Comet: alldifferent_except_0.co
- Gecode/R: all_different_except_0.rb
- Choco: AllDifferentExcept0_test.java
- JaCoP: AllDifferentExcept0_test.java
- Gecode: alldifferent_except_0.cpp

Some models using alldifferent_except_0

And here is some real use of the constraint:

- Nonogram (Comet): nonogram.co. (A faster model using the regular constraint, nonogram_regular.co, is described here and here).
- I wrote about alldifferent_except_0 in Pi Day Sudoku 2009. However, as faster way of solving the problem was found and is described in Solving Pi Day Sudoku 2009 with the global cardinality constraint). Note: the competition is still on, so there is no link to any model.
- Sudoku generate (Comet): sudoku_generate.co
- all paths graph (MiniZinc): all_paths_graph.mzn
- Cube sum (MiniZinc): cube_sum.mzn
- Message sending (MiniZinc): message_sending.mzn

As the first two entries indicates, there may be faster solutions than using (a decomposition) of alldifferent_except_0, but even as a decomposition is a great conceptual tool when modeling a problem.

Implementations

In the implementations below we also see how to define a function (predicate) in the constraint programming systems.

For the Gecode/R model there are different approaches:
- "standard" ("direct") approach where we loop over all different pairs of elements and ensures that if both values are different from 0 then they should be different
- using count
- using global cardinality ("simulated" in Gecode/R, see below)

Also, in some models we use the slighly more general version alldifferent_except_c where c is any constant (e.g. "Pi" in the Pi Day Sudoku puzzle mentioned above.

MiniZinc:

Model: alldifferent_except_0.mzn.
predicate all_different_except_0(array[int] of var int: x) =
   let {
      int: n = length(x)
   }
   in
   forall(i,j in 1..n where i != j) (
        (x[i] > 0 /\ x[j] > 0) 
        -> 
        x[i] != x[j]
   )
;

// usage:
constraint all_different_except_0(x);

Comet:

Model: alldifferent_except_0.co.
function void alldifferent_except_0(Solver m, var{int}[] x) {
  int n = x.getSize();
  forall(i in 1..n, j in i+1..n) {
    m.post(
           x[i] > 0 && x[j] > 0 
           => 
           x[i] != x[j]
           );
  }
}

// usage
exploreall {
  // ...
  alldifferent_except_0(m, x);
}

Gecode/R

Model:all_different_except_0.rb.

When modeling the constraint in Gecode/R, I experimented with different approaches. The reification variant all_different_except_0_reif is actually quite fast.
# The simplest and the fastest implementation 
# using count for 1..max (poor man's global cardinality)
def all_different_except_0
  (1..self[0].domain.max).each{|i|
    self.count(i).must <= 1
  }
end

# global cardinality version using an extra array with the counts
def global_cardinality(xgcc)
  (self[0].domain.max+1).times{|i|
    xgcc[i].must == self.count(i)
  }
end

#
# The standard approach using reification.
#
def all_different_except_0_reif(x)
  n = x.length
  b1_is_an bool_var_matrix(n,n)
  b2_is_an bool_var_matrix(n,n)
  b3_is_an bool_var_matrix(n,n)
  n.times{|i|
    n.times{|j|
      if i != j then 
        x[i].must_not.equal(0, :reify => b1[i,j]) 
        x[i].must_not.equal(0, :reify => b2[i,j]) 
        x[i].must_not.equal(x[j], :reify => b3[i,j])
        (b1[i,j] & b2[i,j]).must.imply(b3[i,j])
      else 
        b1[i,j].must.true
        b2[i,j].must.true
        b3[i,j].must.true
      end
    }
  }
end

    # ...
    # usage:
    x.all_different_except_0
    # all_different_except_0_gcc(x)
    # all_different_except_0_reif(x)

Choco

Model: AllDifferentExcept0_test.java.

Note that here alldifferent_except_0 is derived from the more general version alldifferent_except_c.
//
// decomposition of alldifferent except 0
//
public void allDifferentExcept0(CPModel m, IntegerVariable[] v) {
    allDifferentExceptC(m, v, 0);
}

//
// slightly more general: alldifferent except c
//
public void allDifferentExceptC(CPModel m, IntegerVariable[] v, int c) {
    int len = v.length;
    for(int i = 0; i < v.length; i++) {
        for(int j = i+1; j < v.length; j++) {
            m.addConstraint(ifThenElse(
                                       and(
                                           gt(v[i], c), 
                                           gt(v[j], c)
                                           ), 
                                       neq(v[i], v[j]),
                                       TRUE
                                       )
                            );
        }
    }    
}

// ...
// usage: 

    m.addConstraint(allDifferent(queens));

JaCoP

Model: AllDifferentExcept0_test.java

This is exactly the same approach as the Choco version.
//
// decomposition of alldifferent except 0
//
public void allDifferentExcept0(FDstore m, FDV[] v) {
    allDifferentExceptC(m, v, 0);
} // end allDifferentExcept0


//
// slightly more general: alldifferent except c
//
public void allDifferentExceptC(FDstore m, FDV[] v, int c) {
    int len = v.length;
    for(int i = 0; i < v.length; i++) {
        for(int j = i+1; j < v.length; j++) {
            m.impose(new IfThen(
                                       new And(
                                           new XneqC(v[i], c), 
                                           new XneqC(v[j], c)
                                           ), 
                                       new XneqY(v[i], v[j])
                                       )
                            );
        }
    }        
} // end allDifferentExceptC


        // ...
        // usage:
        allDifferentExcept0(store, x);

Gecode

alldifferent_except_0.cpp

The Gecode version is very succint since it use overloaded boolean operators. Very nice.
void alldifferent_except_0(Space& space, IntVarArray x, IntConLevel icl = ICL_BND) {
  for(int i = 0; i < x.size(); i++) {
    for(int j = i+1; j < x.size(); j++) {
      post(space,
           tt(
           imp(x[i] != 0 && x[j] != 0, 
           // =>
           x[i] != x[j])),
           icl
           );
    }
  }
} // alldifferent_except_0


// ...
// usage:
    alldifferent_except_0(*this, x, opt.icl());

May 05, 2009

Learning constraint programming - part II: Modeling with the Element constraint

As indicated last in Learning constraint programming (languages) - part I here are some findings when implementing Crossword, Word square, and Who killed Agatha?. See links below for the implementations.

Spoiled
The first constraint programming system i learned after constraint logic programming in Prolog was MiniZinc. When implemented the problems below I realized that I have been quite spoiled by using MiniZinc. The way MiniZinc (and also Comet) supports the Element constraint, i.e. access of variable arrays/matrices, is very straightforward in these systems and it doesn't matter whether the array to access is an array of integers or of non-variable variable integers. In the Java (Choco, JaCoP) and C++ systems (Gecode), however, this is another matter. Due to different circumstances I have not implemented these models in Gecode/R.

Element in MiniZinc and Comet
Accessing arrays and matrices in MiniZinc and Comet is simply done by using the [] construct, no matter what the type of the array or the index are (I assume integers and variable integers here). For the other systems we must explicitly use the Element constraint (called nth in Choco).

Crossword

This is a standard constraint programming problem, used as a running example in Apt's great book Principles of Constraint Programming. Here is a formulation of the problem (stated differently than in the book):
Place the words listed below in the following crossword. The '#' means a blocked cell, and the numbers indicate the overlappings of the words.

      1   2   3   4   5
    +---+---+---+---+---+
  1 | 1 |   | 2 |   | 3 |
    +---+---+---+---+---+
  2 | # | # |   | # |   |
    +---+---+---+---+---+
  3 | # | 4 |   | 5 |   |
    +---+---+---+---+---+
  4 | 6 | # | 7 |   |   |
    +---+---+---+---+---+
  5 | 8 |   |   |   |   |
    +---+---+---+---+---+
  6 |   | # | # |   | # |
    +---+---+---+---+---+
                         
We can use the following words
* AFT * ALE * EEL * HEEL * HIKE * HOSES * KEEL * KNOT * LASER * LEE * LINE * SAILS * SHEET * STEER * TIE

Models


MiniZinc: crossword.mzn
Choco: CrossWord.java
JaCoP: CrossWord.java
Gecode/R: (Not implemented)
Comet: crossword.co
Gecode: crossword.cpp

Note: I have seen more general models for solving crossword problems in Choco, JaCoP, Gecode/R, and Gecode with constructions other that the simple Elements used here. Since I wanted to compare the same way of solving the problem using the same Element-construct this may be an unfair comparison between the systems. Well, this is at least a finding how to implement this problem by Element...

Explanation of variables
The matrix A is the individual chars of the words (Comet variant):
int A[1..num_words,1..word_len] = 
  [
   [h, o, s, e, s], //  HOSES
   [l, a, s, e, r], //  LASER
   [s, a, i, l, s], //  SAILS
   [s, h, e, e, t], //  SHEET
   [s, t, e, e, r], //  STEER
   [h, e, e, l, 0], //  HEEL
   [h, i, k, e, 0], //  HIKE
   [k, e, e, l, 0], //  KEEL
   [k, n, o, t, 0], //  KNOT
   [l, i, n, e, 0], //  LINE
   [a, f, t, 0, 0], //  AFT
   [a, l, e, 0, 0], //  ALE
   [e, e, l, 0, 0], //  EEL
   [l, e, e, 0, 0], //  LEE
   [t, i, e, 0, 0]  //  TIE
];
overlapping is the matrix of the overlapping cells)
This is the Comet version:
[
 [1, 3, 2, 1],   //  s
 [1, 5, 3, 1],   //  s 
  
 [4, 2, 2, 3],   //  i
 [4, 3, 5, 1],   //  k
 [4, 4, 3, 3],   //  e
  
 [7, 1, 2, 4],   //  l
 [7, 2, 5, 2],   //  e
 [7, 3, 3, 4],   //  e
  
 [8, 1, 6, 2],   //  l
 [8, 3, 2, 5],   //  s
 [8, 4, 5, 3],   //  e
 [8, 5, 3, 5]    //  r
 ];
E is the variable array of which the word to use for the different overlappings. This is in fact the only variable (array) that is needed in the problem, apart from the utility/convenience variables.

The main constraint for the crossword example in each system is stated thus:

MiniZinc:
forall(i in 1..num_overlapping) (
   A[E[overlapping[i,1]], overlapping[i,2]] =  A[E[overlapping[i,3]], overlapping[i,4]]
)
Comet:
  forall(i in 1..num_overlapping) {
    cp.post(A[E[overlapping[i,1]], overlapping[i,2]] ==  A[E[overlapping[i,3]], overlapping[i,4]], onDomains);
  }
Choco:
Note that Choco has a special Element which support two dimensional arrays (matrix), which we use.
for(int I = 0; I < num_overlapping; I++) {
  IntegerVariable tmp = makeIntVar("tmp" + I, 1, 26);
  M.addConstraint(nth(E[overlapping[I][0]], W[overlapping[I][1]], AX, tmp));
  M.addConstraint(nth(E[overlapping[I][2]], W[overlapping[I][3]], AX, tmp));
}
JaCoP:
Here we had used some trickery by using a transposed version of the word matrix since JaCoP has no special Element constraint for two dimensional arrays.
for (int I = 0; I < num_overlapping; I++) {
   FDV tmp = new FDV(store, "TMP" + I, 0, num_words*word_len);
   store.impose(new Element(E[overlapping[I][0]], words_t[overlapping[I][1]], tmp));
   store.impose(new Element(E[overlapping[I][2]], words_t[overlapping[I][3]], tmp));
}
Gecode:
This is more complicated compared to the two Java systems since in Gecode we use an array (of length rows*cols) to simulate the matrix. (There is a Matrix "view" in Gecode but the indices must be of type integer, not IntVar, so it can not be used.) Also, the constraints plus and mult takes IntVar as argument.

The first overlapped crossing is "expanded" like this (Gecode is 0-based):
   A[E[overlapping[i,0]], overlapping[i,1]] // MiniZinc/Comet
   =>
   a1 = A[ E[I*4+0] * word_len + overlapping[I*4+1]] // Gecode
Here is the complete code. The comments hopefully explains what is going on.

First we define an utility function for accessing the element according to the above principle.
/**
 *
 * Special version of element for an array version of a "matrix" words,
 * E is an integer variable array, C is an array of IntVars for
 * the offset j in the words "matrix".
 *
 * The call 
 *    element_offset(*this, words, E[i], word_len_v, C[j], res, opt.icl());
 *
 * corresponds to:
 *    res = words[E[i], j] --> words[E[i]*word_len+J]
 *
 */
void element_offset(Space& space,
                   IntArgs words,
                   IntVar e,
                   IntVar word_len,
                   IntVar c,
                   IntVar res,
                   IntConLevel icl = ICL_DOM) {

      element(space, words, 
              plus(space, 
                   mult(space, 
                        e, 
                        word_len, icl), 
                   c, icl), 
              res, icl);
}

The function is then used as follows:
for(int I = 0; I < num_overlapping; I++) {
   IntVar e1(*this, 0, num_overlapping*4);
   IntVar e2(*this, 0, num_overlapping*4);

   IntVarArray o(*this, 4, 0, num_overlapping*4);
   for(int J = 0; J < 4; J++) {
     post(*this, o[J] == overlapping[I*4+J], opt.icl());
   }

   element(*this, E, o[0], e1, opt.icl());      // e1 = E[I*4+0]
   element(*this, E, o[2], e2, opt.icl());      // e2 = E[I*4+2]

   IntVar a1(*this, 0, num_words*word_len);
      
   element_offset(*this, A, e1, word_len_v, o[1], a1, opt.icl());
   element_offset(*this, A, e2, word_len_v, o[3], a1, opt.icl());
}
(The same element_offset function is also used in the word_square problem below.) It took quite a time to get the function and temporary variables (and their domains) right. With training (and the element_offset as a skeleton) similiar problems should be easier to implement.

Note: this is not a bashing of Gecode. Gecode is a great system and it happens that for this specific problem, Gecode does not has the appropriate support. I should also mention that it was a long time since I programmed in C++ and am little rusty.

As mentioned earlier, I have been very spoiled by the MiniZinc (and Comet) constructs. Also: I'm a very 'lazy' person (in the Perl sense of the word lazy) and likes the agile programming languages - Perl, Ruby, Python etc - much for their high level constructs.

Word square problem

The word problem is a cousin to the crossword problem, and is described in Wikipedia's Word_square:
A word square is a special case of acrostic. It consists of a set of words, all having the same number of letters as the total number of words (the "order" of the square); when the words are written out in a square grid horizontally, the same set of words can be read vertically.
An example of order 7 found by the Comet model where we see that the first row word (aalborg) is also the first column word.

aalborg
abaiser
lantaca
bittman
osamine
recanes
granese

Models

Here are the models for solving the Word square problem:
MiniZinc: word_square.mzn
Choco: WordSquare.java
JaCoP: WordSquare.java
Gecode/R: (Not implemented it Gecode/R)
Comet: word_square.co
Gecode: word_square.cpp

It is somewhat easier than the crossword problem. As before, E is the array of the index of the words to use, and words is an matrix of the words. Also, these models is an experiment of how to read a file, the word list /usr/dict/words (standard on Unix/Linux systems).

MiniZinc
forall(I, J in 1..word_len) (
  words[E[I], J] = words[E[J],I]
)
Comet
  forall(i in 1..word_len) {
    forall(j in 1..word_len) {    
      cp.post(words[E[i], j] == words[E[j],i], onDomains);
    }
  }
JaCoP
// 
// The overlappings (crossings).
// Note that we use a transposed word matrix for the Element.
//
for(int i = 0; i < word_length ; i++) {
    for(int j = 0; j < word_length ; j++) {
        // Comet: words[E[i], j] ==  words[E[j],i]
        FDV tmp = new FDV(store, "tmp" + i + " " + j, 0, dict_size);
        store.impose(new Element(E[i], words[j], tmp));
        store.impose(new Element(E[j], words[i], tmp));
    }
}
Choco
// Constants for the nth constraint below
IntegerVariable [] C = new IntegerVariable[dict_size];
for (int I = 0; I < word_length; I++) {
    C[I] = makeIntVar("C"+I, I,I);
}

// The overlappings (crossings)
for(int I = 0; I < word_length ; I++) {
    for(int J = 0; J < word_length ; J++) {
        // Comet: words[E[i], j] ==  words[E[j],i]
        IntegerVariable tmp = makeIntVar("tmp" + I + " " + J, 0, dict_size);
        M.addConstraint(nth(E[I], C[J], words, tmp));
        M.addConstraint(nth(E[J], C[I], words, tmp));
    }
}
Gecode
Note that this model use the same function element_offset that was used in the Crossword problem. It took some time to realize that it also could be used here.
// convenience variables for the element constraints below
// since element, plus, and mult wants IntVars.
IntVar word_len_v(*this, word_len, word_len);
IntVarArray C(*this, word_len, 0, word_len-1);
for(int i = 0; i < word_len; i++) {
  rel(*this, C[i], IRT_EQ, i, opt.icl());
}

for(int i = 0; i < word_len; i++) {
  for(int j = 0; j < word_len; j++) {
    // words[E[i], j] ==  words[E[j],i]

    IntVar tmp(*this, 0, num_words);

    // tmp == words[E[i], j] --> words[E[i]*word_len+j]
    element_offset(*this, words, E[i], word_len_v, C[j], tmp, opt.icl());

    // tmp == words[E[j], i]  --> words[E[j]*word_len+i]
    element_offset(*this, words, E[j], word_len_v, C[i], tmp, opt.icl());
  }
}

Who killed Agatha?

This is a standard benchmark for theorem proving, also known as The Dreadsbury Mansion Murder Mystery.

Problem formulation from The h1 Tool Suite
Someone in Dreadsbury Mansion killed Aunt Agatha. Agatha, the butler, and Charles live in Dreadsbury Mansion, and are the only ones to live there. A killer always hates, and is no richer than his victim. Charles hates noone that Agatha hates. Agatha hates everybody except the butler. The butler hates everyone not richer than Aunt Agatha. The butler hates everyone whom Agatha hates. Noone hates everyone. Who killed Agatha?
Originally from F. J. Pelletier: Seventy-five problems for testing automatic theorem provers., Journal of Automated Reasoning, 2: 191-216, 1986.

Models


MiniZinc: who_killed_agatha.mzn
JaCoP : WhoKilledAgatha.java
JaCoP : WhoKilledAgatha_element.java
Choco: WhoKilledAgatha.java
Choco: WhoKilledAgatha_element.java
Comet: who_killed_agatha.co
Gecode: who_killed_agatha.cpp

In Some new Gecode models I wrote about the findings in implemented this problem in Gecode and compared to Comet/MiniZinc.

The models use two 3x3 matrices for representing the two relations hates and richer with 0..1 as domain (i.e. boolean). The Element constraints is used for implementing the condition A killer always hates, and is no richer than his victim. where the_killer is an integer variable; the_victim is in some models replaced by agatha (the integer 0). The interesting thing here is that at least one of the indices are integer variables, which caused the difficulties in the two problems above.

These models also use a lot of boolean constructs. A comparison of how these are implemented in the different CP systems may be described in a future blog post.

MiniZinc
hates[the_killer, the_victim] = 1 /\
richer[the_killer, the_victim] = 0
Comet:
m.post(hates[the_killer, the_victim] == 1);
m.post(richer[the_killer, the_victim] == 0);
Note: In the models below I have simplified the problem by using agatha (defined as the integer 0) instead of the integer variable the_victim. This is not a problem since we know that Agatha is the victim, and is the reason why the Element is easier to use than for Crossword and Word square.

JaCoP variant 1 (no Element):
JaCoP don't have a direct support for the case when the index i (in matrix[i][j]) is an integer variable so the first variant of modeling the condition A killer always hates, and is no richer than his victim. does not use Element at all. In WhoKilledAgatha.java we simply loop over all integers (0..2), check if "this" i equals the_killer and then we can use two integers for accessing the matrices. Also, note the IfThen construct.
for(int i = 0; i < n; i++) {
    store.impose(
                 new IfThen(
                            new XeqC(the_killer, i),
                            new XeqC(hates[i][agatha], 1)
                            )
                 );
    store.impose(
                 new IfThen(
                            new XeqC(the_killer, i),
                            new XeqC(richer[i][agatha], 0)
                            )
                 );
}
This was the first variant I implemented, but then I recall the "trickery" used in Crossword and Word square where the matrices where transposed and Element could be used. The problem with this approach is that all constraints must be rewritten in a way that may be confusing. Come to think of it, maybe the names of the matrices should have been changed to is_hated_by and poorer.

JaCoP variant 2 (transposed matrices, Element)
This method of transposition and using Element is implemented in WhoKilledAgatha_element.java. The constraint is now much simpler:
int shift = -1;
for(int i = 0; i < n; i++) {
    store.impose(new Element(the_killer, hates[agatha], one, shift));
    store.impose(new Element(the_killer, richer[agatha], zero, shift));
}
Note: the Element in JaCoP defaults to start index as 1, but has support for shifting it to 0, by using -1 as the shift parameter. Choco variant 1 (no Element)
I implemented exact the same principle that was used in the two JaCoP model in the two Choco models. The first - no Element - is WhoKilledAgatha.java:

for(int i = 0; i < n; i++) {
    m.addConstraint(implies(
                            eq(the_killer,i),
                            eq(hates[i][agatha], 1)
                            )
                    );
    m.addConstraint(implies(
                            eq(the_killer, i),
                            eq(richer[i][agatha], 0)
                            )
                    );
}
Choco variant 2 (transposed matrices, nth)
Note: one and zero are integer variables since nth cannot handle plain integers as the last argument.
for(int i = 0; i < n; i++) {
   m.addConstraint(nth(the_killer, hates[agatha], one));
   m.addConstraint(nth(the_killer, richer[agatha], zero)
}

Comments

Here we have seen - not surprisingly - that using the Element constraint is quite different depending of which CP system we use and it can be easy or not so easy. It was my explicit intension to see how to solve the same problem as similiar as possible. We should also note that most (if not all) problems can be modeled in many ways, some not using Element at all.

One last comment: The two Java models of Who killed Agatha? took quite a long time to implement. The main reason for that was not the the handling of Element but was due to a bug of confusing the two matrices in one of the conditions. Sigh.