CDCL: Conflict-Driven Clause Learning

Extend DPLL with conflict analysis: implication graphs, the first-UIP cut, asserting clause derivation, and non-chronological backjumping — the algorithm CP-SAT's Boolean layer actually runs.

Estimated time
~18 min
Difficulty
advanced
Sources
6 sources

DPLL from the previous lesson is complete and correct, but it backtracks blindly — when it hits a contradiction, it rewinds only one step, even if the real culprit was a decision made ten levels ago. CDCL fixes this by doing detective work at the scene of the conflict: it traces exactly who caused the problem, encodes that cause as a new clause, and jumps back precisely as far as needed — no further.

Coming from the previous lesson?

In “Boolean Satisfiability and DPLL” you traced the padlock formula (AB)(¬AC)(¬B¬C)(A \lor B) \land (\neg A \lor C) \land (\neg B \lor \neg C) step by step. You saw that one decision on A=True triggered a chain of unit propagations that solved the whole instance without backtracking. This lesson asks: what happens when that chain ends in a contradiction instead of a solution? That is where CDCL takes over.

When Unit Propagation Hits a Wall

Recall how unit propagation works: every decision at a given level ripples through the formula, forcing other variables. In the padlock example, the cascade was benign — it found the solution. But consider a harder formula where the cascade ends in two clauses that both become empty simultaneously. That is a conflict.

A conflict in 5 variables

Take this 7-clause formula over variables A, B, C, D, E:

(¬AC)    (¬BD)    (¬C¬DE)    (¬C¬E)    (AB)    (¬B¬E)    (BC)(\neg A \lor C) \;\land\; (\neg B \lor D) \;\land\; (\neg C \lor \neg D \lor E) \;\land\; (\neg C \lor \neg E) \;\land\; (A \lor B) \;\land\; (\neg B \lor \neg E) \;\land\; (B \lor C)

DPLL branches: A = True (decision level 1). Unit propagation on (¬AC)(\neg A \lor C) forces C = True. No more unit clauses from A alone.

DPLL branches again: B = True (decision level 2). Unit propagation cascades:

  • (¬BD)(\neg B \lor D) forces D = True.
  • (¬C¬DE)(\neg C \lor \neg D \lor E) — both C and D are True, so ¬C and ¬D are False; only E remains → E = True.
  • (¬C¬E)(\neg C \lor \neg E) — both C=T and E=T make this clause empty (False). Conflict!
  • (¬B¬E)(\neg B \lor \neg E) — both B=T and E=T make this clause empty too. Second conflict path.

Every assignment was forced by the ones before it. But which decision actually caused the problem?

DPLL’s answer: shrug, undo B=True, try B=False. But that ignores the real cause. CDCL’s answer: build an implication graph and read the cause directly from its structure.

Step through unit propagation to build the implication graph, then watch conflict analysis trace back to the cut that yields the learned clause.

Check your understanding

In the implication graph above, what does an edge from node 'C=T' to node 'E=T' represent?

The Implication Graph and Conflict Analysis

Implication graph def.

A directed graph where each node is a variable assignment (x=v)(x = v) annotated with its decision level, and each edge (uw)(u \to w) is labeled with the clause that made ww a unit clause once uu (and any other antecedents) was assigned. Decision nodes have no incoming edges. A conflict node (written ⊗) is added when a clause becomes empty; its antecedents are all literals that together falsify that clause.

The implication graph makes visible something that plain backtracking ignores: the causal structure of a conflict. Not every node in the graph contributed to the conflict — only those that have a path to the conflict node. CDCL exploits this to learn something precise.

What to learn from a conflict. The conflict happened because a set of assignments jointly falsified some clause. If we could add a new clause to the formula that prevents exactly that combination of assignments, future search will never walk into the same dead end. This new clause is called the learned clause (or conflict clause).

How do we extract it? By drawing a cut across the implication graph that separates the decision nodes (reason side) from the conflict node (conflict side). Every edge crossing the cut corresponds to a literal whose assignment contributed to the conflict. The negations of those literals form the learned clause.

Common misconception

You should always undo just the most recent decision and try the opposite value.

What's actually true

That is chronological backtracking — DPLL’s strategy. It is correct (you will eventually try every combination) but inefficient. In the example above, the real problem is the combination of A=True and B=True. Undoing only B=True and trying B=False will still allow A=True to propagate C=True, and C=True is part of the conflict. CDCL learns a clause encoding this dependency explicitly.

The First-UIP Cut

Among all possible cuts, CDCL modern solvers universally use the first-UIP cut. Understanding why requires the concept of a Unique Implication Point (UIP).

Unique Implication Point (UIP) def.

A node at decision level dd in the implication graph is a UIP if every path from the level-dd decision node to the conflict node passes through it. The first UIP is the UIP closest to the conflict node.

Intuitively, the first UIP is the “bottleneck” through which all blame at the current level flows. Cutting just below the first UIP gives the tightest, most compact learned clause — and guarantees the clause will be asserting: at the backjump level, exactly one literal in the learned clause is unset, making it immediately unit-propagate. [GRASP: A Search Algorithm for Propositional Satisfiability]

Use the dropdown to select different cuts on the implication graph. Observe how the cut position changes the learned clause. The first-UIP cut (Cut 1) produces the most useful asserting clause.
Show the formal first-UIP algorithm (resolution-based view)

