Ground terms inside quantifiers seem not to be used for e-matching (until quantifier is instantiated once) - z3

When choosing instantiations for quantified assumptions (without mbqi), Z3 searches its e-graph of ground terms (modulo equalities) for possible matches. I understood that this e-graph consists of the ground terms present in the original problem, plus those which are formed during the proof search. However, it seems that a ground term mentioned under a quantifier is not used as a candidate for matching, until the surrounding quantifier itself is instantiated.
For example, if I write:
(assert
(forall ((n Int) ) (!
(and (f 0) (g n))
:pattern ((g n))
)))
(assert (forall ((n Int) ) (!
(not (= (h n) n))
:pattern ((f n))
)))
(assert (= (h 0) 0))
(check-sat)
Then Z3 reports unknown. Whereas if I pull the conjunction of (f 0) out of the forall (as in the full example below) then Z3 reports unsat (and if I track statistics, I can see that the second quantifier is instantiated.
I find this behaviour a bit confusing, and I wondered whether or not it is intended? If so, what is the rationale behind this choice? I think it could be affecting some of our axiomatisations (but need to debug the details further).
By the way, we tried using a let expression to pull the ground term out of the quantifier, but (probably because let expressions are just substituted back in again?) we didn't see a difference.
The full version of the example is here:
(set-option :print-success true) ; Boogie: false
(set-option :global-decls true) ; Boogie: default
(set-option :auto_config false) ; Usually a good idea
(set-option :smt.mbqi false)
(set-option :model.v2 true)
(set-option :smt.phase_selection 0)
(set-option :smt.restart_strategy 0)
(set-option :smt.restart_factor |1.5|)
(set-option :smt.arith.random_initial_value true)
(set-option :smt.case_split 5)
(set-option :smt.delay_units true)
(set-option :smt.delay_units_threshold 16)
(set-option :nnf.sk_hack true)
(set-option :smt.qi.eager_threshold 100)
(set-option :smt.qi.cost "(+ weight generation)")
(set-option :type_check true)
(set-option :smt.bv.reflect true)
(set-option :smt.qi.profile true)
(declare-fun f (Int) Bool)
(declare-fun g (Int) Bool)
(declare-fun h (Int) Int)
(assert
(forall ((n Int) ) (!
(and (f 0) (g n))
:pattern ((g n))
)))
; this version of the above axiom lets us get "unsat"
;(assert
; (and (f 0) (forall ((n Int) ) (!
; (g n)
; :pattern ((g n)))
; )))
(assert (forall ((n Int) ) (!
(not (= (h n) n))
:pattern ((f n))
)))
(assert (= (h 0) 0))
(check-sat)
(get-info :all-statistics)

FWIW, given what I know about Z3's quantifier instantiation mechanisms these days, it's pretty clear that this behaviour is expected; ground terms that happen to be under a quantifier won't make it into the E-graph until the quantifier is instantiated.

Related

Unexpected unknown with ill-defined function definition axiom

The SMT program further down encodes the (ill-defined) function definition ∀ s · wild(s) = 1 + wild(s) in a slightly roundabout way (applying Dafny's "limited functions" encoding of recursive functions) and then attempts to prove that wild(emp) = 1 + wild(emp). However, Z3 4.6.0 (and also a recent 4.7.0 nightly) unexpectedly yield unknown instead of unsat.
(set-option :auto_config false) ;; true -> no change in behaviour
(set-option :smt.mbqi false) ;; true -> no change in behaviour
(declare-sort Snap) ;; In the full example, this is ...
(declare-const emp Snap) ;; ... declared using declare-datatypes
(declare-fun wild (Snap) Int)
(declare-fun wild%limited (Snap) Int)
(assert (forall ((s Snap)) (! ;; AX-1
(= (wild%limited s) (wild s))
:pattern ((wild s))
)))
(assert (forall ((s Snap)) (! ;; AX-2
(=
(wild s)
(+ 1 (wild%limited emp)))
:pattern ((wild s))
)))
(push) ;; Full examples uses incremental mode
(assert
(not
(=
(wild emp)
(+ 1 (wild emp)))))
(check-sat) ;; UNKNOWN --- but why?
(pop)
Given my understanding of Z3 and triggers, I would expect the following proof steps to happen:
¬(wild(emp) = 1 + wild(emp)) // Source assertion
≡ ¬(1 + wild%limited(emp) = 1 + wild(emp)) // By AX-2
≡ ¬(1 + wild%limited(emp) = 1 + wild%limited(emp)) // By AX-1
≡ ¬(true) // Done: UNSAT
But that doesn't appear to happen. My guess is that the axioms aren't instantiated — and indeed, get-info :all-statistics reports no quantified instantiation.
Can anybody shed some light on this?
the last assert simplifies to "true", so there is no ground occurrence of (wild emp) that would trigger quantifier instantiation.

Converting Z3 QBF formula directly to pcnf

I'm using Z3 theorem prover (using Z3Py: the Z3 API in Python) to create QBF (Quantified Boolean formula).
Is there any way in Z3 to directly convert your qbf formula into Prenex normal form ?
I don't think there's a tactic to convert to Prenex, but you can surely apply the quantifier-elimination tactic and further process your formulas. Note that the transformed formulas will not really look like the originals, as they are mechanically generated.
Here's an example:
from z3 import *
f = Function('f', IntSort(), IntSort(), IntSort())
x, y = Ints('x y')
p = ForAll(x, Or(x == 2, Exists(y, f (x, y) == 0)))
print Tactic('qe')(p)
Here qe is the quantifier elimination tactic. This produces:
[[Not(Exists(x!0,
Not(Or(x!0 == 2,
Exists(x!1,
And(f(x!0, x!1) <= 0,
f(x!0, x!1) >= 0))))))]]
For a nice tutorial on tactics, see here: http://ericpony.github.io/z3py-tutorial/strategies-examples.htm
You could use the skolemize tactic (snf) which will by definition be in prenex form. However it will also eliminate existential quantifiers which is not what you want. Here's an example.
(declare-fun a (Int) Bool)
(declare-fun b (Int) Bool)
(declare-fun c (Int) Bool)
(assert
(forall ((x Int))
(or
(exists ((y Int))
(a y)
)
(exists ((z Int))
(=>
(b z)
(c x)
)
)
)
)
)
(apply
(and-then
; mode (symbol) NNF translation mode: skolem (skolem normal form), quantifiers (skolem normal form + quantifiers in NNF), full (default: skolem)
(using-params snf :mode skolem)
)
:print_benchmark true
:print false
)
When Z3 is given the above it will responds with something like
(declare-fun c (Int) Bool)
(declare-fun b (Int) Bool)
(declare-fun a (Int) Bool)
(assert (forall ((x Int))
(or (exists ((y Int)) (a y)) (exists ((z Int)) (=> (b z) (c x))))))
(check-sat)
You can see the available tactics by running
echo "(help-tactic)" | ./z3 -in | less
from a bash shell.
Unfortunately I can't see one that states it does conversion to prenex.

Can z3 always give result when handling nonlinear real arithmetic

I have a problem required to solve a set of nonlinear polynomial constraints. Can z3 always give a result (sat or unsat) when handling nonlinear real arithmetic.
Is the result also sound?
Yes, it is complete assuming (1) availability of resources, and (2) you only use real constraints so that the nlsat tactic is used, as the last I checked, it wasn't full integrated with the other solvers, see the below questions/answers for more details. Here's a simple example illustrating this (at least by default, rise4fun link: http://rise4fun.com/Z3/SRZ8 ):
(declare-fun x () Real)
(declare-fun y () Real)
(declare-fun z () Real)
(assert (>= (* 2 (^ x 2)) (* y z)))
(assert (> x 100))
(assert (< y 0))
(assert (< z 0))
(assert (> (^ y 2) 1234))
(assert (< (^ z 3) -25))
(check-sat) ; sat
(get-model)
(declare-fun b () Int)
(assert (> b x))
(check-sat) ; unknown
Z3 Theorem Prover: Pythagorean Theorem (Non-Linear Artithmetic)
mixing reals and bit-vectors
z3 produces unknown for assertions without quantifiers
z3 existential theory of the reals
Combining nonlinear Real with linear Int
Z3 support for nonlinear arithmetic
Encoding returns "unknown"
For the incremental question, it may be possible to use nlsat with incremental solving, but in this simple example applying a standard method (rise4fun link: http://rise4fun.com/Z3/Ce1F and see: Soft/Hard constraints in Z3 ) there is an unknown, although a model assignment is made, so it may be useful for your purposes. If not, you can try push/pop: Incremental solving in Z3 using push command
(set-option :produce-unsat-cores true)
(set-option :produce-models true)
(declare-const p1 Bool)
(declare-const p2 Bool)
(declare-const p3 Bool)
(declare-const p4 Bool)
(declare-const p5 Bool)
(declare-const p6 Bool)
(declare-const p7 Bool)
(declare-fun x () Real)
(declare-fun y () Real)
(declare-fun z () Real)
(assert (=> p1 (>= (* 2 (^ x 2)) (* y z))))
(assert (=> p2 (> x 100)))
(assert (=> p3 (< y 0)))
(assert (=> p4 (< z 0)))
(assert (=> p5 (> (^ y 2) 1234)))
(assert (=> p6 (< (^ z 3) -25)))
(assert (=> p7 (< x 50)))
(check-sat p1 p2 p3 p4 p5 p6 p7) ; unsat
(get-unsat-core) ; (p2 p7)
(check-sat p1 p2 p3 p4 p5 p6) ; unknown, removed one of the unsat core clauses
(get-model)
(declare-fun b () Int)
(assert (> b x))
(check-sat) ; unknown

Program satisfiability using Z3

How to check satisfiability of a program using Z3? For example:
Boolean x, y
while(x is False) {
x = x or y
y = x & y
}
y = x or y
You can represent programs as a set of Horn clauses. For your program you can represent it as follows:
(set-logic HORN)
(set-option :fixedpoint.engine bmc)
(declare-fun L0 (Bool Bool) Bool)
(declare-fun L1 (Bool Bool) Bool)
(declare-fun L2 (Bool Bool) Bool)
(declare-fun L3 (Bool Bool) Bool)
(assert (forall ((x Bool) (y Bool)) (L0 x y))) ; all values of x,y are possible at L0
(assert (forall ((x Bool) (y Bool)) (=> (L0 x y) (L1 x y)))) ; from L0 move to L1 without changing x, y
(assert (forall ((x Bool) (y Bool) (x1 Bool) (y1 Bool))
(=> (and (not x) (L1 x y) (= x1 (or x y)) (= y1 (and x1 y))) (L1 x1 y1)))); assignment in while loop
(assert (forall ((x Bool) (y Bool)) (=> (and x (L1 x y)) (L2 x y)))) ; exit while loop
(assert (forall ((x Bool) (y Bool)) (=> (L2 x y) (L3 x (or x y))))) ; assignment after while loop
(assert (forall ((x Bool) (y Bool)) (=> (L3 true true) false))) ; say x = true, y = true is unreachable.
(check-sat)
I have added a last assertion to make a reachability statement.
I have also instructed Z3 to use a BMC engine to unfold the Horn clauses using
bounded model checking. Other engines are available as well, for the example the PDR/IC3
engine (set-option :fixedpoint.engine pdr), does not unfold transition relations.
Now the meaning of reachability vs. satisfiability
is going to be different for Horn clauses, as compared to a conjunction of the unfolded
transition relation:
the above clauses are UNSAT.
This does in fact correspond to a feasible path from L0 to (L3 true true).
If you change the last statement to (L3 true false), you get the answer "sat"
(The BMC problem is UNSAT). While BMC itself would not terminate on with such a loop,
it turns out that the last transition and loop exit condition are enough to prune out
the possibility of (L3 true false) so Z3 solves this problem by pre-processing the horn clauses.
You can of course also write down the transition relation for the program statements you have and unfold this directly yourself into a logical formula that you check satisfiability of.

Can I use declare-const to eliminate the forall universal quantifier?

I have some confusion of using universal quantifier and declare-const without using forall
(set-option :mbqi true)
(declare-fun f (Int Int) Int)
(declare-const a Int)
(declare-const b Int)
(assert (forall ((x Int)) (>= (f x x) (+ x a))))
I can write like this:
(declare-const x Int)
(assert (>= (f x x) (+ x a))))
with Z3 will explore all the possible values of type Int in this two cases. So what's the difference?
Can I really use the declare-const to eliminate the forall quantifier?
No, the statements are different. Constants in Z3 are nullary (0 arity) functions, so (declare-const a Int) is just syntactic sugar for (declare-fun a () Int), so these two statements are identical. Your second statement (assert (>= (f x x) (+ x a)))) implicitly asserts existence of x, instead of for all x as in your first statement (assert (forall ((x Int)) (>= (f x x) (+ x a)))). To be clear, note that in your second statement, only a single assignment for x needs to satisfy the assertion, not all possible assignments (also note the difference in the function f, and see this Z3#rise script: http://rise4fun.com/Z3/4cif ).
Here's the text of that script:
(set-option :mbqi true)
(declare-fun f (Int Int) Int)
(declare-const a Int)
(declare-fun af () Int)
(declare-const b Int)
(declare-fun bf () Int)
(push)
(declare-const x Int)
(assert (>= (f x x) (+ x a)))
(check-sat) ; note the explicit model value for x: this only checks a single value of x, not all of them
(get-model)
(pop)
(push)
(assert (forall ((x Int)) (>= (f x x) (+ x a))))
(check-sat)
(get-model) ; no model for x since any model must satisfy assertion
(pop)
Also, here's an example from the Z3 SMT guide ( http://rise4fun.com/z3/tutorial/guide from under the section "Uninterpreted functions and constants"):
(declare-fun f (Int) Int)
(declare-fun a () Int) ; a is a constant
(declare-const b Int) ; syntax sugar for (declare-fun b () Int)
(assert (> a 20))
(assert (> b a))
(assert (= (f 10) 1))
(check-sat)
(get-model)
You can eliminate a top-level exists with a declare-const. Maybe this is the source of your confusion? The following two are equivalent:
(assert (exists ((x Int)) (> x 0)))
(check-sat)
and
(declare-fun x () Int)
(assert (> x 0))
(check-sat)
Note that this only applies to top-level existential quantifiers. If you have nested quantification of both universals (forall) and existentials (exists), then you can do skolemization to float the existentials to the top level. This process is more involved but rather straightforward from a logical point of view.
There is no general way of floating universal quantifiers to the top-level in this way, at least not in classical logic as embodied by SMT-Lib.

Resources