Programming Z3

TU Wien Guest Lectures, October 2025

Nikolaj Bjørner
Microsoft Research

Contents

Programming Z3

Solver methods

s = Solver()


s.add($\varphi$)
s.assert_and_track($\varphi, \ell$)

s.push()
s.pop()

s.check()
s.check($[\ell_1,\ldots, \ell_n]$)

s.model()
s.proof()
s.unsat_core()   

s.statistics()     


  • Create a solver

  • Add assertion to solver state

  • Create local scopes

  • Check satisfiability

  • Retrieve models, proofs, cores

  • Additional information

Assertions

Solver methods

s.add($\varphi$)
s.assert_and_track($\varphi, \ell$)



Assert $\varphi$ to the solver state. Optionally, track the assertion $\varphi$ by a literal $\ell$.

Unsatisfiable cores contain tracked literals.

Scopes

Solver methods

s.push()

s.pop()



Add or remove a scope from the solver.

Assertions added within a scope are removed when the scope is popped.

Check Satisfiability

Solver methods

s.check()

s.check($[\ell_1,\ldots, \ell_n]$)


Is the solver state satisfiable?

Is the solver state satisfiable modulo the assumption literals $\ell_1, \ldots, \ell_n$.

The solver state is the conjunction of assumption literals and assertions that have been added to the solver in the current scope.

Certificates

Methods


s.model()

s.proof()

s.unsat_core()   


An interpretation satisfying current constraints.

A proof term certifying unsatisfiability of constraints.

A subset of assumptions/tracked assertions that suffice to establish unsatisfiability.

Extended SAT queries

Silva

Slide is by Joao Marques-Silva

Logical Queries - SAT+



sat $\varphi$ unsat


model $\varphi$ (clausal) proof


correction set $\subseteq \varphi_1, \ldots, \varphi_n \supseteq $ core


local min correction set $ \subseteq \varphi_1, \ldots, \varphi_n \supseteq$ local min core


min correction set $ \subseteq \varphi_1, \ldots, \varphi_n \supseteq$ min core


$\max x \varphi(x)$

Logical Queries

\[\begin{mdmathpre}%mdk \mathrm{Satisfiability}~~~&~\varphi \rightsquigarrow \mathid{sat},~\mathid{unsat},~\mathid{timeout}~\smallskip \\ \mathrm{Certificates}~~~~~&~\varphi \rightsquigarrow \mathid{model},~\mathid{proof},~\mathid{unsat}~\mathid{core}~\smallskip\\ \mathrm{Interpolation}~~~~&~\varphi[\mathid{x},\mathid{y}]~\rightarrow \mathid{I}[\mathid{x}]~\rightarrow \psi[\mathid{x},\mathid{z}]~\smallskip\\ \mathrm{Optimization}~~~~~&~\max \mathid{x}~\mid \varphi \smallskip \\ \mathrm{Consequences}~~~~~&~\varphi \rightarrow \varphi_1~\wedge \ldots \wedge \varphi_\mathid{n}\smallskip\\ \mathrm{Sat\ subsets}~~~~~&~\psi_1~\wedge \psi_2,\ \psi_1~\wedge \psi_3\smallskip\\ \mathrm{Unsat\ cores}~~~~~&~\neg(\psi_1~\wedge \psi_2),\ \neg(\psi_1~\wedge \psi_3)\medskip\\ \mathrm{Model\ counting}~~&~|\{~\mathid{x}~\mid \varphi\}|~\medskip\\ \mathrm{All\ models}~~~~~~&~\mathid{Ideal}(\varphi),~\mathid{M}_1~\models \varphi,~\mathid{M}_2~\models \varphi,~\ldots \medskip\\ \mathrm{Model\ probability}~&~\ldots \end{mdmathpre}%mdk \]

Programming with Cores and Correction sets

  • Optimization as SMT with preferred models

  • An introduction to cores and correction sets

  • Show examples of algorithms on top of SMT/Z3

