Håkan Kjellerstrand ([email protected])Independent Researcher, Malmö
http://www.hakank.org/
My Constraint Programming Blog:http://www.hakank.org/constraint_programming_blog/
This talk at SweConsNet 20130527:http://www.hakank.org/constraint_programming/sweconsnet_talk_20130527.ppt
What I (still) like about Constraint Programming
Some more about me* Not a theory guy, much more a modeling guy I like to solve a good puzzle – with CP.
* I don't do CP professionally which might explain some things...
* Co-organizer of the CP 2013 Workshop (in Uppsala, September) “CP Solvers: Modeling, Applications, Integration, and Standardization” http://cp2013.a4cp.org/workshops/cpsolvers
Organizers: Jacob Feldman, Helmut Simonis, and me Constraint Solvers Catalog http://openjvm.jvmhost.net/CPSolvers/
Tested ~24 CP Systems
- Choco (21 models) - Comet (168 models) - ECLiPSe CLP (177 models) - Gecode (165 models) - Gecode/R (29 models) - JaCoP (18 models) - JaCoP/Scala (39 models) - MiniZinc (1031 models) - SICStus Prolog (152 models) - Essence'/Tailor (26 models) - Essence'/Savile Row (56 models) - Zinc (39 models) - Google or-tools/Python (202 models) - Google or-tools/Java (36 models) - Google or-tools/C# (129 models) - OscaR (Scala in OR) (146 models) - Java JSR-331 (42 models) - Numberjack (52 models) - AIMMS+CP (39 models) - B-Prolog (207 models) - Choco3 (90 models) - AMPL (100 "pure" CP models) - ILOG CP Optimizer OPL (as we speak, > 100 models) - Answer Set Programming ("related paradigm" 87 encodings)
In total > 3200 models.
Note: Not all of these are pure CP models, some are plain IP models.
Common Constraint Programming Problems
http://hakank.org/common_cp_models/
* I always start testing a CP system with a number of standard "learning" problems to get a feel for different constructs in the CP system.
* Also, I always report bugs and whine about things I don't like (or is inconvenient/weird/etc) to the developers.
* Forthcoming (perhaps) - Picat, http://picat-lang.org/ (available 20130531) - JaCoP v 4.0 - Gecode-python, https://launchpad.net/gecode-python (Python inteface to Gecode) - Copris, http://bach.istc.kobe-u.ac.jp/copris/ (Scala) - Clojure core.logic, https://github.com/clojure/core.logic - or-tools/C++ (no syntactic sugar at all...) - new MiniZinc solvers
Other suggestions?
So, what do I (still) like about Constraint Programming?
In short: The modeling part, the ease of modeling many types of problems.
Some CP features:- element- reification- generating 1/N/all solution(s)- global constraints- reversibility/bidirection- nonlinear constraints- symmetry breaking- declarative (high level)- nifty language features (wish list)
Many of these features was what caught my interest (blew my mind) in 2008 after checking out mathematical programming some year earlier. Hence the “still”.
Element constraintThe “signum” of Constraint Programming.
* Good example (IMHO): x[y] = z; % MiniZinc, similar Essence' cp.add(x(y) == z, Strong); // OscaR (Scala)
* OK: rel(*this, z == element(x, y); // Gecode
* Not so good: solver.addConstraint( solver.makeEquality(z, solver.makeElement(x, y).var())); // or-tools/Java
Yes, I punish certain host languages (e.g. Java) that don't have operator overloading.
AMPL: can simulate element with “exists”. Here 1..n is the domain of y. s.t. c1: exists{j in 1..n} j = y and x[j] = z;
Reification
* alldifferent_except_0 Implementing a decomposition of this constraint is often a good proxy of the "syntactical sugarness" of a CP system. It's one of the first things I test.
* good example: MiniZinc, Zinc, Comet, Essence' % MiniZinc version forall(i, j in 1..length(x) where i < j) ( (x[i] != 0 /\ x[j] != 0) -> x[i] != x[j] )
* good example: Gecode for(int i = 0; i < x.size(); i++) { for(int j = i+1; j < x.size(); j++) { rel(space, (x[i] != 0 && x[j] != 0) >> (x[i] != x[j]), Icl); } }
Reification Choco3 (beta)// only half-reification (might be easier in fortcoming versions)BoolVar[] b = VariableFactory.boolArray("b_"+i+"_"+j, 3, solver);solver.post(IntConstraintFactory.implies(b[0], (IntConstraintFactory.arithm(v[i], "!=", c))));solver.post(IntConstraintFactory.implies(VariableFactory.not(b[0]), (IntConstraintFactory.arithm(v[i], "=", c))));solver.post(IntConstraintFactory.implies(b[1], (IntConstraintFactory.arithm(v[j], "!=", c))));solver.post(IntConstraintFactory.implies(VariableFactory.not(b[1]), (IntConstraintFactory.arithm(v[j], "=", c))));solver.post(IntConstraintFactory.implies(b[2], (IntConstraintFactory.arithm(v[i], "!=", v[j]))));solver.post(IntConstraintFactory.implies(VariableFactory.not(b[2]), (IntConstraintFactory.arithm(v[i], "=", v[j]))));ALogicTree t = Node.implies(Node.and(Literal.pos(b[0]),
Literal.pos(b[1])), Literal.pos(b[2]) );solver.post(IntConstraintFactory.clauses(t, solver));
Generating 1/N/all solution(s)
* Debugging Example: 8-queens should have 92 solutions, otherwise the model is wrong. Or - much rarer - the solver is wrong. Later: This assumes that no symmetry breaking is used. (Thanks Mats Carlsson.)
I use this very much. In the current AMPL+Gecode implementation this is not implemented which was quite strenuous..
* Ensure unicity of a solution, e.g. a Sudoku problem (set the numbet of solution to 2 and expect just 1)
* Certain combinatorial problems is about counting the number of solutions.
* Generating problem instances This is - kind of - the reverse of solving a problem, by letting all decision variable be free. E.g. "Drive Ya Nuts".
* Special case Search for optimal value and then generate all solutions with that value. No CP system has this as built-in. Hint, hint :-)
Global constraints
Global Constraint Catalog (364 listed global constraints)http://www.emn.fr/z-info/sdemasse/gccat/index.html
Advantages* Special tailored propagators This is the usual sale pitch for global constraints.
* For me: the modeling part High level concepts as "patterns" in modeling
* Some personal favorites all_different all_different_except_0 element ( x[y] = z) global_cardinality_count decrease/increase (sortedness) regular (finite state machine) cumulative (for scheduling like problems) circuit (Hamiltonian circuit) table (allowed assignments) inverse (useful in some puzzles :)
http://hakank.org/minizinc/#global (~170 decompositions in MiniZinc, more or less general)
Reversibility (bi-direction)
Simple example: convert a number ("num") to/from its digits ("x").
// MiniZinc (general predicate, base 10)predicate toNum(array[int] of var int: a, var int: n) = let { int: len = length(a) } in n = sum(i in 1..len) ( pow(10,len-i) * a[len-i+1] );
var 0..999: num;array[1..3] of var 0..9: x;
constraint toNum(x, num) /\ num % 2 = 1 /\ % constraint on num: is an odd number x[2] > 5; % constraint of digits: second digits > 5
- more general: channelling/dual model
Nonlinear constraints
* Compared to (traditional) IP modeling, there is much less need to reformulate/linearize nonlinear constraints.
* There is no need to remember which IP solver it is that handle that nonlinear constraints (quadratic etc).
* No big M's! Though we want as small domains as possible.
Symmetry breaking
Pruning the search tree.
Global constraints:- increasing/decreasing- lex family- precedence
Some system supports dynamic symmetry breaking, e.g.Chris Mears' Lightweight Dynamic Symmetry Breaking (LDSB)http://www.cmears.id.au/symmetry/
Supported by:- ECLiPSe CLP- Gecode
ECLiPSe CLP also has support for- Symmetry Breaking During Search (SBDS)- GAP-based Symmetry Breaking via Dominance Detection (SBDD)
Declarative, high level
Note: I'm not sure how to define "declarative", but I know it when I see it. :-)
http://en.wikipedia.org/wiki/Declarative_programmingThe introduction: “In computer science, declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow. Many languages applying this style attempt to minimize or eliminate side effects by describing what the program should accomplish, rather than describing how to go about accomplishing it (the how is left up to the language's implementation). This is in contrast with imperative programming, in which algorithms are implemented in terms of explicit steps.“
Sometimes it's hard claiming declarativness of CP modeling when the code is full of for(all) loops. Though, I still insist that it's declarative...
High level: The higher, the better (IMHO), at least for prototyping.
- Can be hard to debug "Everything happens at once" is brilliant, but can also be hard to debug. I tend to rely on “printf-debugging“and removing all constraints and then put them back one after another (or testing after adding each single constraint).
- For more complex problems: must use search heuristics (or remodel) Getting the heuristics right is (still) “an art, not a science”.
There are some Black Box solvers/heuristics, but - IMHO - they need to be tweaked as well. Examples: - Gecode (v 4.0): AFC variants, Activity-Based - Choco 3: Activity-Based, Impact-Based - or-tools: Impact-Based - ILOG CP Optimizer: recommends to use no labeling at all first
Question: Is black box solvers a realistic/interesting/feasible goal for CP research?
What I (still) don't like about CP
Nifty language features I
Some of these features are not unique to CP, but are very handy.Many are just syntactic sugar.
- “syntactic sugar is everything” (hakank) If you want to make me happy, implement as many of these as possible. :-)
Some may be possible only for new dedicated CP languages or DSL.
- element as x[y] = z where x, y, and z (or at least y) are decision variables
- general matrix element Gecode and Choco2 has some support for this
Nifty language features II
- “exists”
Supported by MiniZinc and AMPL+CP. Useful when the range in a forall loop is dynamic; or instead of declaring a temporary decision variable (might give worse propagation).
Small example (MiniZinc): % z is a decision variable used elsewhere exists(i in 1..n) ( z = i /\ forall(j in i+1..n) ( x[j] = 0) )
- array/set comprehension % MiniZinc forall(i in 1..n) ( alldifferent([x[i,j] | j in 1..n where cost[i,j] > 0])
)
Nifty language features III
- “external loops” Which all host languages have (except some Prolog's). This is one of the feature I miss most in MiniZinc, i.e. the possibilty to do simple for loops, e.g. to create temporary variables, counting etc. - if-then-else on decision variables as well as non-decision variables
Example: AIMMS and (sometimes) AMPL.
In most other CP system one have to use reifications or dedicated methods like: ifThenElse(condition,thenclause, elseclause) or % MiniZinc (condition → then clause) /\ (not(condition) → else clause)
Choco3 - current beta version - only support half-reification which is a nuisance. (See earlier slide.)
- predicates (functions) Is – surprisingly - not supported in some of the high level systems such as AMPL, OPL and Essence'.
- set variables Sometimes extremely useful. (I definitely respect the complexity of this.)
- Systematically testing all variable and value heuristics It would be nice to have a simple way of systematically testing all variable + value heuristics on a model, e.g. with a simple flag.
I haven't seen this in any system yet, and in some it's quite easy to implement.
Nifty language features IV
CP bloggers
There are few CP bloggers (tweeters etc) compared to the OR bloggers.
- Jean-Charles Regin/Pierre Schaus: "CP is fun" http://cp-is-fun.blogspot.com/
- Jacob Feldman: "CP Standardization Blog" http://cpstandard.wordpress.com/
- Helmut Simonis: "CP Applications Blog" http://hsimonis.wordpress.com/
- Hakan Kjellerstrand: My Constraint Programming Blog http://www.hakank.org/constraint_programming_blog/
Some OR people that sometimes blog about CP. (See next slide.)
Here are some great OR-bloggers that sometimes mention CP:
- Mike Trick: "Michael Trick’s Operations Research Blog" http://mat.gsia.cmu.edu/blog/
- Jean-Francois Puget: "IT Best Kept Secret Is Optimization" https://www.ibm.com/developerworks/community/blogs/jfp/?lang=en
- Erwin Kalvelagen: "Yet Another Math Programming Consultant" http://yetanothermathprogrammingconsultant.blogspot.com/
- Paul Rubin: “OR in an OB World” http://orinanobworld.blogspot.com
OR people that sometimes blog about CP
all_different_except_0: decomposition in different CP systems
Implementation of all_different_except_0 in different CP systems:
- proxy for “ease of modelling”
- overloading of operators (if possible)
- logical operators
- reification
Note: It's not the only way to encode this constraint.
all_different_except_0: decomposition
G12 MiniZinc
forall(i, j in 1..length(x) where i < j) ( (x[i] > 0 /\ x[j] > 0) -> x[i] != x[j])
Comet
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]);}
all_different_except_0: decomposition
Choco2
for(int i = 0; i < v.length; i++) { for(int j = i+1; j < v.length; j++) { m.addConstraint(ifThenElse( and( gt(v[i], 0), gt(v[j], 0) ), neq(v[i], v[j]), TRUE) ); }}
all_different_except_0: decomposition
JaCoP
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], 0), new XneqC(v[j], 0) ), new XneqY(v[i], v[j]) ) ); }}
all_different_except_0: decomposition
JaCoP/Scala
for(i <- 0 until y.length; j <- 0 until i) { val b = new BoolVar("b") b <=> AND((y(i) #\= 0), (y(j) #\= 0)); b -> (y(i) #\= y(j))}
all_different_except_0: decomposition
Gecode
for(int i = 0; i < x.size(); i++) { for(int j = i+1; j < x.size(); j++) { rel(space, (x[i] != 0 && x[j] != 0) >> (x[i] != x[j]), icl); }}
all_different_except_0: decomposition
Gecode/R
n = x.lengthb1_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 } }
all_different_except_0: decomposition
ECLiPSe CLP
alldifferent_except_0(Xs) :- dim(Xs, [Len]), labeling(Xs), ( for(I, 1, Len) * for(J, 1, Len), param(Xs) do ( I \= J, Xs[I] #\= 0, Xs[J] #\= 0 ) -> Xs[I] #\= Xs[J] ; true ).
all_different_except_0: decomposition
SICStus Prolog
alldifferent_except_0(Xs) :- ( foreach(X,Xs) do indomain(X)), ( foreach(XI,Xs), count(I,1,_), param(Xs) do ( foreach(XJ,Xs), count(J,1,_), param(I,XI) do I < J, XI #\=0, XJ #\=0 -> XI #\= XJ ; true ) ).
all_different_except_0: decomposition
Essence'
forall i,j : int(1..n) . ( (i != j) => (((x[i] != 0) /\ (x[j] != 0)) => (x[i] != x[j]))),
all_different_except_0: decomposition
G12 Zinc
forall(i,j in index_set(x) where i != j) ( (x[i] > 0 /\ x[j] > 0) -> x[i] != x[j] )
all_different_except_0: decomposition
or-tools/Python
n = len(a)for i in range(n): for j in range(i): s.Add((a[i] != 0) * (a[j] != 0) <= (a[i] != a[j]))
all_different_except_0: decomposition
or-tools/Java
int n = a.length;for(int i = 0; i < n; i++) { for(int j = 0; j < i; j++) { IntVar bi = s.makeIsDifferentCstVar(a[i], 0); IntVar bj = s.makeIsDifferentCstVar(a[j], 0); IntVar bij = s.makeIsDifferentCstVar(a[i], a[j]); solver.addConstraint( s.makeLessOrEqual( s.makeProd(bi, bj).var(), bij)); }}
all_different_except_0: decomposition
or-tools/C#
int n = a.Length;for(int i = 0; i < n; i++) { for(int j = 0; j < i; j++) { s.Add((a[i] != 0) * (a[j] != 0) <= (a[i] != a[j])); }}
all_different_except_0: decomposition
AIMMS+CP
CONSTRAINT: identifier : CAllDifferentExcept0 index domain : (i,j) | i < j definition : if (x(i) <> 0 and x(j) <> 0) then x(i) <> x(j) endif;
all_different_except_0: decomposition
AMPL+CP
s.t. alldifferent_except0{i in dom,j in dom: i < j}: (x[i] > 0 && x[j] > 0) ==> (x[i] != x[j]);
all_different_except_0: decomposition
B-Prolog
alldifferent_except_0(Xs) :- Len @= Xs^length, foreach(I in 1..Len, J in 1..Len, (I #\=J #/\ Xs[I] #\= 0 #/\ Xs[J] #\= 0) #=> (Xs[I] #\= Xs[J])).
all_different_except_0: decomposition
Choco 3 (beta version)BoolVar[] b = VariableFactory.boolArray("b_"+i+"_"+j, 3, solver);solver.post(IntConstraintFactory.implies(b[0], (IntConstraintFactory.arithm(v[i], "!=", c))));solver.post(IntConstraintFactory.implies(VariableFactory.not(b[0]), (IntConstraintFactory.arithm(v[i], "=", c))));solver.post(IntConstraintFactory.implies(b[1], (IntConstraintFactory.arithm(v[j], "!=", c))));solver.post(IntConstraintFactory.implies(VariableFactory.not(b[1]), (IntConstraintFactory.arithm(v[j], "=", c))));solver.post(IntConstraintFactory.implies(b[2], (IntConstraintFactory.arithm(v[i], "!=", v[j]))));solver.post(IntConstraintFactory.implies(VariableFactory.not(b[2]), (IntConstraintFactory.arithm(v[i], "=", v[j]))));ALogicTree t = Node.implies(Node.and(Literal.pos(b[0]),
Literal.pos(b[1])), Literal.pos(b[2]) );solver.post(IntConstraintFactory.clauses(t, solver));
all_different_except_0: decomposition
Numberjack
return [ ( ((a != 0) & (b != 0)) <= (a != b ) ) for a, b in pair_of(x)]
all_different_except_0: decomposition
OscaR (Scala)
for(i <- 0 until y.length; j <- 0 until i) { cp.add( ((y(i) !== 0) && (y(j) !== 0)) ==> (y(i) !== y(j)) )}
all_different_except_0: decomposition
JSR-331
for(int i = 0; i < v.length; i++) { for(int j = i+1; j < v.length; j++) { Constraint c1 = p.linear(v[i],"!=", 0); Constraint c2 = p.linear(v[j],"!=", 0); Constraint c3 = p.linear(v[i],"!=", v[j]); p.postIfThen(c1.and(c2), c3); }}
all_different_except_0: decomposition
Answer Set Programming (“related paradigm”)
#const n = 6.#const m = 9.
values(0..m).ix(1..n).
% unique indices of x, 1..n1 { x(I, Val) : values(Val) } 1 :- ix(I).
% alldifferent except 0:% If Val > 0 then there must be 0..1 % occurrences of Val in x.{ x(I, Val) : ix(I) } 1 :- values(Val), Val > 0.