lesson depth
Mastery
not started · 0%

Constraint Satisfaction Problems & Arc Consistency

Variables, discrete domains, unary/binary constraints, backtracking with MRV/LCV heuristics, Forward Checking, AC-3, and constrained logit masking.

Freshness: current•16 min read•Computer Science and Programming

Key Learning Outcomes

  • ✓Formulate problem constraints as formal CSP triples and construct constraint networks.
  • ✓Implement the AC-3 algorithm to enforce arc consistency and prune incompatible domain values in polynomial time.
  • ✓Analyze modern constrained decoding engines that map grammar and regex DFAs to vocabulary logit bitmasks during autoregressive sampling.

Mental model

In standard state-space search, states are atomic, black-box entities evaluated only by domain heuristics and goal predicates. In contrast, a Constraint Satisfaction Problem (CSP) represents state using a structured factored representation: a set of variables, each with a domain of possible values, and a set of constraints restricting allowable combinations of assignments. A solution is a complete assignment that violates zero constraints.

By exposing the internal structure of states, CSP algorithms eliminate blind guessing. Rather than generating an entire trajectory before evaluating failure, an agent applies constraint propagation: deterministic domain reduction rules that prune values incompatible with existing assignments.

In contemporary production AI systems, CSP principles are the theoretical engine powering structured generation and constrained decoding. Libraries like Outlines, Guidance, and XGrammar treat JSON schemas and context-free grammars as constraint networks, compiling them into finite automata that mask out invalid vocabulary tokens at each generation step.

CSP Variable & Domain Definition
Binary Constraint Graph Construction
AC-3 Arc Consistency Pruning Queue
Backtracking with MRV Variable Ordering
Complete Consistent Assignment Discovery
Conceptual teaching model synthesized from:aima-search-planning,mackworth-ac3-1977

Learning outcomes

  • Formulate complex engineering and logistical puzzles as formal CSP triples $\langle X, D, C \rangle$.
  • Implement the AC-3 algorithm to enforce arc consistency across binary constraint networks in $O(c \cdot d^3)$ worst-case time.
  • Integrate the Minimum Remaining Values (MRV) and Least Constraining Value (LCV) heuristics into depth-first backtracking search.
  • Connect classical domain pruning to modern grammar-constrained decoding engines that compile regex and schema constraints into logit bitmasks during LLM sampling.

Theory

Formal CSP Formulation

A CSP is formally defined by three components:

text(6 lines)
1CSP = <X, D, C>
2where:
3 X = {X_1, X_2, ..., X_n} : Set of n variables
4 D = {D_1, D_2, ..., D_n} : Domains of discrete or continuous allowable values
5 C = {C_1, C_2, ..., C_m} : Constraints specifying allowable tuples of values

Each constraint $C_i = \langle \text{scope}, \text{rel} \rangle$ consists of a subset of variables and a relation defining permissible value combinations (e.g., $X_1 \ne X_2$, or $X_1 + X_2 \le 10$).

Backtracking Search & Ordering Heuristics

The standard algorithm for solving CSPs is depth-first backtracking search, which assigns values to one variable at a time and backtracks when a variable has no legal values remaining. Plain backtracking is guided by two powerful heuristics:

  1. Minimum Remaining Values (MRV) (Variable Ordering): Choose the unassigned variable with the fewest remaining legal values in its domain. Also known as the "most constrained variable" or "fail-first" heuristic, MRV detects dead-ends as high up the tree as possible.
  2. Degree Heuristic (Tie-Breaker): Choose the variable with the most constraints on other unassigned variables.
  3. Least Constraining Value (LCV) (Value Ordering): Once a variable is selected, choose the value that rules out the fewest choices for neighboring variables in the constraint graph, leaving maximum flexibility for future assignments.

Arc Consistency & The AC-3 Algorithm

Mackworth (1977) introduced the AC-3 algorithm to enforce binary arc consistency. A directed arc $(X_i, X_j)$ is arc-consistent if for every value $x \in D_i$, there exists at least one value $y \in D_j$ that satisfies the binary constraint between $X_i$ and $X_j$.

python(42 lines)
1from collections import deque
2from typing import Dict, List, Set, Tuple
3
4def ac3(
5 variables: List[str],
6 domains: Dict[str, Set[any]],
7 constraints: Dict[Tuple[str, str], Set[Tuple[any, any]]]
8) -> bool:
9 # Initialize queue with all directed arcs in the constraint network
10 queue = deque([arc for arc in constraints.keys()])
11
12 while queue:
13 xi, xj = queue.popleft()
14 if revise(xi, xj, domains, constraints):
15 if len(domains[xi]) == 0:
16 return False # Inconsistency detected: no solution exists
17 # Re-queue all arcs (xk, xi) where xk != xj
18 for xk in variables:
19 if xk != xi and xk != xj and (xk, xi) in constraints:
20 queue.append((xk, xi))
21 return True
22
23def revise(
24 xi: str,
25 xj: str,
26 domains: Dict[str, Set[any]],
27 constraints: Dict[Tuple[str, str], Set[Tuple[any, any]]]
28) -> bool:
29 revised = False
30 to_remove = set()
31 allowed_pairs = constraints.get((xi, xj), set())
32
33 for x in domains[xi]:
34 # Check if there exists ANY y in D_j such that (x, y) satisfies the constraint
35 has_support = any((x, y) in allowed_pairs for y in domains[xj])
36 if not has_support:
37 to_remove.add(x)
38 revised = True
39
40 domains[xi] -= to_remove
41 return revised
22 lines hidden