Cores, Correction Sets, Satisfying Assignments

  • (M)US (Minimal) unsatisfiable subset

    • (minimal) subset of assertions that are unsatisfiable. Also known as a core
  • (M)SS (Maximal) satisfiable subset

    • (maximal) subset of assertions that are satisfiable.
  • (M)CS (Minimal) correction set

    • complement of an (M)SS.
  • (Prime) implicant

    • $m \Rightarrow \phi$ iff $m$ is a core for $\neg \phi$.

A Duality: MUS $\sim$ MCS (Reiter1987)

Reiter

All Cores and Correction Sets (Liffiton et al.2016)


  • Given $\varphi_1, \ldots, \varphi_n$
    • Find all min unsat cores
    • Find all max satisfying subsets
  • Generate subset $S \subseteq \{1,\ldots, n\}$
    • Not a superset of old core
    • Not a subset of old sat subset
  • Is $\bigwedge_{j \in S} \varphi_j$ satisfiable?
    • If yes, find max sat $S' \supseteq S$
    • If no, find min unsat $S' \subseteq S$
  • Block $S'$ from next iteration


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

Cores and Correction sets Algorithm

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

Maximizing Satisfying Assignments (Mencía et al.2015)

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
          

Minimizing Cores (Junker2004; Bradley and Manna2007; Marques-Silva et al.2013; Bacchus and Katsirelos2015)

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})

All maximal satisfying sets (basic)

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
  • Inefficiency: Same unsat cores may be revisited.

All Correction Sets

Find all satisfying subsets among $\varphi_1, \ldots, \varphi_n$:

  • Initialize: $F_1 \leftarrow \varphi_1, \ldots, F_n \leftarrow \varphi_n$, $F \leftarrow \top$.
  • While $F$ is sat:
    • If $F \wedge F_1\wedge \ldots\wedge F_n$ is sat with model $M$
      • $\{ \varphi_j \mid M(\varphi_j) = \top \}$ is mss.
      • $F \leftarrow F \wedge \bigvee \{ \varphi_j \;\mid\; M(\varphi_j) = \bot \}$ add mcs
    • Else suppose $F_1,\ldots, F_k$ is a core
      • Replace by $F'_1, \ldots, F'_{k-1}$:
      • $F'_1 \leftarrow F_1 \vee F_2$, $F'_2 \leftarrow F_3 \vee (F_2 \wedge F_1)$,
        $\ldots$, $F'_{k-1} \leftarrow F_k \vee (F_{k-1} \wedge \ldots)$.

MaxSMT

  • Typical definition: Minimize the number of violated soft assertions.

  • Is built-in, based on MaxSAT algorithms.

MaxSAT example

(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)

MaxSAT flattened

(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)
  • NB. Implementations of MaxSAT typically flatten weights on demand.

MaxSAT flat form

\[ \underbrace{(a \equiv c) \wedge \neg (a \wedge b)}_{F}, \underbrace{a}_{F_1} \underbrace{b}_{F_2} \underbrace{b}_{F_3} \underbrace{c}_{F_4} \underbrace{c}_{F_5} \underbrace{c}_{F_6} \]
  • $F$ - hard constraints

  • $F_1, F_2, \ldots, F_6$ - soft constraints

MaxSAT with Cores (Narodytska and Bacchus2014)

A: $F, \underbrace{F_1, F_2, F_3, F_4 }_{core}, F_5$

A': $F, \ F_2 \vee F_1, F_3 \vee (F_2 \wedge F_1), F_4 \vee (F_3 \wedge (F_2 \wedge F_1)), F_5$

Lemma: for any model $M$ of $F$, $cost(M, A) = 1 + cost(M, A')$

Proof: $M(F_j) = \bot, j$ min: $ M(F'_i) = M(F_{i+1}) \vee i = j + 1, \forall i$

MaxSAT with MCS (N. Bjørner and Narodytska2015)

A: $F, \underbrace{F_1, F_2, F_3, F_4 }_{\mbox{correction set}}, F_5$

A': $\underbrace{ F \wedge (F_1 \vee F_2 \vee F_3 \vee F_4) }_{F`},$
$F_2 \wedge F_1,\ F_3 \wedge (F_2 \vee F_1), \ F_4 \wedge (F_3 \vee (F_2 \vee F_1)), \ F_5$

Lemma: for any model $M$ of $F'$, $cost(M, A) = cost(M, A')$

Proof: $M(F_j) = \top, j$ min:     $ M(F'_i) = M(F_{i+1}) \wedge (i \neq j \vee j = 1), \forall i$

MaxSAT with Cores (python)

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) }

