Why is E-matching for conjunctions sensitive to order / case-splitting strategy? - z3

Given the following simplified quantifiers, with the Z3 options set according to those generated by Boogie (full details below), I get "unknown" as a result:
(declare-fun F (Int) Bool)
(declare-fun G (Int) Bool)
(assert (forall ((x Int)) (! (and
(F x) (G x))
:pattern ((F x))
)))
(assert (not (forall ((x Int)) (! (and
(G x) (F x))
:pattern ((F x))
))))
(check-sat)
My understanding for what (I think) Z3 would do with this problem, is skolemise the existential (not forall), which would yield ground instances of both F and G. Given these in the e-graph, we should be able to instantiate the other quantifier, and get unsat. I can see that Z3 probably has to case-split to do this, but I would expect this case-splitting to take place after removing the quantifier and populating the e-graph.
Instead, the first quantifier doesn't get instantiated in the above problem. I've made a number of observations:
Swapping the order of the (F x) and (G x) terms in the first quantifier results in "unsat" without any quantifier instantiations (I suppose some simplification spots the similarity between the two quantified assertions?).
Swapping the order of the (G x) and (F x) terms in the second quantifier (as well as those in the first) results in "unsat" with a single quantifier instantiation (which is the behaviour I'd expect in general).
Changing the smt.case_split option affects the behaviour. Set to 3 (as chosen by Boogie) or 5, we get "unknown". Set to 0,1,2 or 4, I get "unsat".
It would be great to understand the scenarios above, and why (in the failing cases) these terms don't always make it to the e-graph after skolemisation. I'm not sure what the effects of changing the case_split option are in general. At the moment, I don't think Boogie allows that to be changed (and overrides any choice made on the command-line). But I have the feeling that the e-graph should get the information in all cases, ideally.
Here's the full file (removing most of the options set doesn't seem to make a difference to the failing cases, except for the smt.case_split one):
(set-option :print-success false)
(set-info :smt-lib-version 2.0)
(set-option :AUTO_CONFIG 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.DELAY_UNITS true)
(set-option :NNF.SK_HACK true)
(set-option :smt.MBQI false)
(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 :TIMEOUT 0)
(set-option :smt.QI.PROFILE true)
(set-option :smt.CASE_SPLIT 3)
; done setting options
(declare-fun F (Int) Bool)
(declare-fun G (Int) Bool)
(assert (forall ((x Int)) (! (and
(F x) (G x))
:pattern ((F x))
)))
(assert (not (forall ((x Int)) (! (and
(G x) (F x))
:pattern ((F x))
))))
(check-sat)

This is settled by the answer from https://stackoverflow.com/users/1096362/nikolaj-bjorner to this question:
Surprising behaviour when trying to prove a forall
The translation of the proof obligation into a disjunction, followed by corresponding relevancy of the clauses, explains why Z3 doesn't see both conjuncts as potential triggers.

Related

Why does Z3 return Unknown for these horn clauses

I am using Z3 to solve my horn clauses. In the body of Horn clauses uninterpreted predicates should be positive. However, I need negation of some of uninterpreted predicates.
I have seen some examples in which negation works fine. For instance Z3 would return sat for the following example:
(set-logic HORN)
(declare-fun inv (Int) Bool)
(assert (inv 0))
(assert (forall ((k Int)) (or (> k 10) (not (inv k)) (inv (+ k 1)))))
(check-sat)
But my example looks like the following for which Z3 returns unknown.
(set-logic HORN)
(declare-fun inv (Int ) Bool)
(declare-fun s ( Int ) Bool)
(assert (forall ((k Int) (pc Int))(=>(and (= pc 1)(= k 0)) (inv k ))))
(assert (forall ((k Int)(k_p Int)(pc Int)(pc_p Int))
(=>(and (inv k )(= pc 1)(= pc_p 2)(= k_p (+ k 1))(not (s pc ))(s pc_p ))
(inv k_p ))))
(check-sat)
I wonder if there is a way to rewrite my clauses to Horn clause fragment of Z3.
Your clauses are not in the Horn fragment because the predicate s is used with both polarities in the last assertion. So there are two occurrences of a predicate with positive polarity (both (s pc) and (inv k_p) are positive polarity).
A basic method to avoid polarity issues is to introduce an extra argument to s of type Bool. Consequently, you would also have to say what is the specification of s using Horn clauses so it all makes sense. The typical scenario is that s encodes the behavior of a recursive procedure and the extra Boolean argument to s would be the return value of the procedure s. Of course this encoding doesn't ensure that s is total or functional.
There is a second approach, which is to add an extra argument to "inv", where you let 's' be an array. Then the occurrences (not (s pc)) becomes (not (select s pc)), etc.
It all depends on the intent of your encoding for what makes sense.

Interaction between quantifiers and sets in z3

