|
Nikolaj Bjørner
Microsoft Research
nbjorner@microsoft.com |
Given a formula , fix
Proposition 1.
Let be the subterms in formula . If
for every
then .
Recall global pre-processing rules: If contains the subterm and this is the only occurrence of simplify to and remember that interpretation for should be repaired to .
Many operators are invertible.
Sometimes operators are not invertible, but weakly invertible: If . We cannot solve for or . But we can reset both to some values that solve for 1.
Compute a scoring function based on :
Pick false literal in false clause.
I found that using gradients is not as brittle as local repair. Gradients can exploit derivatives. But local repair is useful when there is not clear derivative for functions.
Combine Theory local search with Boolean local search (Zhang et al., 2024)
Use information from local search to speed up complete search
A fresh topic
Egglog:
Equality saturation in First-Order (Vampire):
If we merge
We can infer that .
“Cut, dice, and slice” extract, concat, and bit-wise: (Bruttomesso and Sharygina, 2009)
Matching modulo AC and ACI
Matching modulo Higher-Order functions
Both topics have established solutions.
For E-matching:
Theory solver offers two main services
Both cases rely on establishing -tautologies
tautologies are justified using theory specific rules
tautologies can be added to propositional RUP proofs as trusted assumptions
EUF - we show how to justify EUF tautologies found by congruence closure
Arithmetic - we illustrate extracting Farkas proofs
Other theories - many rely on instantiating axioms.
SMT solvers combine multiple -solvers into a single solver.
Example: EUF + Arithmetic
For proof generation, we have a nested scenario:
The CDCL solver by default logs -clauses after false unit literals have been removed
Steps required to verify -clause :
T-solvers share and propagate equalities
Each internal -propagation is logged as a lemma.
Assumed and propagated equalities are not known by CDCL(T) solver.
Equality reasoning leaks to -solvers
Aaron Stump (Stump and Dill, 2002; Stump, 2002, 2009; Stump et al., 2002, 2013; Wu et al., 2003; Appel et al., 2003; Stump and Tan, 2005; Klapper and Stump, 2005)
CVC (Barbosa et al., 2022; Ozdemir et al., 2019; Hadarean et al., 2015; Katz et al., 2016)
SMTInterpol (Hoenicke and Schindler, 2022b, 2022a)
VeriT (Bouton et al., 2009; Barbosa and Other, 2020)
Z3 (Moura and Bjørner, 2008) Axiom Profiler (n.d., 34)
Isabelle, Coq, LN (Blanchette et al., 2013; Böhme et al., 2011; Besson et al., 2011; Ekici et al., 2017)
Format proposals (Besson et al., 2011; Schurr et al., 2021; Barrett et al., 2014)
SMT3 (Brown et al., 2022)
Variant of dependent type theory with built-ins for exceptions
First described in 2002, then several 2008, 2013, ..
Noteworthy:
A general contextual rewrite functor
So far unexplored in this work:
Wide set of theories
Unsupported only: Floats, sets, sequences
Target a variety of backends
Pre-processing proofs
Trusted base of proof rules, see SMT Workshop 2022 paper.
Proof rules for linear arithmetic, arrays, ADTs, EUF.
Proof rules formulated mild extension of SMT3
Self-contained checker
(assume a0 (exists ((x A)) (f x)))
(anchor :step t1 :args (:= x vr))
(step t1.t1 (cl (= x vr)) :rule cong)
(step t1.t2 (cl (= (f x) (f vr))) :rule cong)
(step t1 (cl (= (exists ((x A)) (f x))
(exists ((vr A)) (f vr)))) :rule bind)
(step t2 (cl (not (= (exists ((vr A)) (f x))
(exists ((vr A)) (f vr)))) (not (exists ((vr A)) (f x)))
(exists ((vr A)) (f vr))) :rule equiv_pos1)
(step t3 (cl (exists ((vr A)) (f vr))) :premises (a0 t1 t2) :rule resolution)
(define-fun X () A (choice ((vr A)) (f vr)))
(step t4 (cl (= (exists ((vr A)) (f vr)) (f X))) :rule sko_ex)
(step t5 (cl (not (= (exists ((vr A)) (f vr)) (f X)))
(not (exists ((vr A)) (f vr))) (f X)) :rule equiv_pos1)
(step t6 (cl (f X)) :premises (t3 t4 t5) :rule resolution)
(set-option :proof true)
(declare-fun f (Int) Int)
(declare-const x Int)
(assert (or (= (f (f (f x))) x) (= (f (f x)) x)))
(assert (not (= (f (f (f (f (f (f x)))))) x)))
(check-sat)
(get-proof)((proof
(let ((?x25 (f x)))
(let ((?x26 (f ?x25)))
(let ((?x27 (f ?x26)))
(let ((?x32 (f ?x27)))
(let ((?x33 (f ?x32)))
(let ((?x34 (f ?x33)))
(let (($x35 (= ?x34 x)))
(let (($x29 (= ?x26 x)))
(let (($x28 (= ?x27 x)))
(let ((@x47 (hypothesis $x28)))
(let ((@x53 (monotonicity (monotonicity (monotonicity @x47 (= ?x32 ?x25)) (= ?x33 ?x26)) (= ?x34 ?x27))))
(let (($x36 (not $x35)))
(let ((@x37 (asserted $x36)))
(let ((@x57 (lemma (unit-resolution @x37 (trans* @x53 @x47 $x35) false) (not $x28))))
(let (($x30 (or $x28 $x29)))
(let ((@x39 (rewrite (= $x30 $x30))))
(let ((@x58 (unit-resolution (mp (mp (mp (asserted $x30) @x39 $x30) @x39 $x30) @x39 $x30) @x57 $x29)))
(let ((@x64 (trans* (monotonicity (monotonicity @x58 (= ?x27 ?x25)) (= ?x32 ?x26)) @x58 (= ?x32 x))))
(let ((@x69 (trans* (monotonicity (monotonicity @x64 (= ?x33 ?x25)) (= ?x34 ?x26)) @x58 $x35)))
(unit-resolution @x37 @x69 false))))))))))))))))))))))(set-option :sat.smt true)
(set-option :solver.proof.log proof-log.smt2)
(declare-fun f (Int) Int)
(declare-const x Int)
(assert (or (= (f (f (f x))) x) (= (f (f x)) x)))
(assert (not (= (f (f (f (f (f (f x)))))) x)))
(check-sat)Pretty printed from Python:
assume(Or(f(f(f(x))) == x, f(f(x)) == x))
assume(Not(f(f(f(f(f(f(x)))))) == x))
infer(rup, Not(f(f(f(f(f(f(x)))))) == x))
infer(euf(Not(f(f(f(f(f(f(x)))))) == x),
f(f(x)) == x,
cc(f(x) == f(f(f(x)))),
cc(f(f(f(f(x)))) == f(f(x))),
cc(f(f(f(f(f(x))))) == f(x)),
cc(f(f(f(f(f(f(x)))))) == f(f(f(f(x)))))),
Or(Not(f(f(x)) == x), f(f(f(f(f(f(x)))))) == x))
infer(rup, Not(f(f(x)) == x))
infer(rup, f(f(f(x))) == x)
infer(euf(Not(f(f(f(f(f(f(x)))))) == x),
f(f(f(x))) == x,
cc(f(x) == f(f(f(f(x))))),
cc(f(f(f(f(f(x))))) == f(f(x))),
cc(f(f(f(f(f(f(x)))))) == f(f(f(x))))),
f(f(f(f(f(f(x)))))) == x)
infer(rup, False)From SMTLIB:
(declare-fun f (Int) Int)
(define-const $24 Int (f x))
(define-const $25 Int (f $24))
(define-const $26 Int (f $25))
(define-const $27 Bool (= $26 x))
(define-const $28 Bool (= $25 x))
(assume $27 $28)
(define-const $30 Int (f $26))
(define-const $31 Int (f $30))
(define-const $32 Int (f $31))
(define-const $33 Bool (= $32 x))
(assume (not $33))
(declare-fun rup () Proof)
(infer (not $33) rup)
(declare-fun euf (Bool Bool Proof Proof Proof Proof) Proof)
(declare-fun cc (Bool) Proof)
(define-const $42 Bool (= $32 $30))
(define-const $43 Proof (cc $42))
(define-const $40 Bool (= $31 $24))
(define-const $41 Proof (cc $40))
(define-const $38 Bool (= $30 $25))
(define-const $39 Proof (cc $38))
(define-const $36 Bool (= $24 $26))
(define-const $37 Proof (cc $36))
(define-const $34 Bool (not $33))
(define-const $44 Proof (euf $34 $28 $37 $39 $41 $43))
(infer (not $28) $33 $44)
(infer (not $28) rup)
(infer $27 rup)
(declare-fun euf (Bool Bool Proof Proof Proof) Proof)
(define-const $49 Bool (= $32 $26))
(define-const $50 Proof (cc $49))
(define-const $47 Bool (= $31 $25))
(define-const $48 Proof (cc $47))
(define-const $45 Bool (= $24 $30))
(define-const $46 Proof (cc $45))
(define-const $51 Proof (euf $34 $27 $46 $48 $50))
(infer $33 $51)
(infer rup)The empty theory of first-order logic.
merge()
Roots: n1 := find(n1), r2 := find(n2)
assume (r1 != r2)
Erase: for each p in r1.P such that p.cg == p:
erase from table
Update root: r1.find := r2
Justify: ....
Insert: for each p in r1.P:
p.cg = insert p in etable
if p.cg == p:
append p to r2.P
else
add (p.cg == p) to "to_merge"
unmerge():
Erase: for each p in r2.P added from r1.P:
erase p from table
Unjustify: ....
Revert root: r1.find := r1
Insert: for each p in r1.P:
insert p if n was cc root before merge
condition for being cc root before merge:
p.cg == p or !congruent(p, p.cg)
congruent(p,q) := roots of p.ts = roots of q.tsA justification is a reason for merging two nodes. There are two possible reasons for merging nodes:
NB: is justified recursively by justifying .
Invariant: Every non-root node points to a linked list of justifications leading to the root
NB The linked list does not follow direction of union-find.
Missed justifications:
egg (Flatt et al., 2022) keeps track of potential extra paths to find short proofs.
for SMT: data-structure overhead vs. amortized effect of backtracking?
Fine-grained proofs - pro/cons:
A coarse-grained proof object (N. Shankar chat @ FLoC):
(set-option :sat.euf true)
(set-option :sat.smt.proof eufproof.smt2)
(declare-fun f (Int) Int)
(declare-const x Int)
(assert (or (= (f (f (f x))) x) (= (f (f x)) x)))
(assert (not (= (f (f (f (f (f (f x)))))) x)))
(check-sat)assume(Or(f(f(f(x))) == x, f(f(x)) == x))
assume(Not(f(f(f(f(f(f(x)))))) == x))
infer(rup, Not(f(f(f(f(f(f(x)))))) == x))
infer(euf(Not(f(f(f(f(f(f(x)))))) == x),
f(f(x)) == x,
cc(f(x) == f(f(f(x)))),
cc(f(f(f(f(x)))) == f(f(x))),
cc(f(f(f(f(f(x))))) == f(x)),
cc(f(f(f(f(f(f(x)))))) == f(f(f(f(x)))))),
Or(Not(f(f(x)) == x), f(f(f(f(f(f(x)))))) == x))
infer(rup, Not(f(f(x)) == x))
infer(rup, f(f(f(x))) == x)
infer(euf(Not(f(f(f(f(f(f(x)))))) == x),
f(f(f(x))) == x,
cc(f(x) == f(f(f(f(x))))),
cc(f(f(f(f(f(x))))) == f(f(x))),
cc(f(f(f(f(f(f(x)))))) == f(f(f(x))))),
f(f(f(f(f(f(x)))))) == x)
infer(rup, False)SMT solving based on Dual Simplex maintain a tableau of the form:
where are basic and are non-basic variables.
x, y = Reals('x y')
solve([x >= 0, Or(x + y <= 2, x + 2*y >= 6),
Or(x + y >= 2, x + 2*y > 4)])
Introduce slacks to define terms
Formula using slacks:
Only bounds (e.g., ) are asserted during search.
A tableau is infeasible if there is a row such that
(We omit the symmetric case for upper bounds, )
The explanation for infeasibility are the literals or , respectively or .
Slack variables are unfolded into their definitions.
The justification are the coefficients
Dual simplex infeasibility
Bounds propagation
Equality propagation
Properties of div, mod, rem, to_int
Cuts
GCD tests
Non-linear arithmetic
(farkas c1 (<=? g1 g1') c2 (<=? g2 g2') ... cn (<=? gn gn'))
(bound c1 (<=? g1 g1') c2 (<=? g2 g2') ... cn (<=? gn gn') (not (<= e1 e2)))
(implied-eq c0 c1 (<=? g1 g1') c2 (<=? g2 g2') ... cn (<=? gn gn') (not (= e1 e2)))
where <=? is <=, <, > >=, =, and their negations; and are integer constants.
(bound c1 (<=? g1 g1') (<=? g2 g2') ... (<=? gn gn') (not (<= e1 e2)))
(implied-eq c0 c1 (<=? g1 g1') c2 (<=? g2 g2') ... cn (<=? gn gn') (not (= e1 e2)))
Check:
Use Skolem functions, not fresh constants (Katz et al., 2016), when instantiating existentials
(=> (forall ((x I)) (= (A i) (B i))) (= A B))
is instantiated as
A = B or select(A, diff(A,B)) != select(B, diff(A, B))
Use Skolem functions to replace extended operators
(assert (contains a b))
Is rewritten into
(assert (= a (str.++ (contains.left a b) b (contains.right a b)))) (choose ((x T)) (p x))
Used in proof generating systems (veriT, SMTInterpol).
SMTLIB Google Group (Hoenicke, Reynolds, others active)
Some diversity in formats generated by provers and not clear the format from provers should/can be aligned.
(farkas c1 (<=? g1 g1') c2 (<=? g2 g2') ... cn (<=? gn gn'))
where <=? is <=, <, > >=, =, and their negations; and are integer constants.
Define normalization function:
ci * (>= gi gi') -> -ci * (<= gi gi')
ci * (< gi gi') -> ci * (<= (+ 1 gi) gi') if gi is Int
ci * (> gi gi') -> -ci * (< gi gi')
ci * (not (<= gi gi')) -> -ci * (< gi gi')
ci * (<= gi gi') -> -ci * (<= gi' gi) if ci < 0
etc
Let be indices of inequalities, equalities, and reduce using the equalities . Then is non-negative if contains a strict inequality, or negative if it only contains weak inequalities.
Tradeoff between generic rules and simpler rules.
Unclear if added complexity of checking generic rules provides return on investment.
A proof checker could map to nucleus of SMTInterpol format before validating premises.
Model transformers, dual to proofs
Pre and in-processing proofs
Sustainable proofs
Beyond the power of regular resolution
Proofs for