Complexity: For a network with $c$ binary constraints and maximum domain size $d$, an arc $(X_k, X_i)$ can be inserted into the queue at most $d$ times. Testing an arc takes $O(d^2)$ checks. Thus, AC-3 runs in $O(c \cdot d^3)$ time, dramatically shrinking domains before backtracking search begins.

Modern Application: Constrained Logit Masking

In contemporary generative AI, generating valid JSON, SQL, or code according to a schema is an online constraint satisfaction problem:

  • Variables: The output token positions $[t_1, t_2, \dots, t_N]$.
  • Domain: The model vocabulary $V$ (typically 32,000 to 128,000 discrete tokens).
  • Constraints: Specified by a JSON Schema, regex, or context-free grammar (CFG).

Instead of letting the model hallucinate invalid syntax and retrying post-hoc, engines like Outlines compile the schema into a Deterministic Finite Automaton (DFA). At step $k$, given the automaton's current state, the engine computes the set of legal next tokens $V_{\text{valid}} \subseteq V$ and applies an additive logit mask:

text(4 lines)
1logits[token] =
2 logits[token] if token in V_valid
3 -infinity otherwise

This enforces arc consistency dynamically at inference time: invalid tokens are pruned prior to the softmax layer, guaranteeing 100% syntactically and structurally valid outputs on the very first forward pass.

Trade-offs

| Method | Consistency Level | Time Overhead | Backtracking Frequency | Ideal Production Scenario | |---|---|---|---|---| | Naïve Backtracking | None (checks only complete assignments) | Zero pre-processing | Exponentially high | Small toy problems ($n < 10$) | | Forward Checking | 1-step lookahead (filters immediate neighbors) | Low | Low | Medium scheduling, Sudoku | | MAC (Maintaining Arc Consistency / AC-3) | Full binary arc consistency at every step | Moderate polynomial $O(c \cdot d^3)$ | Extremely low | Complex enterprise logistics, compiler register allocation | | Grammar Logit Masking (XGrammar / Outlines) | Token-level grammar state consistency | Small bitmask indexing overhead | Zero (provably zero syntax backtracking) | API tool parameter generation, typed JSON extraction |

Failure modes and misconceptions

  1. Arc Consistency is NOT Global Consistency: Passing AC-3 without an empty domain does not mean a problem has a solution. A network can be completely arc-consistent yet still have zero valid full assignments (e.g. 3-coloring an odd-cycle graph). Backtracking search is still required to verify satisfiability.
  2. Re-Queuing Omission in AC-3: When a value is pruned from domain $D_i$, failing to re-queue incoming arcs $(X_k, X_i)$ breaks propagation. Values in $D_k$ that depended on the pruned value will linger, permitting invalid states.
  3. Overhead in Lightly Constrained Spaces: In loosely constrained problems where almost any assignment works, spending $O(c \cdot d^3)$ running AC-3 before every decision point is slower than simple forward checking or random restart local search.
  4. Treating Constrained Generation as Regex Retries: Engineers often write retry loops around an LLM calling json.loads(). This approach suffers from exponential latency tail spikes. Pre-softmax logit masking completely eliminates retry overhead.
Reflect before revealing the guide

Decision scenario

You are designing an autonomous microservice that ingests unstructured medical invoices and outputs FHIR-compliant patient billing JSON records containing 40 strict nested types, date formats, and enumerated billing codes.

  • Option A: Use temperature 0.0 with prompt-engineering: "You are an expert medical coder. Always output valid FHIR JSON."
  • Option B: Wrap the LLM in an exponential backoff retry loop with 5 attempts, validating JSON syntax with Pydantic after each generation.
  • Option C: Compile the FHIR Pydantic schema into an indexed regex DFA using a grammar-constrained decoding engine (e.g. XGrammar/Outlines) that applies vocabulary logit masking during GPU generation.

Recommendation: Choose Option C. Option A produces schema validation failures on 8-12% of requests due to missing quotes or invalid enumerations. Option B introduces unpredictable p99 latency spikes and multi-dollar API waste on retries. Option C mathematically guarantees 100% schema conformance on the very first generation pass with less than 2% latency overhead.

Prerequisites & Related Concepts (2)

Private notes

0 words
Next