I am currently trying to use Z3 to encode a simple program logic for an untyped language with sets.
My symbolic execution engine needs to prove the validity of the following formula:
To this end, we ask Z3 to check the satisfiability of:
which we then encode as the following SMT-LIB formula:
(define-sort Set () (Array Real Bool))
(define-fun singleton ((x Real)) Set
(store
((as const (Array Real Bool)) false)
x
true))
(define-fun set-union ((x Set) (y Set)) Set
((_ map (or (Bool Bool) Bool)) x y))
(declare-const head Real)
(declare-const tail Set)
(declare-const result Set)
(declare-const value Real)
(assert (forall ((x Real)) (=> (select tail x) (> x head))))
(assert (> head value))
(assert
(forall ((result Set))
(let ((phi1
(forall ((x Real)) (=> (select result x) (> x value))))
(phi2
(= result (union (singleton head) tail))))
(not (and phi1 phi2)))))
(check-sat)
When given this formula, the solver immediately outputs unknown.
My guess is that the problem lies on quantifying over a variable that is bound to a set.
To check this, I simplified the formula above, obtaining:
which we then encode as the following SMT-LIB formula:
(define-sort Set () (Array Real Bool))
(define-fun singleton ((x Real)) Set
(store
((as const (Array Real Bool)) false)
x
true))
(define-fun set-union ((x Set) (y Set)) Set
((_ map (or (Bool Bool) Bool)) x y))
(declare-const head Real)
(declare-const tail Set)
(declare-const result Set)
(declare-const value Real)
(assert (forall ((x Real))(=> (select tail x) (> x head))))
(assert (> head value))
(assert
(not
(forall ((x Real))
(=> (select (union (singleton head) tail) x)
(not (<= x value))))))
(check-sat)
When given this formula, the solver immediately outputs
unsat.
This confirms my guess that the problem lies on the quantification
over a variable that is bound to a set.
My question is whether or not Z3 supports formulae that include
quantification over sets. And, if so, what am I doing wrong?
Quantifier reasoning is always hard for SMT solvers, and in this case you have nested quantifiers. I'm not surprised to hear Z3 simply said Unknown in the first case. Also note that you are quantifying over what's essentially a function (Sets as you implemented are really functions), which makes it even more difficult. But even if you quantified over simpler things, nested quantifiers are never going to be easy to discharge.
Did you try skolemizing your formula, putting it into prenex-normal form, and getting rid of the existentials? That might get you a bit further though you might have to come up with appropriate patterns for instantiation.

Why is this simple Z3 proof so slow?

