Nikolaj Bjørner
Microsoft Research
nbjorner@microsoft.com |
SMT and Z3
Theories
Programming Solvers with an application to MaxSAT
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()
sat unsat
model (clausal) proof
correction set core
local min correction set local min core
min correction set min core
Theory Solvers
(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)
(declare-const xR Real)
(declare-const yR Real)
(declare-const x Int)
(declare-const y Int)
(declare-const a Int)
(assert (< (+ xR yR) a))
(assert (> (+ x y) a))
(assert (or (= x xR) (< x xR (+ x 1)) (< (- x 1) xR x)))
(assert (or (= y yR) (< y yR (+ y 1)) (< (- y 1) yR y)))
(check-sat)
(get-model)
(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
Other theories that admit custom solvers:
Bi-linear real arithmetic
Non-unit two-variable per inequality
At most one unit positive variable per inequality (Horn)
(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) ...))
(assert (and (>= t21 0) ...))
(assert (and (>= t31 0) (>= t32 (+ t31 2)) (<= (+ t32 3) 8)))
(assert (or ... (>= t21 (+ t11 2))))
(assert (or (>= t21 (+ t31 2)) ...))
(check-sat)
(get-model) ; display the model
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
- are basic (dependent)
- are non-basic
Initial values:
Bounds :
, make non-basic.
- are basic (dependent)
- are non-basic
(define-sort A () (Array Int Int))
(declare-fun x () Int)
(declare-fun y () Int)
(declare-fun a1 () A)
(push)
(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)
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)
(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)
(declare-datatypes () ((Tree Empty (Node (left Tree) (data Int) (right Tree)))))
(declare-const t Tree)
(assert (not (= t Empty)))
(check-sat)
(get-model)
sat
(model
(define-fun t () Tree
(Node Empty 0 Empty))
)
from z3 import *
x, y, z = Reals('x y z')
solve(x**2 + y**2 < 1, x*y > 1)
solve(x**2 + y**2 < 1, x*y > 0.4)
solve(x**2 + y**2 < 1, x*y > 0.4, x < 0)
solve(x**5 - x - y == 0, Or(y == 1, y == -1))
; Code written by TheoryGuru
(declare-fun v1 () Real)
(declare-fun v2 () Real)
(declare-fun v3 () Real)
(declare-fun v4 () Real)
(declare-fun v5 () Real)
(declare-fun v6 () Real)
(declare-fun v7 () Real)
(declare-fun v8 () Real)
(declare-fun v9 () Real)
(declare-fun v10 () Real)
(declare-fun v11 () Real)
(declare-fun v12 () Real)
; define true assumption
(assert (and
(> (* 2 v11 v6 v9) (+ (* v12 (^ v6 2)) (* v8 (^ v9 2))))
(> (+ (* 2 v10 v11 v4 v6) (* v12 v5 (^ v6 2)) (* -2 v12 v4 v6 v7) (* v12 (^ v4 2) v8) (* -2 v11 v5 v6 v9) (* 2 v11 v4 v7 v9) (* 2 v10 v6 v7 v9) (* v5 v8 (^ v9 2))) (+ (* (^ v11 2) (^ v4 2)) (* (^ v10 2) (^ v6 2)) (* 2 v10 v4 v8 v9) (* (^ v7 2) (^ v9 2))))
(> v1 0)
(> v2 0)
(> v3 0)
(> v4 0)
(> v6 0)
(> v9 0)
(= (+ (* v1 v11) (* v3 v7) (* v2 v8)) 0)
(= (+ (* v1 v12) (* v11 v2) (* v10 v3)) 0)
(= (+ (* v1 v10) (* v3 v5) (* v2 v7)) 0)
))
; define negative of the hypothesis
(assert (not (>= (* v5 v8) (^ v7 2))))
(check-sat)
(exit)
How can I sepecify to be injective?
(declare-sort A)
(declare-sort B)
(declare-fun f (A) B)
(assert (forall ((x A) (y A)) (=> (= (f x) (f y)) (= x y))))
(assert (forall ((x A) (y A))
(! (=> (= (f x) (f y)) (= x y)) :pattern ((f x) (f y)))))
E-matching instantiates quantifier for every occurrence for terms produced during search.
Some formulations are better than others
(declare-fun g (B) A)
(assert (forall ((x A)) (! (= (g (f x)) x) :pattern ((f x)))))
Synthesize generalized instantiation sets using grammar rules.
Applies to wide range of classes.
(set-option :smt.mbqi true)
;; f an g are "streams"
(declare-fun f (Int) Int)
(declare-fun g (Int) Int)
;; the segment [a, n + a] of stream f is equal to the segment [0, n] of stream g.
(declare-const n Int)
(declare-const a Int)
(assert (forall ((x Int)) (=> (and (<= 0 x) (<= x n))
(= (f (+ x a)) (g x)))))
;; adding some constraints to a
(assert (> a 10))
(assert (>= (f a) 2))
(assert (<= (g 3) (- 10)))
(check-sat)
(get-model)
from z3 import *
x, u, v = Ints('x u v')
stamp = ForAll([x],
Implies(x >= 25,
Exists([u,v],
And(u >= 0, v >= 0, x == 3*u + 5*v))))
print stamp
print Tactic('qe').apply(stamp)
badstamp = ForAll([u,v], Implies(And(u >= 0, v >= 0), x != 3*u + 5*v))
x1 = Int('x1')
last_bad = ForAll([x1],
Implies(x1 > x,
Exists([u,v],
And(u >= 0, v >= 0, x1 == 3*u + 5*v))))
qe_solver = Then('qe','smt').solver()
qe_solver.add(last_bad)
qe_solver.add(badstamp)
print qe_solver.check()
print qe_solver.model()
|
|
Strings: Decidability and efficiency.
Certificates: Modular, Clausal
New, non-disjoint, theories
Programming Solvers with an application to MaxSAT
Optimization as SMT with preferred models
An introduction to cores and correction sets
Show examples of algorithms on top of SMT/Z3
Three main SMT extensions
|
|
Equilvalent formulations
|
|
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()
(declare-const x11 Bool) .... (declare-const y3 Bool)
(define-fun b2i ((b Bool)) Int (ite b 1 0))
; A VM is on exactly one server.
(assert (= (+ (b2i x11) (b2i x12) (b2i x13)) 1))
(assert (= (+ (b2i x21) (b2i x22) (b2i x23)) 1))
(assert (= (+ (b2i x31) (b2i x32) (b2i x33)) 1))
; A server is used if it contains a VM
(assert (=> (y1 (and x11 x21 x31)))
(assert (=> (y2 (and x12 x22 x32)))
(assert (=> (y3 (and x13 x23 x33)))
; Capability constraints
(assert (<= (+ (* 100 (b2i x11)) (* 50 (b2i x21)) (* 15 (b2i x31))) (* 100 (b2i y1))))
(assert (<= (+ (* 100 (b2i x12)) (* 50 (b2i x22)) (* 15 (b2i x32))) (* 75 (b2i y2))))
(assert (<= (+ (* 100 (b2i x13)) (* 50 (b2i x23)) (* 15 (b2i x33))) (* 200 (b2i y3))))
; Optimization goals are expressed via assert-soft.
(assert-soft (not y1) :id num_servers) (assert-soft (not y2) :id num_servers)
(assert-soft (not y3) :id num_servers) (assert-soft (not y1) :id costs :weight 10)
(assert-soft (not y2) :id costs :weight 5) (assert-soft (not y3) :id costs :weight 20)
(set-option :verbose 10)
(check-sat)
(get-objectives)
(M)US (Minimal) unsatisfiable subset
(M)SS (Maximal) satisfiable subset
(M)CS (Minimal) correction set
(Prime) implicant
|
|
def ff(s, p):
return is_false(s.model().eval(p))
def marco(s, ps):
map = Solver()
while map.check() == sat:
seed = {p for p in ps if not ff(map, p)}
if s.check(seed) == sat:
mss = get_mss(s, seed, ps)
map.add(Or(ps - mss))
yield "MSS", mss
else:
mus = get_mus(s, seed)
map.add(Not(And(mus)))
yield "MUS", mus
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(test, sub):
return qx(test, set([]), set([]), sub)
def qx(test, B, D, C):
if {} != D:
if test(B):
return set([])
if len(C) == 1:
return C
C1, C2 = split(C)
D1 = qx(test, B | C1, C1, C2)
D2 = qx(test, B | D1, D1, C1)
return D1 | D2
def test(s):
return lambda S: s.check([f for f in S]) == unsat
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 = quick_explain(test(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 :
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)
(declare-const a Bool)
(declare-const b Bool)
(declare-const c Bool)
(assert-soft a :weight 1)
(assert-soft b :weight 1) (assert-soft b :weight 1)
(assert-soft c :weight 1) (assert-soft c :weight 1) (assert-soft c :weight 1)
(assert (= a c))
(assert (not (and a b)))
(check-sat)
(get-model)
- hard constraints
- soft constraints
A:
A':
Lemma: for any model of ,
Proof: min:
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) }
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 [2] we combine MUS and MCS steps.
a, b, c, d = Bools('a b c d')
s = Solver()
s.add(Implies(a, b), Implies(c, d)) # background formula
print s.consequences([a, c], # assumptions
[b, c, d]) # what is implied?
(sat, [Implies(c, c), Implies(a, b), Implies(c, d)])
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
|
|
We looked at:
Applications: configuration, model checking, scheduling
Queries: MaxSAT, backbones
Theories: a rough overview
Algorithms: as extensions or layers over SAT/SMT