Nikolaj Bjørner
Microsoft Research
nbjorner@microsoft.com |
Goal of this tutorial is to give an overview of what you can do with Z3 from the perspectives of:
A state-of-art Satisfiability Modulo Theories (SMT) solver from Microsoft Research
Targets applications in software engineering
Leonardo de Moura, Nikolaj Bjørner, and Christoph Wintersteiger
On github: https://github.com/Z3prover/z3.git
|
|
Tie, Shirt = Bools('Tie Shirt')
s = Solver()
s.add(Or(Tie, Shirt), Or(Not(Tie), Shirt), Or(Not(Tie), Not(Shirt)))
print s.check()
print s.model()
I = IntSort()
f = Function('f', I, I)
x, y, z = Ints('x y z')
A = Array('A',I,I)
fml = Implies(x + 2 == y, f(Select(Store(A, x, 3), y - 2)) == f(y - x + 1))
s = Solver()
s.add(Not(fml))
print s.check()
Efficient indexing (two-watch literal)
Non-chronological backtracking (backjumping)
Lemma learning
Variable and phase selection
Garbage collection, restarts
: Atomic predicates
: Literals over atoms
: Partial assignment without complementary literals.
: A clause - disjunction of literals.
: a decision literal.
: a literal with explanation clause , where is a disjunct in . The literal is assigned relative to partial assignment when all other literals in occur negatively in .
: A set of clauses.
Theorem 1.
For every , exactly one of the two conditions hold:
Corollary 1.
CDCL realizes the dichotomy by interleaving model and consequence finding:
Corollary 2.
If , then for every
where : .
Corollary 3.
If , i.e., for some , then
for every consequence from using forced literals in it is the case
that and there is no
such that .
Main invariants preserved by CDCL:
if
(declare-sort A)
(declare-fun f (A) A)
(declare-const x A)
(assert (= (f (f x)) x))
(assert (= (f (f (f x))) x))
(check-sat)
(assert (not (= (f x) x)))
(check-sat)
Variants of union-find are effective to decide equality.
Congruence rule
A satisfiable version:
(set-logic QF_IDL) ; optional in Z3
(declare-fun t11 () Int)
(declare-fun t12 () Int)
(declare-fun t21 () Int)
(declare-fun t22 () Int)
(declare-fun t31 () Int)
(declare-fun t32 () Int)
(assert (and (>= t11 0) (>= t12 (+ t11 2)) (<= (+ t12 1) 8)))
(assert (and (>= t21 0) (>= t22 (+ t21 3)) (<= (+ t22 1) 8)))
(assert (and (>= t31 0) (>= t32 (+ t31 2)) (<= (+ t32 3) 8)))
(assert (or (>= t11 (+ t21 3)) (>= t21 (+ t11 2))))
(assert (or (>= t11 (+ t31 2)) (>= t31 (+ t11 2))))
(assert (or (>= t21 (+ t31 2)) (>= t31 (+ t21 3))))
(assert (or (>= t12 (+ t22 1)) (>= t22 (+ t12 1))))
(assert (or (>= t12 (+ t32 3)) (>= t32 (+ t12 1))))
(assert (or (>= t22 (+ t32 3)) (>= t32 (+ t22 1))))
(check-sat)
(get-model) ; display the model
Logic | Fragment | Solver | Example |
---|---|---|---|
LRA | Linear Real Arithmetic | Dual Simplex | |
LIA | Linear Integer Arithmetic | CutSat | |
LIRA | Mixed Real/Integer | Cuts + Branch | |
IDL | Integer Difference Logic | Floyd-Warshall | |
RDL | Real Difference Logic | Bellman-Ford | |
UTVPI | Unit two-variable per inequality | ||
NRA | Polynomial Real Arithmetic | Model based CAD | |
NIA - There is no decision procedure for integer polynomial constraints
Theories, where Z3 falls back to general solvers:
Bi-linear real arithmetic
Non-unit two-variable per inequality
At most one unit positive variable per inequality (Horn)
Solve difference logic using graph Bellman-Ford network flow algorithm. Negative cycle unsat.
General form and
Only bounds (e.g., ) are asserted during search.
- are basic (dependent)
- are non-basic
; This example illustrates different uses of the arrays
; supported in Z3.
; This includes Combinatory Array Logic
; (de Moura & Bjorner, FMCAD 2009).
;
(define-sort A () (Array Int Int))
(declare-fun x () Int)
(declare-fun y () Int)
(declare-fun z () Int)
(declare-fun a1 () A)
(declare-fun a2 () A)
(declare-fun a3 () A)
(push) ; illustrate select-store
(assert (= (select a1 x) x))
(assert (= (store a1 x y) a1))
(check-sat)
(get-model)
(assert (not (= x y)))
(check-sat)
(pop)
(define-fun all1_array () A ((as const A) 1))
(simplify (select all1_array x))
(define-sort IntSet () (Array Int Bool))
(declare-fun a () IntSet)
(declare-fun b () IntSet)
(declare-fun c () IntSet)
(push) ; illustrate map
(assert (not (= ((_ map and) a b) ((_ map not) ((_ map or) ((_ map not) b) ((_ map not) a))))))
(check-sat)
(pop)
(push)
(assert (and (select ((_ map and) a b) x) (not (select a x))))
(check-sat)
(pop)
(push)
(assert (and (select ((_ map or) a b) x) (not (select a x))))
(check-sat)
(get-model)
(assert (and (not (select b x))))
(check-sat)
;; unsat, so there is no model.
(pop)
(push) ; illustrate default
(assert (= (default a1) 1))
(assert (not (= a1 ((as const A) 1))))
(check-sat)
(get-model)
(assert (= (default a2) 1))
(assert (not (= a1 a2)))
(check-sat)
(get-model)
(pop)
(exit)
A = Array(Index, Elem) # array sort
a[i] # index array 'a' at index 'i'
# Select(a, i)
Store(a, i, v) # update array 'a' with value 'v' at index 'i'
# = lambda j: If(i == j, v, a[j])
Const(v, A) # constant array
# = lambda j: v
Map(f, a) # map function 'f' on values of 'a'
# = lambda j: f(a[j])
Ext(a, b) # Extensionality
# Implies(a[Ext(a, b)] == b[Ext(a, b)], a == b)
Example Store(a, i, v)
Each occurrence of Store(a, i, v)
and b[j]
,
Store(a, i, v)[j] == If(i == j, v, a[j])
Store(a, i, v)[i] == v
Each shared occurrences of :
Suppose we are given a model satisfying all asserted equalities.
Store
axioms.
Use current partial interpretation to guide axiom instantiation.
Rough sketch:
Also important to limit set of pairs.
(define-fun is-power-of-two ((x (_ BitVec 4))) Bool
(= #x0 (bvand x (bvsub x #x1))))
(declare-const a (_ BitVec 4))
(assert
(not (= (is-power-of-two a)
(or (= a #x0)
(= a #x1)
(= a #x2)
(= a #x4)
(= a #x8)))))
(check-sat)
(declare-fun X () (_ FloatingPoint 11 53))
(assert (fp.isNormal X))
(assert (not (fp.isSubnormal X)))
(assert (not (fp.isZero X)))
(assert (not (fp.isInfinite X)))
(assert (not (fp.isNaN X)))
(assert (not (fp.isNegative X)))
(assert (fp.isPositive X))
(check-sat)
(get-model)
(declare-const a String)
(declare-const b String)
(declare-const c String)
(assert (str.prefixof b a))
(assert (str.suffixof c a))
(assert (= (str.len a) (+ (str.len b) (str.len c))))
(assert (not (= a (str.++ b c))))
(check-sat)
Formulas are ground unsatisfiable.
Check
while is SAT with model :
if is UNSAT return SAT
model for
find , such that .
return UNSAT
Quite powerful when search space for is finite
Two players try to find values
Players control their variables
Some play loses at round .
Player has lost at round
Player found a model at round , .
induces an evaluation on a subset of literals in , such that .
is an unsatisfiable core for .
Model Based Projection
Find a , such that Any .
Then can block .
Idea: Use to find a sufficient .
def sign(M,a): if M(a) return a else return a
Theory: Non-linear polynomial arithmetic
Partial CAD - model-based projection [30]
CAD decomposes into sign-invariant cylinders.
Choose the cylinder where is a member.
Sat based MBP:
Given:
Find: , free for
Such that:
Contrast this with Core-based MBP:
Given: .
Find: , free for
Such that:
Claim: the two compute the same solutions if the projection operators are independent of the value of .
def level(j,a): return max level of bound variable in atom a of parity j
def strategy(M,j): return
def tailv(j): return
j = 1
M = null
while True:
if strategy(M, j) is unsat:
if j == 1:
return F is unsat
if j == 2:
return F is sat
C = Core(, strategy(M, j))
J = Mbp(tailv(j), C)
j = index of max variable in J of same parity as j
= J
M = null
else:
M = current model
j = j + 1
Other main ingredient of QSAT is option for players to narrow options of opponents by revealing a strategy
Developing practical strategies is work in progress
Solver methods
|
|
Solver methods
|
Unsatisfiable cores contain tracked literals. |
Solver methods
|
Assertions added within a scope are removed when the scope is popped. |
Solver methods
|
Is the solver state satisfiable modulo the assumption literals . The solver state is the conjunction of assumption literals and assertions that have been added to the solver in the current scope. |
Methods
|
|
(M)US (Minimal) unsatisfiable subset
(M)SS (Maximal) satisfiable subset
(M)CS (Minimal) correction set
(Prime) implicant
|
|
def tt(s, f):
return is_true(s.model().eval(f))
def get_mss(s, mss, ps):
ps = ps - mss
backbones = set([])
while len(ps) > 0:
p = ps.pop()
if sat == s.check(mss | backbones | { p }):
mss = mss | { p } | { q for q in ps if tt(s, q) }
ps = ps - mss
else:
backbones = backbones | { Not(p) }
return mss
Use built-in core minimization:
s.set("sat.core.minimize","true") # For Bit-vector theories
s.set("smt.core.minimize","true") # For general SMT
Or roll your own:
def quick_explain(s, mus):
return qx(s, mus, set([]), False)
def qx(s, mus, S, has_support):
if has_support:
if s.check(S) == unsat:
return set([]), { l for l in s.unsat_core() }
if len(mus) == 1:
return S, mus
mus1, mus2 = split(mus)
mus2, S = qx(s, mus2, S | mus1, {} != mus1)
mus1 = S & mus1
mus2, S = qx(s, mus1, S | mus2 - mus1, {} != mus2)
return S & mus, S - mus2
def split(S1):
S1, S2 = S1.copy(), set([])
for i in range(len(S1)/2):
S2 |= { S1.pop() }
return S1, S2
s = Solver()
a, b, c, d, e = Bools('a b c d e')
s.add(Or(a, b))
s.add(Or(Not(a), Not(b)))
s.add(Or(b, c))
s.add(Or(Not(c), Not(a)))
print s.check([a,b,c,d,e])
print s.unsat_core()
mus, S = quick_explain(s, {a,b,c,d})
def all_mss(s, ps):
while sat == s.check():
mss = get_mss(s, { p for p in ps if tt(s, p) }, ps)
s.add(Or(ps - mss))
yield "MSS", mss
Find all satisfying subsets among :
Three main SMT extensions
|
|
Equilvalent formulations
|
|
Box | |
Lex | |
Pareto | |
MaxSAT solver
Primal simplex optimization
Local optimization over can determine if is unbounded
Alternatively use extended number field
We assume if is unbounded, so .
Optimization methods
|
|
x, y = Ints('x y')
opt = Optimize()
opt.set(priority='pareto')
opt.add(x + y == 10, x >= 0, y >= 0)
mx = opt.maximize(x)
my = opt.maximize(y)
while opt.check() == sat:
print mx.value(), my.value()
Typical definition: Minimize the number of violated soft assertions.
Is built-in, based on MaxSAT algorithms.
(declare-const a Bool)
(declare-const b Bool)
(declare-const c Bool)
(assert-soft a :weight 1)
(assert-soft b :weight 2)
(assert-soft c :weight 3)
(assert (= a c))
(assert (not (and a b)))
(check-sat)
(get-model)
A:
A':
Lemma: for any model of ,
Proof: min:
def add_def(s, fml):
name = Bool("%s" % fml)
s.add(name == fml)
return name
def relax_core(s, core, Fs):
prefix = BoolVal(True)
Fs -= { f for f in core }
for i in range(len(core)-1):
prefix = add_def(s, And(core[i], prefix))
Fs |= { add_def(s, Or(prefix, core[i+1])) }
def maxsat(s, Fs):
cost = 0
Fs0 = Fs.copy()
while unsat == s.check(Fs):
cost += 1
relax_core(s, s.unsat_core(), Fs)
return cost, { f for f in Fs0 if tt(s, f) }
A:
A':
Lemma: for any model of ,
Proof: min:
def relax_mcs(s, mcs, Fs):
prefix = BoolVal(False)
Fs -= { f for f in mcs }
s.add(Or(mcs))
for i in range(len(mcs)-1):
prefix = add_def(s, Or(mcs[i], prefix))
Fs |= { add_def(s, And(prefix, mcs[i+1])) }
def maxsat(s, Fs0):
Fs = Fs0.copy()
cost = len(Fs)
while s.check() == sat:
mss = { f for f in Fs if tt(s, f) }
model1 = get_mss(s, mss, Fs)
mcs = Fs - mss
if cost > len(mcs):
cost = len(mcs)
model = model1
relax_mcs(s, [ f for f in mcs ], Fs)
return cost, [ f for f in Fs0 if is_true(model.eval(f)) ]
MCS alone is inefficient. In [12] we combine MUS and MCS steps.
Find all unit literals
Useful for finding fixed parameters [29]
Generalizes to Horn consequences:
def scoped_consequence(phi, assumptions, terms):
s = Solver()
s.add(phi)
if s.check(assumptions) == unsat:
return unsat
model = s.model()
for t in terms:
val = model.eval(t)
s.push()
s.add(t != val)
if s.check(assumptions) == unsat:
print Implies(And(s.unsat_core()), t == val)
s.pop()
scoped_consequence(And(Implies(a, b), Implies(b, c)), [a,d], [b,c,d])
Implies(And(a), b == True)
Implies(And(a), c == True)
Implies(And(d), d == True)
def assumption_consequence(phi, assumptions, ps):
s = Solver()
s.add(phi)
if s.check(assumptions) == unsat:
return unsat
model = s.model()
for p in ps:
if is_true(model.eval(p)):
np = Not(p)
p = p
else:
np = p
p = Not(p)
if s.check(assumptions + [np]) == unsat:
core = [a for a in s.unsat_core() if not eq(a, np)]
print Implies(And(core), p)
def builtin_consequences(phi, assumptions, terms):
s = Solver()
s.add(phi)
return s.consequences(assumptions, terms)
print builtin_consequences(And(Implies(a, b), Implies(b, c)), [a, d], [b, c, d])
Built-in consequence finding can be 100x faster than soft-coded versions.
assert at level 2.
Given formula find smallest set of formulas such that
|
|
Fixedpoint methods
|
|
are state variables.
is a monome (a conjunction of literals).
is a clause (a disjunction of literals).
Convention: is a monome over variables , is a renaming of the same monome to variables .
repeat until ∞
Candidate If for some , , then add to .
Unfold If , then set .
repeat until ∞
Unreachable For , if , return Unreachable.
Reachable If , return Reachable.
repeat until ∞
repeat until ∞
Conflict Let : given a candidate model and clause , such that
Leaf If , and is unsatisfiable,
then add to .
repeat until ∞
Decide Add to if , .
Pssst: I am replacing by greek letters.
Conflict Let : given a candidate model and clause , such that , if , then conjoin to , for .
Conflict Let : given a candidate model and a formula , such that , , then conjoin to , for .
Recall predicate transformer for McCarthy91:
Decide now spawns two children.
Decide If and there are consistent such that and , then add , to .
We also have to take DAG unfolding into acount.
Base Mark if or if there is a consistent
such that and .
Close Mark if all children are marked.
Reachable If is marked, return Reachable.
Decide and Conflict pushing a goal over
Reachable If is satisfiable, then return Reachable.
Decide For for , add to if:
- .
- .
-
- is disjoint from for every .
Decide For for , add to if:
- .
- .
-
- is disjoint from for every .
Close For for , if is satisfiable, but is unsatsifiable,
then update , where .
def sign(s, x):
if tt(s, x):
return x
return Not(x)
def cute(A,B,xs):
sA = Solver()
sB = Solver()
sA.add(A)
sB.add(B)
I = []
while sat == sB.check():
if unsat == sA.check([ sign(sB, x) for x in xs ]):
I1 = Not(And(sA.unsat_core()))
sB.add(I1)
I += [I1]
else:
return None
return And(I)
Can also be viewed as an iteration of IC3 steps. Generalization to EPR [7].
Option 1: use BDDs to represent tables.
Option 2: build tables from ternary bit-vectors.
Option 3,4,..: Use hash tables, B-trees, bit-maps