Part IV · trunk C · constraint programming and SAT · spine
Variables, domains, constraints — and no relaxation. Remove what cannot be part of a solution, search over what remains, undo, repeat.
needs 00~1½ h slides + 3 h lablab: uv run co lab 17
17 · overview
CSPs, and how "prune" differs from "relax and bound".
Node, arc, path, k; AC-3 and AC-4; bounds vs domain consistency; what each costs.
Propagators, the fixpoint, idempotence, trailing.
The CP loop, first-fail, and the two numbers that describe everything.
Words with a dotted underline are glossary terms: click one for its definition. G lists every term in the unit, N shows the notes. Optional extra: interactive figures, linked from the slides they support by a small ▸ chip; skip them freely.
section 1
Feasibility first; an objective, if any, is just another constraint.
17 · formalism
Variables $x_1, \dots, x_n$, finite domains $D_i$, and constraints $C_j \subseteq D_{i_1} \times \cdots \times D_{i_k}$ over subsets of the variables. A solution assigns every variable a value from its domain so that every constraint holds.
| MIP (Part II) | CP (Part IV) | |
|---|---|---|
| core move | relax, then bound | prune domains |
| strength from | tight LP relaxations | strong propagators |
| proves optimality by | dual bound meets incumbent | exhausting the tree |
| easy for it | weighted sums, flows, covering | alldifferent, scheduling, puzzles |
| hard for it | big-M disjunctions, permutations | weak bounds on sums |
Neither dominates, which is why unit 21's hybrid exists.
section 2
How much local reasoning, at what price.
17 · consistency
Higher levels prune more and cost exponentially more: $k$-consistency takes $O(n^k d^k)$ time.
| algorithm | time | idea |
|---|---|---|
| AC-3 (Mackworth 1977) | $O(e d^3)$ | queue of arcs; revise |
| AC-4 (Mohr & Henderson 1986) | $O(e d^2)$ | support counters |
| AC-2001 (Bessière & Régin) | $O(e d^2)$ | AC-3 plus "last support" pointers |
| path consistency | $O(n^3 d^3)$ | rarely used |
If the constraint graph is a tree, arc consistency alone makes search backtrack-free. More generally, width $w$ plus strong $(w+1)$-consistency suffices, which is unit 16's treewidth again.
17 · consistency · example
$x, y, z \in \{1, 2, 3\}$ with $x < y$ and $y < z$. Four arcs, one per direction: $(x,y)$, $(y,x)$, $(y,z)$, $(z,y)$. The queue starts with all four.
| # | revise | domain before → after | push |
|---|---|---|---|
| 1 | $x$ against $y$ | $x$: 123 → 12 | |
| 2 | $y$ against $x$ | $y$: 123 → 23 | $(x,y)$ |
| 3 | $y$ against $z$ | $y$: 23 → 2 | |
| 4 | $z$ against $y$ | $z$: 123 → 3 | $(y,z)$ |
| 5 | $x$ against $y$ | $x$: 12 → 1 | $(y,x)$ |
| 6 | $y$ against $z$ | $y$: 2 → 2 | |
| 7 | $y$ against $x$ | $y$: 2 → 2 |
Revise 1: $x = 3$ has no $y$ above it: gone. Revise 2: $y = 1$ has no $x$ below it: gone. Because $y$ shrank, the arc that reads $y$ to prune $x$ goes back on the queue.
Revise 5 uses the news from revise 3 ($y = 2$): $x = 2$ lost its support.
After 7 revise calls the queue is empty: $x = 1$, $y = 2$, $z = 3$, solved without search.
This constraint graph is a path, a tree, so Freuder's theorem already promised no search would be needed.
17 · consistency
Every value has a support on every edge, so nothing is pruned. No 2-colouring exists.
Arc consistency is local. It checks one constraint at a time and never looks at a cycle. Each propagator is sound (it never removes a solution), and the fixpoint is sound, but it is not complete.
1 . . .
. . 3 .
. 4 . .
. . . 2
133 NotEqual propagations reach a fixpoint where every cell is a singleton: solved without search. Inkala's 9×9 "hardest sudoku" needs 3 598 nodes.
Search fills the gap between "consistent" and "solved". Stronger propagation shrinks the tree, at a price per node.
17 · consistency
Every value in every domain belongs to some satisfying tuple of the constraint.
Only the min and max of each domain need supports, often with the others relaxed to intervals. Interior holes are ignored.
On a linear constraint $\sum a_i x_i \le b$, reasoning from bounds costs $O(n)$. Domain consistency on an equality $\sum a_i x_i = b$ is NP-hard: it contains subset sum.
Lab step 3's LinearLe removes every value $a$ of $x_i$ with
$$a_i\, a > b - \sum_{j \ne i} \min_{v \in D_j} a_j v$$For a single inequality this is exactly domain consistency, since the others' best values are always available. The test checks it against brute-force supports. LinearEq, as two inequalities, is only bounds-like, and the test only asks it to be sound.
The general design question of Part IV: how strong a propagator, for what cost per call?
17 · consistency · example
$x + y = 6$, with $D_x = \{1, 2, 3, 4, 5\}$ and $D_y = \{1, 2, 4, 5\}$: $y$ has a hole at 3.
$x \le 6 - \min y = 5$ and $x \ge 6 - \max y = 1$. Same for $y$. Nothing is removed.
| $x$ | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| needs $y =$ | 5 ✓ | 4 ✓ | 3 ✗ | 2 ✓ | 1 ✓ |
$x = 3$ has no support: domain consistency removes it. Bounds consistency can't see a hole in the middle.
Same domains, $x + y \le 3$. Each variable's best partner is the other's minimum, 1, so $x \le 2$ and $y \le 2$: $D_x = \{1,2\}$, $D_y = \{1,2\}$.
Every remaining value has a support (its partner at 1), so this is already domain consistent: for one inequality, bounds reasoning is exact.
For the equation, checking every value means solving "is there $y$ with $a + y = 6$?"; with many variables that question is subset sum, hence NP-hard.
section 3
Propagators, a queue, and a trail.
17 · engine
A propagator for constraint $C$ is a function $p : D \to D$ with
Run all propagators until none changes anything. The result is the greatest common fixpoint, independent of the order (Apt 1999).
prune(dom) is pure: it reads domains and returns narrowings. Only the engine writes.Real engines add priorities (cheap propagators first), events (fixed / bounds changed / any change), and idempotence flags (Schulte & Stuckey 2008).
17 · engine
Search needs the domains of the parent node back when a branch fails. Three options (Schulte 1999):
Immutable domain tuples passed down the recursion: copying, with structural sharing. No trail, because backtracking is returning. The same node and propagation counts as the trailing engine, 3–10× slower in Python.
Trailing is what in-place mutation costs you: every write must be undoable. Copying is what immutability costs you: every write allocates.
17 · engine · example
$x, y, z \in \{1,2,3\}$ with $x \ne y$ and $y \ne z$. The trail stores (variable, old domain) before every change.
| event | trail after it | domains $x, y, z$ |
|---|---|---|
| mark $m_0$ = 0 | empty | 123, 123, 123 |
| decide $x = 1$ | $(x, 123)$ | 1, 123, 123 |
| propagate $x \ne y$ | $\dots, (y, 123)$ | 1, 23, 123 |
| mark $m_1$ = 2 | ||
| decide $y = 2$ | $\dots, (y, 23)$ | 1, 2, 123 |
| propagate $y \ne z$ | $\dots, (z, 123)$ | 1, 2, 13 |
Pop entries 4 and 3: restore $z = 123$, then $y = 23$. Trail length 2. Domains: $x = 1$, $y = \{2, 3\}$, $z = \{1,2,3\}$, exactly as after the first propagation.
Pop the remaining two: $y = 123$, $x = 123$. Empty trail, original domains.
Undo pops in reverse order, so $y$ is restored to 23 before being restored to 123. Search needs only the marks: "the branch $y = 2$ failed" is undo(m₁), then try $y \ne 2$.
section 4
Propagate, branch, repeat.
17 · search
The CP search loop:
"Fail first": pick the variable most likely to fail, so dead ends are found near the root, where they cut the most.
Nodes measure search effort; a better heuristic or stronger propagation reduces them. Propagations measure inference effort; stronger propagation increases them per node. Units 18–21 each move one against the other.
Lab then, same models in both solvers:
| model | nodes | your s | CP-SAT branches | CP-SAT s |
|---|---|---|---|---|
| queens 11, all 2 680 | 48 950 | 7.52 | 226 330 | 8.60 |
| queens 60, one | 862 | 1.61 | 38 681 | 2.37 |
| sudoku (Inkala), all | 3 598 | 1.30 | 6 538 | 0.12 |
| 3-colour G(200, 4.4/n), unsat | 120 334 | 26.5 | 3 106 | 0.16 |
| 3-colour G(300, 4.4/n), unsat | limit | 67.7 | 17 937 | 0.27 |
On queens the Python engine keeps pace. On unsatisfiable colouring CP-SAT needs 40× fewer branches, because it learns from each failure (units 20–21).
17 · lab
uv run co test 17
uv run co then 17 # the same models in CP-SAT
17 · checkpoint
Rossi, van Beek & Walsh (eds.), Handbook of CP, ch. 3–4.
Mackworth, Artificial Intelligence 8 (1977).
Freuder, JACM 29 (1982): backtrack-free search.
Schulte & Stuckey, TOPLAS 31 (2008).
18 global constraints: alldifferent by matching, cumulative · 19 search heuristics, restarts, LNS · 20 SAT and CDCL.