Nelson–Oppen combination
The Nelson–Oppen method combines decision procedures for separate theories into one for their union[1]. Verification conditions routinely mix theories: a single formula may contain arithmetic, uninterpreted functions, arrays and datatypes, while decision procedures are written for one theory each. Nelson–Oppen requires that the theories share no function or predicate symbols except equality. It purifies the input so that each literal belongs to one theory, introducing variables for subterms that cross from one theory into another; the only connection left between the parts is that they share some variables. The procedures then need to agree only on which shared variables are equal. For convex theories, each procedure reports the equalities between shared variables that its part entails and passes them to the others until the parts are inconsistent or no new equalities appear; in general, the procedures must be tried on each possible arrangement of the shared variables into equal and distinct ones[2]. Greg Nelson and Derek Oppen published the method in 1979[1]. It is the basis of theory combination in SMT solvers.
Purification
A term is pure if all its symbols come from one theory. Purification repeatedly replaces a maximal subterm that belongs to a different theory than its context by a fresh variable and adds the equation . The result is an equisatisfiable conjunction with in the language of theory . For example, becomes for arithmetic and for uninterpreted functions, sharing and .
Equality propagation
A theory is convex if, whenever a conjunction of its literals entails a disjunction of equalities between variables, it entails one of them. Linear real arithmetic and equality with uninterpreted functions are convex. For convex theories, the procedures can simply exchange entailed equalities: each theory solver reports the equalities between shared variables that its part entails, the other adds them, and this repeats. The input is unsatisfiable if some part becomes inconsistent, and satisfiable if the exchange stops with both parts consistent[1][2]. With a polynomial procedure for each convex theory, the combination is polynomial.
Nelson–Oppen for difference logic and uninterpreted functions: each side reports the equalities between shared variables that it entails.
(* Nelson–Oppen for difference logic and uninterpreted functions. Afterpurification each literal belongs to one theory, and the theories sharevariables. Shared variables are named by strings; in the EUF part theyare constants. *)let euf_var v = Euf.Const v(* Does difference logic entail x = y? Only if x - y <= -1 and y - x <= -1are each inconsistent with the constraints (the integer negations ofx = y, one side at a time). *)let dl_entails_eq cs x y =let inconsistent extra = match Dl.check (extra :: cs) with Error _ -> true | Ok _ -> false ininconsistent { Dl.x; y; c = -1 } && inconsistent { Dl.x = y; y = x; c = -1 }let euf_entails_eq lits x y =let eqs = List.filter_map (fun (e, b) -> if b then Some e else None) lits inEuf.implied eqs (euf_var x) (euf_var y)(* The deterministic loop for convex theories: each theory reports theequalities between shared variables that it entails, and they arepassed to the other, until one theory is inconsistent or nothing newis found. *)let combine ~shared (dl : Dl.atom list) (euf : (Euf.Theory.atom * bool) list) =let pairs = List.concat_map (fun x -> List.filter_map (fun y -> if x < y then Some (x, y) else None) shared) shared inlet rec loop dl euf =match Dl.check dl, Euf.Theory.check euf with| Error _, _ -> print_endline " arithmetic: inconsistent"; false| _, Error _ -> print_endline " uninterpreted functions: inconsistent"; false| Ok _, Ok () ->let known (x, y) = euf_entails_eq euf x y && dl_entails_eq dl x y inlet from_dl = List.filter (fun (x, y) -> not (known (x, y)) && dl_entails_eq dl x y) pairs inlet from_euf = List.filter (fun (x, y) -> not (known (x, y)) && euf_entails_eq euf x y) pairs inif from_dl = [] && from_euf = [] then (print_endline " no new equalities: satisfiable"; true)else beginList.iter (fun (x, y) -> Printf.printf " arithmetic entails %s = %s\n" x y) from_dl;List.iter (fun (x, y) -> Printf.printf " uninterpreted functions entail %s = %s\n" x y) from_euf;let dl = dl @ List.concat_map (fun (x, y) -> [ { Dl.x; y; c = 0 }; { Dl.x = y; y = x; c = 0 } ]) from_euf inlet euf = euf @ List.map (fun (x, y) -> ((euf_var x, euf_var y), true)) from_dl inloop dl eufendinloop dl euf
The example of the figure, and the same without y ≤ x.
open Euf(* x <= y and y <= x and f(x) <> f(y), purified: the arithmetic partis x - y <= 0 and y - x <= 0, the functional part f(x) <> f(y). *)
Running it.
x <= y, y <= x, f(x) <> f(y):arithmetic entails x = yuninterpreted functions: inconsistentx <= y, f(x) <> f(y):no new equalities: satisfiable
Non-convex theories
Integer arithmetic is not convex: entails but neither equality alone. Exchanging entailed equalities is then incomplete. The general method guesses an arrangement, a partition of the shared variables into classes of equal variables, and checks each theory's part together with the equalities within classes and the disequalities between them; the input is satisfiable if and only if some arrangement is consistent with every part[1][3]. The number of arrangements is the Bell number of the number of shared variables, and Oppen showed that the combination problem becomes NP-hard in general, even when each theory is polynomial[2].
Over the integers, 1 ≤ x ≤ 2, f(x) ≠ f(1) and f(x) ≠ f(2): equality propagation against guessing arrangements.
open Euf(* Over the integers: 1 <= x <= 2, f(x) <> f(1), f(x) <> f(2). With azero variable z and constants one = 1 and two = 2, all as sharedvariables. Arithmetic entails x = one or x = two, but neither alone. *)let dl = [ { Dl.x = "z"; y = "x"; c = -1 }; { Dl.x = "x"; y = "z"; c = 2 };{ Dl.x = "one"; y = "z"; c = 1 }; { Dl.x = "z"; y = "one"; c = -1 };{ Dl.x = "two"; y = "z"; c = 2 }; { Dl.x = "z"; y = "two"; c = -2 } ]let f v = App ("f", [ Const v ])let euf = [ ((f "x", f "one"), false); ((f "x", f "two"), false) ]let shared = [ "x"; "one"; "two" ](* All partitions of a list: the arrangements of the shared variables. *)let rec partitions = function| [] -> [ [] ]| x :: rest ->List.concat_map (fun p ->([ x ] :: p) :: List.mapi (fun i _ -> List.mapi (fun j b -> if i = j then x :: b else b) p) p)(partitions rest)(* An arrangement as equalities and disequalities between shared variables. *)let constraints arr =let block v = List.find (List.mem v) arr inList.concat_map (fun x -> List.filter_map (fun y ->if x < y then Some (x, y, block x == block y) else None) shared) sharedlet dl_with arr =(* a disequality is a disjunction; try every way of choosing a side *)let eqs, diseqs = List.partition (fun (_, _, e) -> e) (constraints arr) inlet base = dl @ List.concat_map (fun (x, y, _) -> [ { Dl.x; y; c = 0 }; { Dl.x = y; y = x; c = 0 } ]) eqs inlet rec try_ acc = function| [] -> (match Dl.check acc with Ok _ -> true | Error _ -> false)| (x, y, _) :: rest -> try_ ({ Dl.x; y; c = -1 } :: acc) rest || try_ ({ Dl.x = y; y = x; c = -1 } :: acc) rest intry_ base diseqslet euf_with arr =let lits = List.map (fun (x, y, e) -> ((Const x, Const y), e)) (constraints arr) inmatch Theory.check (euf @ lits) with Ok () -> true | Error _ -> false
Running it.
the deterministic loop:no new equalities: satisfiableguessing arrangements:x | one | two arithmetic false functions truex = one | two arithmetic true functions falseone | x = two arithmetic true functions falsex | one = two arithmetic false functions truex = one = two arithmetic false functions false
Equality propagation wrongly finds the formula satisfiable, since neither theory entails any single equality. Enumerating the five arrangements of , and shows that each is rejected by one theory or the other, so the formula is unsatisfiable.
Correctness conditions
The method is sound for any theories, but complete only under conditions. The signatures must be disjoint apart from equality, and each theory must be stably infinite: every quantifier-free formula satisfiable in the theory must be satisfiable in a model with infinitely many elements. The condition ensures that the models of the separate parts, which agree on the shared variables, can be taken to have the same size and merged into one model of the union[1][3]. Tinelli and Harandi gave a clean proof of correctness in 1996[3]. Theories over finite domains, such as fixed-width bit-vectors, are not stably infinite, and combining them requires further care, for instance the conditions of shiny or polite theories[6][7].
In SMT solvers
Inside DPLL(T), the choice of an arrangement can be delegated to the SAT solver. Delayed theory combination introduces Boolean atoms for the equalities between shared variables and lets the search decide them like any other atom, each theory solver checking its part together with those decisions[8]. Model-based theory combination, used in Z3, lets each theory solver build a candidate model and propagates only the equalities that hold in it, repairing the model when another theory disagrees[9]. Both avoid enumerating arrangements explicitly.
History
Nelson and Oppen introduced the method in 1979 for the Stanford Pascal Verifier, as a way to build a simplifier from cooperating decision procedures[1]. Oppen analyzed its complexity and the role of convexity in 1980[2]. Shostak proposed an alternative in 1978 and 1984 for theories with canonizers and solvers, which combines them more tightly[4]; its correctness was debated until Rueß and Shankar gave a corrected account in 2001[5]. The conditions for completeness were clarified in the 1990s and 2000s[3][6][7], and the integration of combination into DPLL(T) produced delayed and model-based combination[8][9].
see also
- 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.
- 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.
- DPLL(T)DPLL(T) is the architecture of lazy SMT solvers: a CDCL SAT solver searches over the Boolean structure of a formula, treating each theory atom as a propositional variable, and a theory solver for T checks the consistency of the atoms it assigns. When the theory solver finds the assigned literals inconsistent, it returns an explanation, whose negation the SAT solver learns as a clause before backjumping; it may also propagate literals the assignment implies. The approach separates a generic engine from pluggable theory solvers and is used by nearly all SMT solvers, including Z3, cvc5 and Yices.
- 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.
further reading
- [1]G. Nelson, D. C. Oppen, “Simplification by cooperating decision procedures”, ACM Transactions on Programming Languages and Systems 1 (1979).
- [2]D. C. Oppen, “Complexity, convexity and combinations of theories”, Theoretical Computer Science 12 (1980).
- [3]C. Tinelli, M. Harandi, “A new correctness proof of the Nelson–Oppen combination procedure”, FroCoS (1996).
- [4]R. E. Shostak, “Deciding combinations of theories”, Journal of the ACM 31 (1984).
- [5]H. Rueß, N. Shankar, “Deconstructing Shostak”, LICS (2001).
- [6]C. Tinelli, C. G. Zarba, “Combining nonstably infinite theories”, Journal of Automated Reasoning 34 (2005).
- [7]S. Ranise, C. Ringeissen, C. G. Zarba, “Combining data structures with nonstably infinite theories using many-sorted logic”, FroCoS (2005).
- [8]M. Bozzano, R. Bruttomesso, A. Cimatti, T. Junttila, S. Ranise, P. van Rossum, R. Sebastiani, “Efficient theory combination via Boolean search”, Information and Computation 204 (2006).
- [9]L. de Moura, N. Bjørner, “Model-based theory combination”, Electronic Notes in Theoretical Computer Science 198 (2008).
- [10]D. Kroening, O. Strichman, Decision Procedures: An Algorithmic Point of View, ch. 10, Springer (2nd ed., 2016).
last updated