DPLL(T)
DPLL(T) is the standard way to decide satisfiability of formulas that mix Boolean structure with a background theory , such as linear arithmetic or equality with uninterpreted functions[2][1]. The formula's theory atoms, such as , are abstracted as propositional variables, and a CDCL SAT solver, the DPLL part, searches for an assignment to them that satisfies the Boolean structure. A theory solver for checks whether the literals assigned so far are consistent in the theory. If they are not, it returns an explanation, a subset of them that is inconsistent, and the SAT solver adds the negation of the explanation as a learned clause, a theory lemma, and backjumps. It can also tell the SAT solver about literals that the current ones imply, which is theory propagation. The architecture was presented by Ganzinger, Hagen, Nieuwenhuis, Oliveras and Tinelli in 2004[2] and described as an abstract transition system in 2006[1]; it underlies nearly every SMT solver.
The abstract procedure
Nieuwenhuis, Oliveras and Tinelli described DPLL and its extensions as rules that transform a state , where is the sequence of assigned literals, some marked as decisions, and the set of clauses[1]. The Boolean rules are those of CDCL: Decide adds an unassigned literal as a decision; UnitPropagate adds the last literal of a clause whose other literals are false; Backjump replaces the part of after some decision with a literal implied by a learned clause; Fail ends with unsatisfiability when a clause is false and there are no decisions. The theory adds:
T-Propagate adds a literal of when . T-Learn adds a clause built from atoms of when ; theory lemmas, the negations of explanations, are such clauses. T-Forget removes a learned clause. T-Backjump is Backjump with in place of propositional entailment. A run is correct whatever order the rules are applied in, which is what makes it possible to vary the strategy without changing the correctness argument[1].
A small DPLL(T) implementation over integer atoms, generic in the theory. It backtracks chronologically; production solvers use CDCL with first-UIP learning.
(* DPLL(T): a DPLL search over Boolean atoms that consults a theorysolver whenever unit propagation reaches a fixed point. Literals arenon-zero integers: i stands for atoms.(i-1), -i for its negation. Thetheory accepts the assigned atoms or explains a conflict by a subset ofthem, whose negation is learned as a clause (a theory lemma). *)module type THEORY = sigtype atomtype modelval check : (atom * bool) list -> (model, (atom * bool) list) resultendtype stats = { mutable decisions : int; mutable conflicts : int; mutable lemmas : int;mutable checks : int; mutable tprops : int }module Make (T : THEORY) = structlet trace = ref falselet say fmt = Printf.ksprintf (fun s -> if !trace then print_endline s) fmt(* online: consult the theory at every propagation fixed point; iffalse, only when the assignment is total ("lemmas on demand").tprop: also ask the theory for implied atoms. *)let solve ?(online = true) ?(tprop = false) ?(name = string_of_int) (atoms : T.atom array) (input : int list list) =let n = Array.length atoms inlet st = { decisions = 0; conflicts = 0; lemmas = 0; checks = 0; tprops = 0 } inlet clauses = ref input inlet value = Array.make (n + 1) 0 and level = Array.make (n + 1) 0 inlet trail = ref [] and dl = ref 0 inlet lv l = if value.(abs l) = 0 then 0 else if (value.(abs l) > 0) = (l > 0) then 1 else -1 inlet assign l = value.(abs l) <- (if l > 0 then 1 else -1); level.(abs l) <- !dl; trail := l :: !trail inlet show l = if l < 0 then "¬(" ^ name (-l) ^ ")" else name l inlet index a = let i = ref 0 in Array.iteri (fun j x -> if x = a then i := j + 1) atoms; !i inlet theory lits = st.checks <- st.checks + 1; T.check (List.map (fun l -> (atoms.(abs l - 1), l > 0)) lits) inlet lemma expl = List.map (fun (a, b) -> if b then - (index a) else index a) expl inlet rec propagate () =let conflict = ref None and changed = ref false inList.iter (fun c ->if !conflict = None && not (List.exists (fun l -> lv l = 1) c) thenmatch List.filter (fun l -> lv l = 0) c with| [] -> conflict := Some c| [ l ] -> assign l; changed := true; say " propagate %s" (show l)| _ -> ()) !clauses;match !conflict with| Some c -> Some c| None when !changed -> propagate ()| None when not online && Array.exists (( = ) 0) (Array.sub value 1 n) -> None| None ->(* ask the theory about the whole assignment *)match theory (List.rev !trail) with| Error expl ->let c = lemma expl inst.lemmas <- st.lemmas + 1; clauses := c :: !clauses;say " theory conflict, learn %s" (String.concat " ∨ " (List.map show c));Some c| Ok _ when tprop ->(* theory propagation: an unassigned atom whose negation wouldbe inconsistent is implied; learn the lemma that says so *)let implied = List.find_map (fun v ->if value.(v) <> 0 then Noneelse List.find_map (fun l ->match theory (List.rev (-l :: !trail)) with| Error expl when List.mem (atoms.(v - 1), l < 0) expl -> Some (l, lemma expl)| _ -> None) [ v; -v ]) (List.init n (fun i -> i + 1)) in(match implied with| Some (l, c) ->st.tprops <- st.tprops + 1; clauses := c :: !clauses;assign l; say " theory propagates %s" (show l); propagate ()| None -> None)| Ok _ -> Noneinlet result = ref None inwhile !result = None domatch propagate () with| Some _ ->st.conflicts <- st.conflicts + 1;if !dl = 0 then result := Some Noneelse begin(* undo the latest decision and assert its negation one level down *)let d = List.find (fun l -> level.(abs l) = !dl) (List.rev !trail) intrail := List.filter (fun l -> if level.(abs l) >= !dl then (value.(abs l) <- 0; false) else true) !trail;decr dl; assign (-d); say "backtrack: %s" (show (-d))end| None ->match List.find_opt (fun v -> value.(v) = 0) (List.init n (fun i -> i + 1)) with| None -> (match theory (List.rev !trail) with| Ok m -> result := Some (Some (m, List.rev !trail))| Error _ -> assert false)| Some v -> st.decisions <- st.decisions + 1; incr dl; assign v; say "decide %s" (show v)done;(Option.get !result, st)end
An example
Take difference-logic atoms , , and , and the clauses stating that the third and second hold, and that the first or the fourth does.
Running the example with and without theory propagation.
module S = Dpllt.Make (Dl.Theory)(* Atoms 1: x - y <= 0, 2: y - z <= -1, 3: z - x <= 0, 4: w - y <= 5.Clauses: (3), (2), (1 ∨ 4). *)let atoms = [| { Dl.x = "x"; y = "y"; c = 0 }; { Dl.x = "y"; y = "z"; c = -1 };{ Dl.x = "z"; y = "x"; c = 0 }; { Dl.x = "w"; y = "y"; c = 5 } |]let clauses = [ [ 3 ]; [ 2 ]; [ 1; 4 ] ]
Running it.
without theory propagation:propagate z - x <= 0propagate y - z <= -1decide x - y <= 0theory conflict, learn ¬(z - x <= 0) ∨ ¬(y - z <= -1) ∨ ¬(x - y <= 0)backtrack: ¬(x - y <= 0)propagate w - y <= 5model: w = 0, x = 0, y = -1, z = 0 (1 decision)with theory propagation:propagate z - x <= 0propagate y - z <= -1theory propagates ¬(x - y <= 0)propagate w - y <= 5model: w = 0, x = 0, y = -1, z = 0 (0 decisions)
Lazy and eager
Before DPLL(T), two approaches competed. The eager approach translates the whole formula into an equisatisfiable propositional formula, encoding the relevant theory facts as clauses up front, and hands it to a SAT solver[8]. The lazy approach, developed around 2002 in CVC, ICS, MathSAT and Verifun, lets a SAT solver find a propositional model first and asks a theory solver to check it, adding a lemma for each inconsistent model: "lemmas on demand"[3][4][5][6]. DPLL(T) is the lazy approach made online: the theory solver is consulted during the search, after propagation, rather than only on complete models, so inconsistent partial assignments are cut off early.
Consulting the theory at every propagation fixed point against only on complete assignments, on job-shop scheduling instances.
(* Consulting the theory at every propagation fixed point (online), oronly once the Boolean assignment is complete (offline, the "lemmas ondemand" approach), on job-shop instances at their optimal makespan andone below it. *)module S = Dpllt.Make (Dl.Theory)
Running it.
instance makespan mode decisions lemmas checks3x3 23 yes online 12 9 233x3 23 yes offline 87 30 323x3 22 no online 5 5 103x3 22 no offline 87 31 314x3 27 yes online 35 21 584x3 27 yes offline 273 45 474x3 26 no online 28 24 524x3 26 no offline 418 88 88
Checking only complete assignments needs several times more decisions, since the search keeps extending assignments that are already inconsistent, and the gap grows with the instance. The number of theory checks is similar, but each offline check comes after much more wasted work.
Theory propagation and explanations
How much a theory solver contributes depends on two capabilities. Theory propagation assigns atoms implied by the current literals, which is cheap for some theories, such as difference logic, where implied atoms correspond to paths in the constraint graph, and Nieuwenhuis and Oliveras showed that exhaustive propagation pays off there[9]. Explanations must be small, since the learned clause contains one literal for each element; they must also be available for propagated literals, because conflict analysis resolves through them as it does through ordinary reasons[1][7].
History
Combining SAT solvers with decision procedures began with the eager encodings of the late 1990s[8] and the lazy tools of 2002: CVC from Stanford[6], ICS from SRI with lemmas on demand[3], MathSAT from Trento[4] and Verifun from Compaq[5]. Ganzinger, Hagen, Nieuwenhuis, Oliveras and Tinelli introduced DPLL(T) at CAV 2004 with a modular interface between a DPLL engine and theory solvers[2], and the 2006 Journal of the ACM paper gave the abstract transition-system view that became the standard account[1]. The Barcelogic solver built on it did well in the first SMT competitions, and the design was adopted by Yices, Z3, cvc4 and their successors[7].
see also
- SMTSatisfiability modulo theories (SMT) is the problem of deciding whether a first-order formula is satisfiable when some of its symbols are interpreted by background theories, such as integer or real arithmetic, bit-vectors, arrays and uninterpreted functions. SMT solvers such as Z3 and cvc5 combine a SAT solver, which handles the Boolean structure, with specialized decision procedures for the theories, usually in the DPLL(T) architecture. They read the standard SMT-LIB language and are the reasoning engines of program verifiers, symbolic execution tools, test generators and many other analysis tools.
- Theory solverIn an SMT solver, a theory solver decides whether a conjunction of literals of one theory, such as linear arithmetic or equality with uninterpreted functions, is consistent. The SAT search asserts literals to it incrementally and asks it to check them; on inconsistency it returns an explanation, a small subset of the asserted literals that is already inconsistent, which the SAT search learns as a clause. A theory solver must also undo assertions when the search backtracks, and it may propagate literals that the asserted ones imply. The quality of explanations and propagation largely determines the solver's performance.
- CDCLConflict-driven clause learning (CDCL) is the algorithm of modern SAT solvers. It extends DPLL backtracking search with three ideas: when propagation falsifies a clause, conflict analysis derives a new clause that explains the conflict and adds it to the formula; the solver then backjumps non-chronologically to the decision level at which the new clause forces a literal; and branching follows the variables involved in recent conflicts. With watched literals, restarts and clause deletion, CDCL solvers decide industrial formulas with millions of variables, and with restarts they are as strong as general resolution, exponentially stronger than DPLL.
- Difference logicDifference logic is the fragment of linear arithmetic whose atoms bound the difference of two variables by a constant, x − y ≤ c, over the integers or the reals. A conjunction of such constraints is satisfiable exactly when the graph with an edge from y to x of weight c for each constraint has no negative cycle, so it can be decided by shortest-path algorithms such as Bellman–Ford, and a negative cycle is a short explanation of unsatisfiability. Boolean combinations of difference constraints are handled by SMT solvers through DPLL(T); they express scheduling problems and the timing constraints of timed automata.
- Congruence closureCongruence closure is the algorithm that decides whether an equation between terms follows from a set of ground equations, using only the laws of equality and the congruence rule that equal arguments give equal function applications. It maintains equivalence classes of subterms in a union–find structure and, whenever two classes merge, merges any applications of the same function whose arguments have become equal. It runs in O(n log n) time and decides the quantifier-free theory of equality with uninterpreted functions (EUF), which makes it a core theory solver in SMT; the same data structure, the e-graph, underlies equality saturation in compilers.
- Nelson–Oppen combinationThe Nelson–Oppen method decides satisfiability of quantifier-free formulas that mix several theories with disjoint signatures, such as linear arithmetic and uninterpreted functions, using only a decision procedure for each theory. The formula is first purified, by naming mixed subterms with fresh variables, so that every literal belongs to one theory; the theory solvers then agree on which of their shared variables are equal, either by propagating the equalities each entails (enough for convex theories) or by searching over all arrangements of the shared variables. It is complete for theories that are stably infinite, and it is how SMT solvers combine their theory solvers.
- DPLLDPLL, the Davis–Putnam–Logemann–Loveland procedure, is the backtracking search algorithm for Boolean satisfiability on which all complete SAT solvers are built. It repeatedly simplifies a CNF formula by unit propagation and pure literal elimination, then chooses an unassigned variable and tries both of its values in turn, backtracking to the most recent choice when a clause becomes false. It is sound and complete, uses memory linear in the formula, and its runs on unsatisfiable formulas correspond to tree-like resolution proofs, which gives exponential lower bounds such as the n! − 1 decisions it needs for the pigeonhole formulas. Clause learning turned it into CDCL.
- Simplex methodThe simplex method solves linear programs, maximizing a linear function subject to linear inequalities, by walking along the edges of the feasible polyhedron from vertex to vertex, each step improving the objective, until no neighbouring vertex is better. Each step is a pivot on a tableau. It is fast in practice and in smoothed analysis, but takes exponential time on the Klee-Minty cubes with Dantzig's pivoting rule, and needs a rule such as Bland's to avoid cycling on degenerate problems. Its final tableau also gives a solution of the dual program, certifying optimality.
referenced by
further reading
- [1]R. Nieuwenhuis, A. Oliveras, C. Tinelli, “Solving SAT and SAT Modulo Theories: from an abstract Davis–Putnam–Logemann–Loveland procedure to DPLL(T)”, Journal of the ACM 53 (2006).
- [2]H. Ganzinger, G. Hagen, R. Nieuwenhuis, A. Oliveras, C. Tinelli, “DPLL(T): fast decision procedures”, CAV (2004).
- [3]L. de Moura, H. Rueß, “Lemmas on demand for satisfiability solvers”, SAT (2002).
- [4]G. Audemard, P. Bertoli, A. Cimatti, A. Korniłowicz, R. Sebastiani, “A SAT based approach for solving formulas over Boolean and linear mathematical propositions”, CADE (2002).
- [5]C. Flanagan, R. Joshi, X. Ou, J. B. Saxe, “Theorem proving using lazy proof explication”, CAV (2003).
- [6]A. Stump, C. W. Barrett, D. L. Dill, “CVC: a cooperating validity checker”, CAV (2002).
- [7]C. Barrett, R. Sebastiani, S. A. Seshia, C. Tinelli, “Satisfiability modulo theories”, in A. Biere et al. (eds.), Handbook of Satisfiability, IOS Press (2nd ed., 2021).
- [8]R. E. Bryant, S. German, M. N. Velev, “Exploiting positive equality in a logic of equality with uninterpreted functions”, CAV (1999).
- [9]R. Nieuwenhuis, A. Oliveras, “DPLL(T) with exhaustive theory propagation and its application to difference logic”, CAV (2005).
last updated