MaxSAT with MCS (python)

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 Narodytska2015) we combine MUS and MCS steps.

All SMT

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)

All SMT example

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]

All SMT - avoiding lemmas by using scopes

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))

All SMT - user propagator (N. S. Bjørner et al.2023)

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])

All SMT - user propagator instance

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

Other Propagation Facilities

 add_eq
 propagate

References

Fahiem Bacchus, and George Katsirelos. “Using Minimal Correction Sets to More Efficiently Compute Minimal   Unsatisfiable Sets.” In Computer Aided Verification - 27th International Conference, CAV   2015, San Francisco, CA, USA, July 18-24, 2015, Proceedings, Part II, 70–86. 2015. doi:10.1007/978-3-319-21668-3_5🔎
Nikolaj Bjørner, and Nina Narodytska. “Maximum Satisfiability Using Cores and Correction Sets.” In Proceedings of the Twenty-Fourth International Joint Conference on   Artificial Intelligence, IJCAI 2015, Buenos Aires, Argentina, July 25-31, 2015, 246–252. 2015. http://​ijcai.​org/​Abstract/​15/​041🔎
Nikolaj S. Bjørner, Clemens Eisenhofer, and Laura Kovács. “Satisfiability Modulo Custom Theories in Z3.” In Verification, Model Checking, and Abstract Interpretation - 24th International   Conference, VMCAI 2023, Boston, MA, USA, January 16-17, 2023, Proceedings, edited by Cezara Dragoi, Michael Emmi, and Jingbo Wang, 13881:91–105. Lecture Notes in Computer Science. Springer. 2023. doi:10.1007/978-3-031-24950-1_5🔎
Aaron R. Bradley, and Zohar Manna. “Checking Safety by Inductive Generalization of Counterexamples to   Induction.” In Formal Methods in Computer-Aided Design, 7th International Conference,   FMCAD 2007, Austin, Texas, USA, November 11-14, 2007, Proceedings, 173–180. 2007. doi:10.1109/FAMCAD.2007.15🔎
Ulrich Junker. “QUICKXPLAIN: Preferred Explanations and Relaxations for Over-Constrained   Problems.” In Proceedings of the Nineteenth National Conference on Artificial Intelligence,   Sixteenth Conference on Innovative Applications of Artificial Intelligence, July 25-29, 2004, San Jose, California, USA, 167–172. 2004. http://​www.​aaai.​org/​Library/​AAAI/​2004/​aaai04-​027.​php🔎
Mark H Liffiton, Alessandro Previti, Ammar Malik, and Joao Marques-Silva. “Fast, Flexible MUS Enumeration.” Constraints 21 (2). Springer US: 223–250. 2016. 🔎
João Marques-Silva, Mikolás Janota, and Anton Belov. “Minimal Sets over Monotone Predicates in Boolean Formulae.” In Computer Aided Verification - 25th International Conference, CAV   2013, Saint Petersburg, Russia, July 13-19, 2013. Proceedings, 592–607. 2013. doi:10.1007/978-3-642-39799-8_39🔎
Carlos Mencía, Alessandro Previti, and João Marques-Silva. “Literal-Based MCS Extraction.” In Proceedings of the Twenty-Fourth International Joint Conference on   Artificial Intelligence, IJCAI 2015, Buenos Aires, Argentina, July 25-31, 2015, 1973–1979. 2015. http://​ijcai.​org/​Abstract/​15/​280🔎
Nina Narodytska, and Fahiem Bacchus. “Maximum Satisfiability Using Core-Guided MaxSAT Resolution.” In AAAI 2014, 2717–2723. 2014. 🔎
Raymond Reiter. “A Theory of Diagnosis from First Principles.” Artif. Intell. 32 (1): 57–95. 1987. doi:10.1016/0004-3702(87)90062-2🔎