Constraint Programming: My View Hakan Kjellerstrand http://www.hakank.org/index_eng.html My Constraint Programming Blog: http://www.hakank.org/constraint_programming_blog/ Overview - Presentation of me - What is Constraint Programming - why is it so fascinating? - Key concepts in CP - Examples of modelling with code (G12 MiniZinc and or-tools) - Questions Hakan Kjellerstrand (hakank@gmail.com) - http://www.hakank.org/index_eng.html (on the web since 1995) - Software Developer at Bokus (http://www.bokus.com/), Swedish on-line book store. 1997-2001, 2005-today. - Software developer since 1996, mostly web/internet/dot.com - Technical Writer and support, Swedish word processor Cicero 1987–1994 - Malmoe General Hospital, EDB department etc 1982-1987 - Education: Political Science, Philosophy and Computer Science Search for “Hakan Kjellerstrand”; that's me. Constraint Programming - Pure private interest, “private researcher” - Checked out CLP in 1998 and later 2001 - 2007 autumn: Mathematical Programming/Operations Research - 2008 February: Found G12 MiniZinc (blogged at Swedish blog) - Late 2008: Started “My Constraint Programming Blog” - Have now tested about 18 CP systems, 15 of them in public (bug reports, suggestions, examples) - At least one academic paper in the pipeline (as co-author) Other interests: - programming languages - machine learning/data mining, symbolic regression - magic (though not performing any more) Constraint Programming systems MiniZinc : http://hakank.org/minizinc/ Zinc : http://hakank.org/minizinc/zinc.html Choco : http:/hakank.org/choco/ JaCoP : http://hakank.org/JaCoP/ JaCoP/Scala : http://hakank.org/jacop/jacop_scala.html Gecode/R : http://hakank.org/gecode_r/ Comet : http://hakank.org/comet/ Gecode : http://hakank.org/gecode/ ECLiPSe CLP : http://hakank.org/eclipse// Tailor/Essence' : http://hakank.org/tailor/ SICStus Prolog : http://hakank.org/sicstus/ or-tools/Python : http://hakank.org/or-tools/#python or-tools/Java : http://hakank.org/or-tools/#java or-tools/C# : http://hakank.org/or-tools/#csharp Answer Set Programming : http://hakank.org/asp/ 861 models 39 models 20 models 18 models 38 models 27 models 166 models 162 models 176 models 29 models 150 models 202 models 36 models 107 models 84 models Common constraint models: http://hakank.org/common_cp_models/ What is CP used for? In industry CP is used for (among much other things): - scheduling and resource allocation - staff rostering - complex configuration problems - DNA sequencing - vehicle routing - analysis of analog circuits - ... - in general: combinatorial (optimization) What do I do with CP? - Understanding CP/OR concepts * bin packing, seating problems, TSP, ... - Understanding/experimenting with math/CS concepts * clique, hitting set, graph related, ... - Exhausting/checking combinatorial structures * 17x17 problem, Graeco-Latin (magic) squares, ... - Modelling grid/pencil games * Sudoku, KenKen, KillerSudoku, Strimko, Rogo, ... - Recreational math/CS puzzles * 8809 = 6 problem, alphametic problems, ... - Planning problems (optimal steps for a plan) * 15-puzzle, M12, etc. My main interests and goals with CP Main interest in CP: - learning CP - modelling - testing CP systems - global constraints (decompositions) Method: - Explore (testing a lot of CP systems) - Exploit (modelling) - Explain (blogging) In general: - trying to make CP more known - solve specific problems What is CP? Some key concepts Techniques: - Constraints (CSP, constraint satisfaction problem) - Constraint store - Search tree - Combinatorial optimization - Decision variables (vs. parameters) - Domains (often finite) - Propagator, special algorithm for a constraint - Fix point Modelling: - Declarative - Global constraints (alldifferent, element, etc) - Decomposition (high level version of a global constraint) - Search strategies, to guide traversing the search tree - Reifications - Bi-direction (multiple flow patterns) - Channelling/dual model - Symmetry breaking Main principle of CP (simplified) - All variables are declared with a DOMAIN (often finite domain), including arrays, matrices, etc. - The solver PROPAGATES the CONSTRAINTS and variables in the model until: * a FIXPOINT is reached (no changes in the domains) * or assign the variables in the search tree, perhaps via backtracking - If a DOMAIN of a variable is empty then the assignment is illegal and the solver has to backtrack. Time for some code! G12 MiniZinc for high level concepts: - http://www.g12.cs.mu.oz.au/minizinc/ - http://hakank.org/minizinc/ or-tools (mostly Python and C#): - http://code.google.com/p/or-tools/ - http://hakank.org/or-tools/ Sudoku 4x4 4 _ 3 1 _ _ _ _ _ _ _ _ 4 1 _ 2 Constraints: - all rows must have different digits - all columns must have different digits - all boxes (2x2) must have different digits Let's see how this can be stated in a CP system. Sudoku 4x4 Constraints: - alldifferent(each ROW) - alldifferent(each COLUMN) - alldifferent(each BLOCK) % each 2x2 block Where alldifferent(ARRAY) can be defined as a DECOMPOSITION in MiniZinc: predicate alldifferent(array[int] of var int: x) = forall(i, j in 1..length(x) where i < j) ( x[i] != x[j] ) ; Sudoku 4x4 – MiniZinc code % init of hints, search, and output missing int: r = 2; int: n = r*r % declare decision variables array[1..n, 1..n] of var 1..n: x; constraint % rows forall(i in 1..n) (alldifferent([x[i,j] | j in 1..n])) /\ % columns forall(j in 1..n) (alldifferent([x[i,j] | i in 1..n])) /\ % blocks forall(i in 0..r-1,j in 0..r-1) ( alldifferent([x[r,c] | r in i*r+1..i*r+r, c in j*r+1..j*r+r]) ) ; Sudoku 4x4 – or-tools/Python block_size = 2; line_size = block_size ** 2 line = range(0, line_size);block = range(0, block_size) x = {} for i in line: % declare decision variables for j in line: x[(i, j)] = s.IntVar(1, line_size) for i in line: % rows s.Add(s.AllDifferent([x[(i,j)] for j in line])) for j in line: % columns s.Add(s.AllDifferent([x[(i,j)] for i in line])) for i in block: % blocks for j in block: one_block = [] for di in block: for dj in block: one_block.append(x[(i * block_size + di, j * block_size + dj)]) s.Add(s.AllDifferent(one_block)) Sudoku 4x4 – or-tools/C# int bs = 2; // Block size IEnumerable<int> BLOCK = Enumerable.Range(0, bs); int n = bs * bs; // Grid size IEnumerable<int> RANGE = Enumerable.Range(0, n); IntVar[,] x = s.MakeIntVarMatrix(n, n, 1, n, "x"); foreach(int i in RANGE) { s.Add((from j in RANGE // Rows select x[i,j]).ToArray().AllDifferent()); s.Add((from j in RANGE // Columns select x[j,i]).ToArray().AllDifferent()); } foreach(int i in BLOCK) { // Blocks foreach(int j in BLOCK) { s.Add((from di in BLOCK from dj in BLOCK select x[i*bs+di, j*bs+dj] ).ToArray().AllDifferent()); } } Sudoku 4x4 – propagation 4 _ _ _ 3 1 _ _ _ _ _ _ 4 1 _ 2 Solution (unique) 4 2 3 1 1 3 2 4 2 3 1 4 4 1 3 2 How does a CP solver reach this solution? Sudoku 4x4 – propagation example (simplified) 4 3 1 1234 1234 1234 1234 1234 1234 1234 1234 1234 4 1 2 1234 Add DOMAINS (1..4) to all unknown variables. Hints are FIXED already. Now we will propagate the three alldifferent constraints: - alldifferent(ROW) - alldifferent(COLUMN) - alldifferent(BLOCK) This is a very simplified example. Real CP systems use more intelligent propagation. Sudoku 4x4 – simple propagation example 4 3 1 2 1234 1234 1234 1234 1234 1234 1234 1234 4 1 2 1234 Cell (1,1): Fixed value (4). Cell (1,2): Reduce: - remove 4 (row, block) - remove 1 (column, block) - remove 3 (block) → Single value: 2 Sudoku 4x4 – simple propagation example 4 3 1 2 1234 1234 1234 1234 1 3 1234 1234 1234 4 1 2 1234 Cell (1,3): Reduce: - remove 4 (row, column) - remove 2 (row) → {1 3} Sudoku 4x4 – simple propagation example 4 3 1 2 1 3 1234 3 1234 Cell (1,4): Reduce: - remove 4 (row) - remove 1 (column) - remove 2 (column) →3 Note: Here we don't go back to fix cell (1,3). 1234 1234 1234 1234 4 1 2 1234 Cell (2,1): fixed (3) Cell (2,2): fixed (1) Sudoku 4x4 – simple propagation example 4 3 1 2 1234 1234 1234 1234 1 3 2 3 1234 4 1 2 1234 Cell (2,3): Reduce: - remove 3 (row) - remove 1 (row) - remove 4 (column) →2 Sudoku 4x4 – simple propagation example 4 3 1 2 1234 1234 1234 1234 1 3 2 3 4 4 1 2 1234 Cell (2,4): Reduce: - remove 3 (row) - remove 1 (row, column) - remove 2 (row, column) →4 Sudoku 4x4 – simple propagation example 4 3 1 2 2 1234 1234 1234 1 3 2 3 4 4 1 2 1234 Cell (3,1): Reduce: - remove 4 (row, column) - remove 1 (row) - remove 3 (column) →2 Sudoku 4x4 – simple propagation example 4 3 1 2 2 1234 3 1234 1 3 2 3 4 4 1 2 1234 Cell (3,2): Reduce: - remove 1 (row, column, block) - remove 2 (row) - remove 4 (row) →3 Sudoku 4x4 – simple propagation example 4 3 1 2 2 1234 3 1234 1 3 3 2 4 1 2 1234 4 Cell (3,3): Fixed. Cell (3,4): Fixed. Sudoku 4x4 – simple propagation example 4 3 1 2 2 1 3 1234 1 3 2 3 4 4 1 2 1234 Cell (4,1): Reduce - remove 2 (row) - remove 4 (column) - remove 3 (column) →1 Sudoku 4x4 – simple propagation example 4 3 1 2 2 1 1 3 2 3 4 3 4 4 1 2 1234 Cell (4,2): Reduce - remove 1 (row) - remove 2 (row) - remove 3 (column) →4 Sudoku 4x4 – simple propagation example 4 3 1 2 1 3 3 2 4 Cell (4,3): Reduce - remove 2 (row, block) - remove 4 (column, block) – - remove 1 (block) →3 Cell (4,4): Fixed 2 2 1 3 4 4 1 2 3 Are we finished? No! There is still a variable/cell with no single assignment, i.e. Cell (1,3). Sudoku 4x4 – simple propagation example 4 3 1 2 1 Cell (1,3): Reduce - remove 3 (row, block) →1 3 2 4 And now all variables has been assigned to a single value. 2 1 3 4 4 1 2 3 Sudoku 4x4 – simple propagation example 4 2 3 1 1 3 2 4 2 3 1 4 4 1 3 2 … and we got a solution! It is unique – as a Sudoku should be. Sudoku 4x4: Points to take home - Domain reduction is one of the key principles to CP. - Using search heuristics (strategies) influences the order of variable/value selection. Another model: Minesweeper Minesweeper – in this version – is a simple grid problem: ..2.3. 2..... ..24.3 1.34.. .....3 .3.3.. - Each number represents how many bombs there are in the nearby cells - The “.” (dot) represents an unknown cell: either a bomb or empty cell. - Problem: Where are the bombs? Minesweeper – MiniZinc version (the setup) int: X = -1; % the unknowns % >= 0 for number of mines in the neighbourhood array[1..r, 1..c] of -1..8: game; % decision variables: 0/1 for no bomb/bomb array[1..r, 1..c] of var 0..1: mines; % problem instance int: r = 6; % rows int: c = 6; % column game = array2d(1..r, 1..c, [ X,X,2,X,3,X, 2,X,X,X,X,X, X,X,2,4,X,3, 1,X,3,4,X,X, X,X,X,X,X,3, X,3,X,3,X,X, ]); Minesweeper – MiniZinc version (constraint) % game[1..n, 1..n]: the given hints % mines[1..n, 1..n]: 0/1 where 1 represent a bomb % X: -1 represents the unknown constraint forall(i in 1..r, j in 1..c) ( ( (game[i,j] >= 0 ) → % the hint number must be the number % of all the surrounded bombs game[i,j] = sum(a,b in {-1,0,1} where i+a > 0 /\ j+b > 0 /\ i+a <= r /\ j+b <= c ) (mines[i+a,j+b]) ) /\ % if a hint, then it can't be a bomb (game[i,j] > X -> mines[i,j] = 0) ) ; Minesweeper – MiniZinc version Declarative aspect of Constraint Programming. The ideal: - Just state the requirements (the constraints) - It's now up to the CP solver to find the solution. % ... % the hint number must be the number % of all the surrounded bombs game[i,j] = sum(a,b in {-1,0,1} where i+a > 0 /\ j+b > 0 /\ i+a <= r /\ j+b <= c ) (mines[i+a,j+b]) % ... Minesweeper - Solution ..2.3. 2..... ..24.3 1.34.. .....3 .3.3.. 100001 010110 000010 000010 011100 100011 % 1: Bomb, 0: no bomb Minesweeper – or-tools/Python mines = {} # define decision variables for i in range(r): for j in range(c): mines[(i,j)] = solver.IntVar(0,1) # constraints for i in range(r): for j in range(c): if game[i][j] >= 0: solver.Add( game[i][j] == solver.Sum([mines[i+a,j+b] for a in S for b in S if i + a >=0 and j + b >=0 and i + a < r and j + b < c])) if game[i][j] > X: # This cell cannot be a mine solver.Add(mines[i,j] == 0) Minesweeper – or-tools/C# // constraints for(int i = 0; i < r; i++) { for(int j = 0; j < c; j++) { if (game[i,j] >= 0) { var tmp = from a in S from b in S where i + a >= 0 && j + b >= 0 && i + a < r && j + b < c select(mines[i+a,j+b]); solver.Add(tmp.ToArray().Sum() == game[i,j]); } if (game[i,j] > X) { solver.Add(mines[i,j] == 0); } } } Minesweeper – other implementations Implementations of Minesweeper using the same (as possible) approach: http://hakank.org/common_cp_models/#minesweeper Answer Set Programming: minesweeper.lp Choco: MineSweeper.java Comet: minesweeper.co ECLiPSE: minesweeper.ecl Gecode/R: minesweeper.rb Google CP Solver/C#: minesweeper.cs Google CP Solver/Java: Minesweeper.java Google CP Solver/Python: minesweeper.py JaCoP: MineSweeper.java JaCoP/Scala: Minesweeper.scala MiniZinc: minesweeper.mzn SICStus: minesweeper.pl Tailor/Essence': minesweeper.eprime Zinc: minesweeper.zinc Minesweeper – search strategies (MiniZinc) To ensure the best/good performance, search strategies (heuristics) are used: - variable selection: which variable to use first_fail, input_order, most_constrained, etc - value selection: which value to try on the selected variable indomain_min, indomain_max, etc - the variables (as array) to work on - first_fail/indomain_min is often good to start with Selecting the best strategy is an art, not an science, and often requires much experimentation. solve :: int_search( [mines[i,j]|i in 1..r,j in 1..c], first_fail, indomain_min, complete) satisfy; Minesweeper – search (or-tools/Python) - variable selection: INT_VAR_DEFAULT, CHOOSE_RANDOM, etc - value selection: INT_VALUE_DEFAULT, ASSIGN_MIN_VALUE - the variables, as array, to work on (here: mines) - INT_VAR_DEFAULT/INT_VALUE_DEFAULT is often good to start with db = solver.Phase([mines[(i,j)] for i in range(r) for j in range(c)], solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT) Key concepts in CP modelling Some of the most important - and IMHO the most fascinating concepts in CP regarding the modelling: - Global constraints - Element - Reification (logical relation) - Bi-directedness (multiple flow pattern) - Channelling (dual views) - 1/N/All solutions - Symmetry breaking Global constraints - “Patterns” in modelling problems - Conceptual tool - Much research in effective propagators - Global Constraint Catalog, 364 global constraints http://www.emn.fr/z-info/sdemasse/gccat/index.html Some of the most common global constraints: - All different: all elements must be distinct - All different except 0: all elements != 0 must be distinct - Element: indexing with decision variables - Global Cardinality Count: count occurrences of values in array - Decrease/Increase: sortedness - Regular: finite state machine - Cumulative: for scheduling - Circuit: Hamiltonian circuit - Table: Allowed assignments http://hakank.org/minizinc/#global (~170 decompositions) Element constraint (z = x[y]) One of the most common – and important - constraint: - z = x[y], where the index (y) is a decision variable Most CP solvers don't have support for this direct syntax. Instead: - z = element(x, y) - z = x.element(y) - element(x,y,z) % MiniZinc array[1..4] of var 1..4: x; var 1..4: y; var 1..4: z; constraint z = x[y]; % % % % example: x = [4,3,1,2] y = 2 → z = 3 Element constraint (z = x[y]) // or-tools/C# solver.Add(z == x.Element(y)); % or-tools/Python solver.Add(z == solver.Element(x, y)) // or-tools/Java solver.addConstraint( solver.makeEquality(z, solver.makeElement(x, y).var())); Reification Reification: truth values of constraints C => b If constraint C holds then BoolVar b = 1 else b = 0. Some examples (MiniZinc syntax): x and y are var int (domain 1..10) b1 and b2 are var boolean (x = y) -> (b1 = 1) (x > 4) -> (y <= 4) b1 <-> not(b2) (x = 1 \/ y = 1) <-> (b1 = b2) Reification – alldifferent_except_0 (MiniZinc) The global constraint alldifferent_except_0 is quite useful: All variables that are != 0 must be distinct. Applications: - an NxN grid which should contain N distinct values, e.g. one per row and column. Other cells are set to 0. Here is a decomposition using reification in MiniZinc: forall(i,j in index_set(x) where i < j) ( (x[i] != 0 /\ x[j] != 0) -> x[i] != x[j] ) Reification – alldifferent_except_0 (or-tools/C#) In or-tools we have to use Boolean logic. public static void AllDifferentExcept0(Sol s, IntVar[] a) { int n = a.Length; for(int i = 0; i < n; i++) { for(int j = 0; j < i; j++) { // (a[i] != 0 /\ a[j] != 0) → (a[i] != a[j]) s.Add((a[i] != 0) * (a[j] != 0) <= (a[i] != a[j])); } } } Reification - differences between or-tools' interfaces or-tools/Python: for i in range(n): for j in range(i): s.Add((a[i] != 0) * (a[j] != 0) <= (a[i] != a[j])) or-tools/Java: 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]); s.addConstraint( s.makeLessOrEqual( s.makeProd(bi, bj).var(), bij)); } } } “Bi-direction” of variables Cf Prolog where a variable may be either input, output or both (“multiple flow pattern”). Simple example: - converting a number (variable) TO/FROM its digits (array) given a base [Later note: After the talk Nicolas Beldiceanu mentioned that this concept is better known as “reversibility” in Prolog.] “Bi-direction” of variables (or-tools/C#) - converting a number (variable) TO/FROM its digits (array) given a base private static Constraint ToNum(IntVar[] a, % digits IntVar num, % number int bbase) { int len = a.Length; IntVar[] tmp = new IntVar[len]; for(int i = 0; i < len; i++) { // represent a[i] with exponents in base tmp[i] = (a[i]*(int)Math.Pow(bbase,(len-i-1))).Var(); } % returns “success”/”fail” of the Constraint return tmp.Sum() == num; } // num = 532 a = {5,3,2} tmp = [5*100,3*10,2*1] “Bi-direction” of variables (or-tools/C#) - converting a number (variable) TO/FROM its digits (array) given a base int n = 5; IntVar[] x = solver.MakeIntVarArray(n,0, 9); IntVar n = solver.MakeIntVar(0, 99999); Solver.Add(ToNum(x, n)); // FROM number TO array → x = {1,3,2,5,4} solver.Add(n == 13254); // OR // TO number FROM the digits -> n = 13254 solver.Add(x[0] == 1); solver.Add(x[1] == 3); solver.Add(x[2] == 2); solver.Add(x[3] == 5); solver.Add(x[4] == 4); “Bi-direction” of variables – “dual view” - converting a number (variable) TO/FROM its digits (array) given a base We can now add constraints on either n or x: a “dual view”. Example: int n = 5; IntVar[] x = solver.MakeIntVarArray(n, 0, 9); IntVar n = solver.MakeIntVar(12345, 99999); Solver.Add(ToNum(x, n)); Solver.Add(x.AllDifferent()); solver.Add(x[3] == 4); solver.Add(n >= 50000); The ability to add constraints quite easily to a model is one of the strengths of CP. 1/N/All solutions Most CP solvers can show any number of solutions. - First solution: e.g. just any valid solution (schedule/seating/etc). - N solutions, e.g. N=2 to check if/ensure that there are > 1 solutions. * Sudoku: must be unique solution (model is wrong if 2 solutions are printed) - All solutions: e.g. as debugging aid * N-queens for N=8 should yield 92 solutions Symmetry breaking A common technique to reduce the search tree is to use SYMMETRY BREAKING. Some standard constraints for symmetry breaking: - increasing: the array must be sorted - lex_less(x, y): array x is lexicographic less then array y - lex2(matrix): all rows and columns must be in lexicographic order Special forms: - x[0] < x[k-1] So, why do I like Constraint Programming? - Excellent tool for certain type of problem solving - Modelling language is often high or very high - Declarative - Reification - Global constraints (efficiency, conceptual tool) - Bi-direction of predicates (“dual view”) - Can be very fast - Can show 1, 2, N, or all solutions But - like any programming paradigm - is no silver bullet. - It takes time to model a problem with much experiments, especially to get search strategies right: art not science - Debugging can be harder than in non-CP programming. What have I not talked about? Advanced features in or-tools such as - Large Neighbourhood Search - Vehicle Routing - Min/Max-Cost Flow - Graph algorithms - etc. Advanced features in CP in general: - how search heuristics and propagation really works - set variables (can be very handy) - advanced examples - how a CP solver works under the hood Thank you! - Questions? Thanks to Laurent Perron and the or-tools group for a great system! Hakan Kjellerstrand http://www.hakank.org/index_eng.html