The first-UIP clause can be derived via resolution — the same resolution used in the Davis-Putnam procedure, applied just to the conflict:

  1. Start with the clause that became empty (the conflict clause).
  2. Resolve it with the antecedent clause of the most-recently-assigned literal at the current decision level that appears in the current clause.
  3. Repeat until the current clause has exactly one literal from the current decision level — that literal is the first UIP.
  4. The resulting clause is the first-UIP learned clause.

Example from our graph:

  • Start: conflict clause (¬C¬E)(\neg C \lor \neg E).
  • E was forced at level 2 by (¬C¬DE)(\neg C \lor \neg D \lor E). Resolve (¬C¬E)(\neg C \lor \neg E) with (¬C¬DE)(\neg C \lor \neg D \lor E) on E: produces (¬C¬D)(\neg C \lor \neg D).
  • C is at level 1, D is at level 2. D is the only level-2 literal. Stop — D (negated: ¬D) is not the UIP, but let’s check: every path from B to ⊗ goes through E, so E is the UIP. Hmm — the resolution chain should stop one step earlier: (¬C¬E)(\neg C \lor \neg E) resolved with E’s antecedent to produce the clause containing the UIP E as the single level-2 literal.

In practice, the resolution derivation and the cut-based derivation are equivalent; solvers implement the resolution version because it integrates naturally with the clause database. [Handbook of Satisfiability (2nd ed.), Chapter 4]

Check your understanding

Why does the first-UIP cut produce an 'asserting' clause?

Non-chronological Backjumping

Once we have the learned clause, we know exactly how far back to jump. The asserting level is the second-highest decision level among the literals in the learned clause — the highest level at which all but the UIP literal are already determined (to False). At that level, the UIP literal is immediately forced by unit propagation.

Asserting level def.

Given a learned clause CC derived by first-UIP analysis at decision level dd, the asserting level is the maximum decision level of any literal in CC other than the first UIP. After backjumping to the asserting level, the learned clause becomes a unit clause and forces the UIP literal without branching.

This is the mechanism that makes CDCL vastly superior to DPLL on industrial instances. [Chaff: Engineering an Efficient SAT Solver]

Watch chronological backtracking undo one level at a time versus CDCL jumping straight back to the decision level that caused the conflict.

Why does this scale to millions of variables?

In an industrial SAT instance with hundreds of decision levels, chronological backtracking from level 150 might undo 140 decisions before reaching the real culprit at level 10. CDCL jumps directly to level 10. More importantly, the learned clause propagates at level 10, immediately shrinking the search space. Over thousands of conflicts, learned clauses form a growing library of “no-go zones” that prune the search tree far more aggressively than any heuristic DPLL could apply.

Check your understanding

The learned clause at a conflict is (¬X₁ ∨ ¬X₃ ∨ X₅). X₁ was set at level 1, X₃ at level 3, X₅ at level 5 (the conflict level). What is the asserting level?

The Complete CDCL Loop

Putting all four pieces together gives the CDCL algorithm that runs inside every modern SAT solver — including CP-SAT’s Boolean layer.

flowchart TD
  A([Start: CNF + empty assignment]) --> B[Unit Propagation]
  B --> C{Conflict?}
  C -- No --> D{All vars assigned?}
  D -- Yes --> SAT([Return SAT])
  D -- No --> E[Pick variable, branch
VSIDS heuristic]
  E --> B
  C -- Yes --> F{At decision
level 0?}
  F -- Yes --> UNSAT([Return UNSAT])
  F -- No --> G[Conflict analysis:
build implication graph]
  G --> H[Derive first-UIP
learned clause]
  H --> I[Add learned clause
to formula]
  I --> J[Backjump to
asserting level]
  J --> B
CDCL algorithm control flow
DPLL (1962) CDCL (1996+)
On conflict Undo last decision, flip itAnalyse conflict, learn clause, backjump
Backtracking Chronological (one level at a time)Non-chronological (to asserting level)
Formula size over time FixedGrows by learned clauses (managed by deletion)
Variable ordering Static or simple heuristicDynamic VSIDS (decays activity per conflict)
Practical scale ~hundreds of variablesMillions of variables
Completeness CompleteComplete
DPLL vs CDCL — the four key differences
[MiniSat: A Minimalistic and Extensible SAT Solver]

VSIDS (Variable State Independent Decaying Sum) deserves a brief mention. Each variable has an activity score that is bumped every time it appears in a conflict. When CDCL needs to branch, it picks the highest-activity unassigned variable. Activity decays geometrically over time, so the solver naturally focuses on variables involved in recent conflicts — the ones most likely to be entangled in the current hard region of the search space. [Chaff: Engineering an Efficient SAT Solver]

The solver inside OR-Tools CP-SAT is a Glucose-style CDCL implementation with watched-literal propagation — a data structure trick where each clause tracks two unset literals (“watched literals”) and only wakes up when one of them is falsified. This reduces the cost of unit propagation from O(clause length × clauses) to near-linear in practice, making millions of propagations per second tractable on scheduling instances with tens of thousands of variables. [CP-SAT Solver Documentation — OR-Tools]

Check your understanding

After backjumping and adding the learned clause, why does CDCL run unit propagation immediately rather than branching?

Check your understandingQ 1 / 5

An implication graph has decision node B=T at level 2, propagated nodes D=T and E=T (both forced by B and by C from level 1), and a conflict ⊗ reached by both E and B. What is the minimum information needed to derive the learned clause?