3

How does forall statement work in SMT? I could not find information about usage. Can you please simply explain this? There is an example from
https://rise4fun.com/Z3/Po5.

(declare-fun f (Int) Int)
(declare-fun g (Int) Int)
(declare-const a Int)
(declare-const b Int)
(declare-const c Int)
(assert (forall ((x Int))
                (! (= (f (g x)) x)
                   :pattern ((g x)))))
(assert (= (g a) c))
(assert (= (g b) c))
(assert (not (= a b)))
(check-sat)
bc_msa
  • 31
  • 6

2 Answers2

2

For general information on quantifiers (and everything else SMTLib) see the official SMTLib document:

http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf

Quoting from Section 3.6.1:

Exists and forall quantifiers. These binders correspond to the usual universal and existential quantifiers of first-order logic, except that each variable they quantify is also associated with a sort. Both binders have a non-empty list of variables, which abbreviates a sequential nesting of quantifiers. Specifically, a formula of the form (forall ((x1 σ1) (x2 σ2) · · · (xn σn)) ϕ) (3.1) has the same semantics as the formula (forall ((x1 σ1)) (forall ((x2 σ2)) (· · · (forall ((xn σn)) ϕ) · · · ) (3.2) Note that the variables in the list ((x1 σ1) (x2 σ2) · · · (xn σn)) of (3.1) are not required to be pairwise disjoint. However, because of the nested quantifier semantics, earlier occurrences of same variable in the list are shadowed by the last occurrence—making those earlier occurrences useless. The same argument applies to the exists binder.

If you have a quantified assertion, that means the solver has to find a satisfying instance that makes that formula true. For a forall quantifier, this means it has to find a model that the assertion is true for all assignments to the quantified variables of the relevant sorts. And likewise, for exists the model needs to be able to exhibit a particular value satisfying the assertion.

Top-level exists quantifiers are usually left-out in SMTLib: By skolemization, declaring a top-level variable fills that need, and also has the advantage of showing up automatically in models. (That is, any top-level declared variable is automatically existentially quantified.)

Using forall will typically make the logic semi-decidable. So, you're likely to get unknown as an answer if you use quantifiers, unless some heuristic can find a satisfying assignment. Similarly, while the syntax allows for nested quantifiers, most solvers will have very hard time dealing with them. Patterns can help, but they remain hard-to-use to this day. To sum up: If you use quantifiers, then SMT solvers are no longer decision-procedures: They may or may not terminate.

If you are using the Python interface for z3, also take a look at: https://ericpony.github.io/z3py-tutorial/advanced-examples.htm. It does contain some quantification examples that can clarify things for you. (Even if you don't use the Python interface, I heartily recommend going over that page to see what the capabilities are. They more or less translate to SMTLib directly.)

Hope that gets you started. Stack-overflow works the best if you ask specific questions, so feel free to ask clarification on actual code as you need.

alias
  • 28,120
  • 2
  • 23
  • 40
  • Thank you for your help. I have read the documentation provided here and tried to understand logic behind, some were too theoretical for me to understand. I do not use the Python interface actually I was trying to implement 'for loop' with some prior intuition I thought 'forall' and usage of pattern could be a way to implement it. Am I wrong? What is the way of implementing for loop? – bc_msa Jun 10 '20 at 22:32
  • @bc_msa How to encode the effect of loops has been discussed before: e.g. https://stackoverflow.com/questions/48089983 and https://stackoverflow.com/questions/42650978. Encoding loops manually is a bit of work and significantly simplified if done for you by an intermediate verification language such as https://viper.ethz.ch/, https://github.com/dafny-lang/dafny or https://github.com/boogie-org/boogie/. – Malte Schwerhoff Jun 11 '20 at 08:36
  • Also see https://stackoverflow.com/questions/53702290/translating-loop-semantics-to-smt-lib for my take on this comment. – alias Jun 11 '20 at 16:02
1

Semantically, a quantifier forall x: T . e(x) is equivalent to e(x_1) && e(x_2) && ..., where the x_i are all the values of type T. If T has infinitely many (or statically unknown many) values, then it is intuitively clear that an SMT solver cannot simply turn a quantifier into the equivalent conjunction.

The classical approach in this case are patterns (also called triggers), pioneered by Simplify and available in Z3 and others. The idea is rather simple: users annotate a quantifier with a syntactical pattern that serves a heuristic for when (and how) to instantiate the quantifier.

Here is an example (in pseudo-code):

assume forall x :: {foo(x)} foo(x) ==> false

Here, {foo(x)} is the pattern, indicating to the SMT solver that the quantifier should be instantiated whenever the solver gets a ground term foo(something). For example:

assume forall x :: {foo(x)} foo(x) ==> 0 < x
assume foo(y)
assert 0 < y

Since the ground term foo(y) matches the trigger foo(x) when the quantified variable x is instantiated with y, the solver will instantiate the quantifier accordingly and learn 0 < y.

Patterns and quantfier triggering is difficult, though. Consider this example:

assume forall x :: {foo(x)} (foo(x) || bar(x)) ==> 0 < y
assume bar(y)
assert 0 < y

Here, the quantifier won't be instantiated because the ground term bar(y) does not match the chosen pattern.

The previous example shows that patterns can cause incompletenesses. However, they can also cause termination problems. Consider this example:

assume forall x :: {f(x)} !f(x) || f(f(x))
assert f(y)

The pattern now admits a matching loop, which can cause nontermination. Ground term f(y) allows to instantiate the quantifier, which yields the ground term f(f(y)). Unfortunately, f(f(y)) matches the trigger (instantiate x with f(y)), which yields f(f(f(y))) ...

Patterns are dreaded by many and indeed tricky to get right. On the other hand, working out a triggering strategy (given a set of quantifiers, find patterns that allow the right instantiations, but ideally not more than these) ultimately "only" required logical reasoning and discipline.

Good starting points are: * https://rise4fun.com/Z3/tutorial/, section "Quantifiers" * http://moskal.me/smt/e-matching.pdf * https://dl.acm.org/citation.cfm?id=1670416 * http://viper.ethz.ch/tutorial/, section "Quantifiers"

Z3 also has offers Model-based Quantifier Instantiation (MBQI), an approach to quantifiers that doesn't use patterns. As far as I know, it is unfortunately also much less well documented, but the Z3 tutorial has a short section on MBQI as well.

Malte Schwerhoff
  • 12,684
  • 4
  • 41
  • 71