The following Z3 code times out on the online repl:
; I want a function
(declare-fun f (Int) Int)
; I want it to be linear
(assert (forall ((a Int) (b Int)) (
= (+ (f a) (f b)) (f (+ a b))
)))
; I want f(2) == 4
(assert (= (f 2) 4))
; TIMEOUT :(
(check-sat)
So does this version, where it is looking for a function on the reals:
(declare-fun f (Real) Real)
(assert (forall ((a Real) (b Real)) (
= (+ (f a) (f b)) (f (+ a b))
)))
(assert (= (f 2) 4))
(check-sat)
It's faster when I give it a contradiction:
(declare-fun f (Real) Real)
(assert (forall ((a Real) (b Real)) (
= (+ (f a) (f b)) (f (+ a b))
)))
(assert (= (f 2) 4))
(assert (= (f 4) 7))
(check-sat)
I'm quite unknowledgeable about theorem provers. What is so slow here? Is the prover just having lots of trouble proving that linear functions with f(2) = 4 exist?
The slowness is most likely due to too many quantifier instantiations, caused by problematic patterns/triggers. If you don't know about these yet, have a look at the corresponding section of the Z3 guide.
Bottom line: patterns are a syntactic heuristic, indicating to the SMT solver when to instantiate the quantifier. Patterns must cover all quantified variables and interpreted functions such as addition (+) are not allowed in patterns. A matching loop is a situation in which every quantifier instantiation gives rise to further quantifier instantiations.
In your case, Z3 probably picks the pattern set :pattern ((f a) (f b)) (since you don't explicitly provide patterns). This suggests Z3 to instantiate the quantifier for every a, b for which the ground terms (f a) and (f b) have already occurred in the current proof search. Initially, the proof search contains (f 2); hence, the quantifier can be instantiated with a, b bound to 2, 2. This yields (f (+ 2 2)), which can be used to instantiate the quantifier once more (and also in combination with (f 2)). Z3 is thus stuck in a matching loop.
Here is a snippet arguing my point:
(set-option :smt.qi.profile true)
(declare-fun f (Int) Int)
(declare-fun T (Int Int) Bool) ; A dummy trigger function
(assert (forall ((a Int) (b Int)) (!
(= (+ (f a) (f b)) (f (+ a b)))
:pattern ((f a) (f b))
; :pattern ((T a b))
)))
(assert (= (f 2) 4))
(set-option :timeout 5000) ; 5s is enough
(check-sat)
(get-info :reason-unknown)
(get-info :all-statistics)
With the explicitly provided pattern you'll get your original behaviour (modulo the specified timeout). Moreover, the statistics report lots of instantiations of the quantifier (and more still if you increase the timeout).
If you comment the first pattern and uncomment the second, i.e. if you "guard" the quantifier with a dummy trigger that won't show up in the proof search, then Z3 terminates immediately. Z3 will still report unknown, though, because it "knowns" that it did not account for the quantified constraint (which would be a requirement for sat; and it also cannot show unsat).
It is sometimes possible to rewrite quantifiers in order to have better triggering behaviour. The Z3 guide, for example, illustrates that in the context of injective functions/inverse functions. Maybe you'll be able to perform a similar transformation here.

Minor change results in "unknown" - related to quantifier preprocessing?

The following "minimal" program, distilled from a much larger program, is expected to yield unsat (and does). However, uncommenting the additional conjunct in the quantifier AX-1 changes the result to unknown (in Z3 4.5.0 x64 on Windows 10).
(set-option :auto_config false)
(set-option :smt.mbqi false)
(declare-fun foo (Int) Bool)
(declare-const k Real)
(assert (forall ((i Int)) (!
(and
(< 0.0 k)
; (implies (<= 0 i) (< 0.0 k)) ;;; ---- uncomment this line ----
)
:pattern ((foo i))
:qid |AX-1|)))
(assert (forall ((i Int)) (!
(foo i)
:pattern ((foo i))
:qid |AX-2|)))
(declare-const j Int)
(assert (< j 0))
; (push) ;;; doesn't make a difference
(assert (not
(ite
(foo j)
(< 0.0 k)
false)))
; (set-option :smt.qi.profile true)
(check-sat)
; (get-info :all-statistics)
; (pop)
The quantifier instantiation statistics show that AX-2 is instantiated in both cases, but AX-1 is only instantiated if the additional conjunct is not included. My assumption is that in the latter case, Z3 eliminates the quantifier since the quantified variable doesn't occur in the body.
However, I find it surprising that the version with the additional conjunct - in which Z3 presumably doesn't eliminate the quantifier - yields unknown, since the trigger for the quantifier ((foo j)) should be available.
Question: Is this behaviour expected - and if so, why?
Confirmed as a bug, see Github issue 935.

Executing get-model or unsat-core depending on solver's decision

I wonder, if there is a possibility in a SMT-LIB 2.0 script to access the last satisfiability decision of a solver (sat, unsat, ...). For example, the following code:
(set-option :produce-unsat-cores true)
(set-option :produce-models true)
(set-logic QF_UF)
(declare-fun p () Bool)
(declare-fun q () Bool)
(declare-fun r () Bool)
(assert (! (=> p q) :named PQ))
(assert (! (=> q r) :named QR))
(assert (! (not (=> p r)) :named nPR))
(check-sat)
(get-model)
(get-unsat-core)
ran in Z3 returns:
unsat
(error "line 15 column 10: model is not available")
(PQ QR nPR)
and ran in MathSAT returns:
unsat
(error "model generation not enabled")
In MathSAT 5 it just breaks on (get-model) and doesn't even reach (get-unsat-core).
Is there any way in SMT-LIB 2.0 language to get-model iff the decision was SAT and unsat-core iff the decision was UNSAT? Solution could for example look like this:
(check-sat)
(ite (= (was-sat) true) (get-model) (get-unsat-core))
I searched SMT-LIB 2.0 language documentation, but I did not found any hint.
EDIT:
I also tried the code below, and unfortunately it did not work.
(ite (= (check-sat) "sat") (get-model) (get-unsat-core))
The SMT language does not let you write commands like this.
The way that tools, such as Boogie, deal with this is to use
a two-way text pipe: It reads back the result from (check-sat).
If the resulting string is "unsat" models are not available, but
cores would be if the check uses asssumptions. If the resulting
string is "sat" the tool can expect that a (get-model) command
succeeds.
As Nikolaj said in his answer, the right way to do this is to parse the solver output and conditionally generate either a (get-model) or a (get-unsat-core) statement.
However, with mathsat you can use the code without the (get-model) statement, and call mathsat with the -model option. For example:
$ cat demo_sat.smt2
(set-option :produce-unsat-cores true)
(set-option :produce-models true)
(set-logic QF_UF)
(declare-fun p () Bool)
(declare-fun q () Bool)
(declare-fun r () Bool)
(assert (! (=> p q) :named PQ))
(assert (! (=> q r) :named QR))
; (assert (! (not (=> p r)) :named nPR))
(check-sat)
(get-unsat-core)
$ mathsat -model demo_sat.smt2
sat
( (p false)
(q false)
(r false) )
(error "no unsatisfiability proof, impossible to compute unsat core")
And in the unsat case:
$ cat demo_unsat.smt2
(set-option :produce-unsat-cores true)
(set-option :produce-models true)
(set-logic QF_UF)
(declare-fun p () Bool)
(declare-fun q () Bool)
(declare-fun r () Bool)
(assert (! (=> p q) :named PQ))
(assert (! (=> q r) :named QR))
(assert (! (not (=> p r)) :named nPR))
(check-sat)
(get-unsat-core)
$ mathsat -model demo_unsat.smt2
unsat
( PQ
QR
nPR )
Unfortunately there does not seem to exist an option like -model for producing unsat cores. So this hack won't work if you want to use it with an incremental problem, unless you are OK with the solver terminating after the first sat result. (Because at the first sat result the solver will exit on the error for (get-unsat-core).)

Resources