|
Nikolaj Bjørner
Microsoft Research
nbjorner@microsoft.com |
|
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 |
|
Slide is by Joao Marques-Silva
sat unsat
model (clausal) proof
correction set core
local min correction set local min core
min correction set min core
Optimization as SMT with preferred models
An introduction to cores and correction sets
Show examples of algorithms on top of SMT/Z3
(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 (N. Bjørner and Narodytska, 2015) we combine MUS and MCS steps.
def block_model(s):
m = s.model()
s.add(Or([ f() != m[f] for f in m.decls() if f.arity() == 0]))
It is naturally also possible to block models based on the evaluation
of only a selected set of terms, and not all constants mentioned in the
model. The corresponding block_model is then
def block_model(s, terms):
m = s.model()
s.add(Or([t != m.eval(t, model_completion=True) for t in terms]))
A loop that cycles through multiple solutions can then be formulated:
def all_smt(s, terms):
while sat == s.check():
print(s.model())
block_model(s, terms)x, y = Ints('x y')
s = Solver()
s.add(1 <= x, x <= y, y <= 3)
s.push()
print("all ", x)
all_smt(s, [x])
s.pop()
print("all ", x, y)
all_smt(s, [x, y])
all x
[x = 1, y = 1]
[x = 2, y = 2]
[x = 3, y = 3]
all x y
[x = 3, y = 3]
[x = 2, y = 2]
[x = 1, y = 1]
[x = 1, y = 2]
[x = 1, y = 3]
[x = 2, y = 3]def all_smt(s, initial_terms):
def block_term(s, m, t):
s.add(t != m.eval(t, model_completion=True))
def fix_term(s, m, t):
s.add(t == m.eval(t, model_completion=True))
def all_smt_rec(terms):
if sat == s.check():
m = s.model()
yield m
for i in range(len(terms)):
s.push()
block_term(s, m, terms[i])
for j in range(i):
fix_term(s, m, terms[j])
yield from all_smt_rec(terms[i:])
s.pop()
yield from all_smt_rec(list(initial_terms))class BlockTracked(UserPropagateBase):
def __init__(self, s):
UserPropagateBase.__init__(self, s)
self.trail = []
self.lim = []
self.add_fixed(lambda x, v : self._fixed(x, v))
self.add_final(lambda : self._final())
def push(self):
self.lim += [len(self.trail)]
def pop(self, n):
self.trail = self.trail[0:self.lim[len(self.lim) - n]]
self.lim = self.lim[0:len(self.lim)-n]
def _fixed(self, x, v):
self.trail += [(x, v)]
def _final(self):
print(self.trail)
self.conflict([x for x, v in self.trail])s = SimpleSolver()
b = BlockTracked(s)
x, y, z, u = Bools('x y z u')
b.add(x)
b.add(y)
b.add(z)
s.add(Or(x, Not(y)), Or(z, u), Or(Not(z), x))
print(s.check())
python propagator.py
[(z, True), (x, True), (y, False)]
[(z, True), (x, True), (y, True)]
[(z, False), (y, False), (x, False)]
[(z, False), (y, False), (x, True)]
[(z, False), (y, True), (x, True)]
unsat add_eq
propagate