Possible Z3 bug with pattens/triggers - z3

I tried running Z3 on the following smt2 file
(set-option :print-success false)
(set-info :smt-lib-version 2.6)
(set-option :smt.AUTO_CONFIG false)
(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 3)
(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.BV.REFLECT true)
(set-option :smt.qi.max_multi_patterns 1000)
(set-option :smt.mbqi false)
(set-option :model.compact false)
(set-option :model.v2 true)
(set-option :pp.bv_literals false)
; done setting options
(declare-sort Ref 0)
(declare-sort Field 0)
(declare-sort Mask 0)
(declare-fun select_mask (Mask Ref Field) Real)
(declare-fun zero_mask () Mask)
(declare-sort Heap 0)
(declare-fun length (Heap Ref) Int)
(declare-fun length* (Heap Ref) Int)
(declare-fun select_heap (Heap Ref Field) Ref)
(declare-fun next () Field)
(declare-fun null () Ref)
(declare-fun this () Ref)
(declare-fun mask () Mask)
(declare-fun store_mask (Mask Ref Field Real) Mask)
(assert (forall ( ( ?x0 Mask) ( ?x1 Ref) ( ?x2 Field) ( ?x3 Real)) (! (= (select_mask (store_mask ?x0 ?x1 ?x2 ?x3) ?x1 ?x2) ?x3) :weight 0)))
(assert (forall ( ( ?x0 Mask) ( ?x1 Ref) ( ?y1 Ref) ( ?x2 Field) ( ?y2 Field) ( ?x3 Real)) (! (=> (or (not (= ?x1 ?y1)) (not (= ?x2 ?y2))) (= (select_mask (store_mask ?x0 ?x1 ?x2 ?x3) ?y1 ?y2) (select_mask ?x0 ?y1 ?y2))) :weight 0)))
(assert (forall ((o_2 Ref) (f_4 Field) ) (! (= (select_mask zero_mask o_2 f_4) 0.0)
:pattern ( (select_mask zero_mask o_2 f_4))
)))
(assert (forall ((heap Heap) (this Ref) ) (! (= (length heap this) (length* heap this))
:pattern ( (length heap this))
)))
(assert (forall ((heap Heap) (this Ref) ) (! (= (length heap this) (ite (= (select_heap heap this next) null) 1 (+ 1 (length* heap (select_heap heap this next)))))
:pattern ( (length heap this))
)))
(assert (not (= this null)))
(assert (= mask (store_mask zero_mask this next 1.0)))
(declare-fun heap () Heap)
(declare-fun valid-next () Field)
(push 1)
(set-info :boogie-vc-id test2)
(set-option :timeout 0)
(set-option :rlimit 0)
(assert
(or (<= (select_mask mask this next) 0.0)
(and (= (select_heap heap this next) this)
(or (> 1.0 (select_mask zero_mask null valid-next))
(and (<= 1.0 (select_mask zero_mask null valid-next))
(>= (length heap this) (length heap (select_heap heap this next))))))))
(check-sat)
(pop 1)
Of the Z3 versions I tried, 4.8.6 - 4.8.9 returned unknown but 4.8.10 - 4.9.1 returned unsat. The unsat result seems to come from triggering the length axioms, but it seems like if disjuncts were looked at in order, Z3 should find a partial model where (= (select_heap heap this next) this) and (> 1.0 (select_mask zero_mask null valid-next)) without triggering the length axioms and return unknown. Is this a bug in Z3, or does Z3 not provide gaurentees about the order disjuncts are looked at.
I also tried removing the push and pop lines which caused all the versions of Z3 I tried to return unsat.

After trimming z3 specific settings, Both CVC4 and CVC5 also return unsat for this query. Note that unknown isn't incompatible with unsat: It's perfectly OK for the solver to now return unsat for something it previously said unknown. (Or vice-versa.)
Given CVC4/CVC4 and z3 all agree on unsat, the way to debug this would be:
See if you can get an unsat core, and if that makes sense
Assert extra conditions that tie the model to what it should be according to your expectations. Hopefully doing that will show you why it is unsatisfiable.
If you need more help from this community, you'll have to specify exactly what model you'd consider would satisfy this formula, and debug it first to remove as much of the problem as you possibly can. Best of luck!

Related

Why is Z3 unable to prove `unsat` for this query, whereas cvc5 succeeds?

I have a (relatively) simple query attached below
(set-logic AUFLIRA)
(set-option :produce-models true)
(declare-sort Loc 0)
(declare-sort HeapChunk 0)
(declare-sort HeapIndex 0)
(declare-fun makeHeapChunk (Loc Real) HeapChunk)
(declare-fun valid ((Array HeapIndex HeapChunk)) Bool)
(declare-const empty (Array HeapIndex HeapChunk))
(assert (
forall (
(heap (Array HeapIndex HeapChunk))
(i1 HeapIndex)
(i2 HeapIndex)
(l1 Loc)
(l2 Loc)
(own_val1 Real)
(own_val2 Real)
)
(=>
(and
(valid heap)
(= (select heap i1) (makeHeapChunk l1 own_val1))
(= (select heap i2) (makeHeapChunk l2 own_val2))
(> (+ own_val1 own_val2) 1)
(not (= i1 i2))
)
(not (= l1 l2))
)
))
(declare-const index0 HeapIndex)
(declare-const index1 HeapIndex)
(assert (not (= index0 index1)))
(declare-const l1 Loc)
(declare-const heap0 (Array HeapIndex HeapChunk))
(assert (valid heap0))
(assert (= heap0 (store empty index0 (makeHeapChunk l1 0.6))))
(declare-const heap1 (Array HeapIndex HeapChunk))
(assert (valid heap1))
(assert (= heap1 (store heap0 index1 (makeHeapChunk l1 0.5))))
(check-sat)
(get-model)
(exit)
It should return unsat because heap1 contains two elements which violate the constraints in the big assertion (essentially because 0.6 + 0.5 > 1 and valid(heap1)). Giving this example to cvc5 returns unsat which is the expected outcome, but z3 returns unknown. I want to understand why, and what I can do to help z3 figure this out.
Thanks in advance.

Z3 returns model not available

If possible I'd like a second opinion on my code.
The constraints of the problem are:
a,b,c,d,e,f are non-zero integers
s1 = [a,b,c] and s2 = [d,e,f] are sets
The sum s1_i + s2_j for i,j = 0..2 has to be a perfect square
I don't understand why but my code returns model not available. Moreover, when commenting out the following lines:
(assert (and (> sqrtx4 1) (= x4 (* sqrtx4 sqrtx4))))
(assert (and (> sqrtx5 1) (= x5 (* sqrtx5 sqrtx5))))
(assert (and (> sqrtx6 1) (= x6 (* sqrtx6 sqrtx6))))
(assert (and (> sqrtx7 1) (= x7 (* sqrtx7 sqrtx7))))
(assert (and (> sqrtx8 1) (= x8 (* sqrtx8 sqrtx8))))
(assert (and (> sqrtx9 1) (= x9 (* sqrtx9 sqrtx9))))
The values for d, e, f are negative. There is no constraint that requires them to do so. I'm wondering if perhaps there are some hidden constraints that sneaked in and mess up the model.
A valid expected solution would be:
a = 3
b = 168
c = 483
d = 1
e = 193
f = 673
Edit: inserting (assert (= a 3)) and (assert (= b 168)) results in the solver finding the correct values. This only puzzles me further.
Full code:
(declare-fun sqrtx1 () Int)
(declare-fun sqrtx2 () Int)
(declare-fun sqrtx3 () Int)
(declare-fun sqrtx4 () Int)
(declare-fun sqrtx5 () Int)
(declare-fun sqrtx6 () Int)
(declare-fun sqrtx7 () Int)
(declare-fun sqrtx8 () Int)
(declare-fun sqrtx9 () Int)
(declare-fun a () Int)
(declare-fun b () Int)
(declare-fun c () Int)
(declare-fun d () Int)
(declare-fun e () Int)
(declare-fun f () Int)
(declare-fun x1 () Int)
(declare-fun x2 () Int)
(declare-fun x3 () Int)
(declare-fun x4 () Int)
(declare-fun x5 () Int)
(declare-fun x6 () Int)
(declare-fun x7 () Int)
(declare-fun x8 () Int)
(declare-fun x9 () Int)
;all numbers are non-zero integers
(assert (not (= a 0)))
(assert (not (= b 0)))
(assert (not (= c 0)))
(assert (not (= d 0)))
(assert (not (= e 0)))
(assert (not (= f 0)))
;both arrays need to be sets
(assert (not (= a b)))
(assert (not (= a c)))
(assert (not (= b c)))
(assert (not (= d e)))
(assert (not (= d f)))
(assert (not (= e f)))
(assert (and (> sqrtx1 1) (= x1 (* sqrtx1 sqrtx1))))
(assert (and (> sqrtx2 1) (= x2 (* sqrtx2 sqrtx2))))
(assert (and (> sqrtx3 1) (= x3 (* sqrtx3 sqrtx3))))
(assert (and (> sqrtx4 1) (= x4 (* sqrtx4 sqrtx4))))
(assert (and (> sqrtx5 1) (= x5 (* sqrtx5 sqrtx5))))
(assert (and (> sqrtx6 1) (= x6 (* sqrtx6 sqrtx6))))
(assert (and (> sqrtx7 1) (= x7 (* sqrtx7 sqrtx7))))
(assert (and (> sqrtx8 1) (= x8 (* sqrtx8 sqrtx8))))
(assert (and (> sqrtx9 1) (= x9 (* sqrtx9 sqrtx9))))
;all combinations of sums need to be squared
(assert (= (+ a d) x1))
(assert (= (+ a e) x2))
(assert (= (+ a f) x3))
(assert (= (+ b d) x4))
(assert (= (+ b e) x5))
(assert (= (+ b f) x6))
(assert (= (+ c d) x7))
(assert (= (+ c e) x8))
(assert (= (+ c f) x9))
(check-sat-using (then simplify solve-eqs smt))
(get-model)
(get-value (a))
(get-value (b))
(get-value (c))
(get-value (d))
(get-value (e))
(get-value (f))
Nonlinear integer arithmetic is undecidable. This means that there is no decision procedure that can decide arbitrary non-linear integer constraints to be satisfiable. This is what z3 is telling you when it says "unknown" as the answer your query.
This, of course, does not mean that individual cases cannot be answered. Z3 has certain tactics it applies to solve such formulas, but it is inherently limited in what it can handle. Your problem falls into that category: One that Z3 is just not capable of solving.
Z3 has a dedicated NRA (non-linear real arithmetic) tactic that you can utilize. It essentially treats all variables as reals, solves the problem (nonlinear real arithmetic is decidable and z3 can find all algebraic real solutions), and then checks if the results are actually integer. If not, it tries another solution over the reals. Sometimes this tactic can handle non-linear integer problems, if you happen to hit the right solution. You can trigger it using:
(check-sat-using qfnra)
Unfortunately it doesn't solve your particular problem in the time I allowed it to run. (More than 10 minutes.) It's unlikely it'll ever hit the right solution.
You really don't have many options here. SMT solvers are just not a good fit for nonlinear integer problems. In fact, as I alluded to above, there is no tool that can handle arbitrary nonlinear integer problems due to undecidability; but some tools fare better than others depending on the algorithms they use.
When you tell z3 what a and b are, you are essentially taking away much of the non-linearity, and the rest becomes easy to handle. It is possible that you can find a sequence of tactics to apply that solves your original, but such tricks are very brittle in practice and not easily discovered; as you are essentially introducing heuristics into the search and you don't have much control over how that behaves.
Side note: Your script can be improved slightly. To express that a bunch of numbers are all different, use the distinct predicate:
(assert (distinct (a b c)))
(assert (distinct (d e f)))

Z3 Java API - get unsat core

I am trying to figure out how to get the unsat core using the Java API for Z3. Our scenario is as follows (code is underneath, which works in rise4fun):
We create the SMT2 input programtically
The input contains function definitions, datatype declarations, and assertions
We parse this using the parseSMTLIB2String API
We ensure that the context and the solver have unsat_core -> true
Z3 returns UNSAT for the provided input, which is correct
The UNSAT core is always empty though.
The same input produces an UNSAT core correctly on rise4fun (x1 x3)
I am guessing I am misusing the API somehow... but not quite sure how/why.
I have noticed that I cannot set the unsat core option in the SMT string that I pass to parseSMTLIB2String, because that is not allowed. I am guessing that is expected.
Can someone point me in the right direction please?
Thanks!!
(set-option :smt.macro-finder true)
;; The following is only for rise4fun, i cannot get it
;; to work with the parse SMT Java API
(set-option :produce-unsat-cores true)
(define-sort Ref () Int)
(declare-datatypes (T1 T2) ((Tuple2 (mk-Tuple2 (_1 T1)(_2 T2)))))
(declare-datatypes (T1 T2 T3) ((Tuple3 (mk-Tuple3 (_1 T1)(_2 T2)(_3 T3)))))
(define-sort Set (T) (Array T Bool))
(define-sort Bag (T) (Array T Int))
(declare-const emptySetOf_Int (Set Int))
(assert (!(forall ((x Int)) (= (select emptySetOf_Int x) false)) :named AXIOM1))
(declare-sort TopLevelDeclarations) (declare-const mk-TopLevelDeclarations TopLevelDeclarations)
(declare-datatypes () ((A (mk-A (x Int)(y Int)))))
(declare-datatypes () ((Any
(lift-TopLevelDeclarations (sel-TopLevelDeclarations TopLevelDeclarations))
(lift-A (sel-A A))
null))
)
(declare-const heap (Array Ref Any))
(define-fun deref ((ref Ref)) Any
(select heap ref)
)
(define-fun deref-is-TopLevelDeclarations ((this Ref)) Bool
(is-lift-TopLevelDeclarations (deref this))
)
(define-fun deref-TopLevelDeclarations ((this Ref)) TopLevelDeclarations
(sel-TopLevelDeclarations (deref this))
)
(define-fun deref-is-A ((this Ref)) Bool
(is-lift-A (deref this))
)
(define-fun deref-A ((this Ref)) A
(sel-A (deref this))
)
(define-fun deref-isa-TopLevelDeclarations ((this Ref)) Bool
(deref-is-TopLevelDeclarations this)
)
(define-fun deref-isa-A ((this Ref)) Bool
(deref-is-A this)
)
(define-fun A!x ((this Ref)) Int
(x (deref-A this))
)
(define-fun A!y ((this Ref)) Int
(y (deref-A this))
)
(define-fun TopLevelDeclarations.inv ((this Ref)) Bool
true
)
(assert (! (forall ((this Ref))
(=> (deref-is-TopLevelDeclarations this) (TopLevelDeclarations.inv this))
) :named x0))
(define-fun A.inv ((this Ref)) Bool
(and
(> (A!x this) 0)
(> (A!y this) 0)
(< (A!x this) 0)
)
)
(assert (! (forall ((this Ref))
(=> (deref-is-A this) (A.inv this))
) :named x1))
(assert (!(deref-is-TopLevelDeclarations 0) :named TOP))
(assert (!(exists ((instanceOfA Ref)) (deref-is-A instanceOfA)) :named x3))
(check-sat)
(get-unsat-core)
You're not using the Java API, except for the call to parseSMTLIB2String. This function does not execute any SMT commands and there is no function that would do that for you. parseSMTLIB2String exists exclusively to parse assertions, it will ignore everything else. For this particular problem, I recommend to simply pass the problem file to z3.exe either as a command line argument or via stdin (use -in option). This produces
unsat
(x1 x3)
If the intent is to use the Java API for other things at a later point, see the Java API unsat core example.
I just had the same problem and found that it depends on how you pass the parsed input to the solver.
While the following approach produces an empty unsat-core...:
BoolExpr[] program = context.parseSMTLIB2File(input_file, null, null, null, null);
solver.add(program);
solver.check();
solver.getUnsatCore();
... the unsat-core is produced correctly if you give the parsed input file to the solver as follows:
BoolExpr[] program = context.parseSMTLIB2File(input_file, null, null, null, null);
solver.check(program);
solver.getUnsatCore();
I therefore assume that everything that is passed via solver.add() belongs to the assumptions and therefore presumably does not belong to the unsat-core according to Z3.

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

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.

soundness issue with integer/bv mixed benchmarks?

I've the following SMT-Lib2 script:
(set-option :produce-models true)
(declare-fun s0 () Int)
(declare-fun table0 (Int) (_ BitVec 8))
(assert (= (table0 0) #x00))
(assert
(let ((s3 (ite (or (< s0 0) (<= 1 s0)) #x01 (table0 s0))))
(let ((s5 (ite (bvuge s3 #x02) #b1 #b0)))
(= s5 #b1))))
(check-sat)
(get-model)
With Z3 v3.2 running on the Mac, I get:
sat
(model
;; universe for (_ BitVec 8):
;; bv!val!2 bv!val!3 bv!val!0 bv!val!1
;; -----------
;; definitions for universe elements:
(declare-fun bv!val!2 () (_ BitVec 8))
(declare-fun bv!val!3 () (_ BitVec 8))
(declare-fun bv!val!0 () (_ BitVec 8))
(declare-fun bv!val!1 () (_ BitVec 8))
;; cardinality constraint:
(forall ((x (_ BitVec 8)))
(and (= x bv!val!2) (= x bv!val!3) (= x bv!val!0) (= x bv!val!1)))
;; -----------
(define-fun s0 () Int
(- 1))
(define-fun table0 ((x!1 Int)) (_ BitVec 8)
(ite (= x!1 0) bv!val!0
(ite (= x!1 (- 1)) bv!val!3
bv!val!0)))
)
Which states that s0 = -1 is a model. However, with s0 = -1, we have s3 = 1 and s5 = #b0, which makes the assertion false. In fact, I'm quite sure the benchmark as stated is unsatisfiable.
One thing I noticed in the Z3 output is the quantified formula it gives for the cardinality constraint. It says:
;; cardinality constraint:
(forall ((x (_ BitVec 8)))
(and (= x bv!val!2) (= x bv!val!3) (= x bv!val!0) (= x bv!val!1)))
The assertion is a conjunction, which sounds rather weird; shouldn't that be a disjunction? I'm not sure if this is the root-cause of the problem, but it sure sounds fishy.
There are two problems in Z3.
First, you are correct, there is a typo in the model printer. It should be a "or" instead of an "and". The second problem is that Z3 did not install the bit-vector theory and treated (_ BitVec 8) as a uninterpreted sort. This was a bug in the preprocessor that is used to decide in which logic the problem is in. You can workaround this bug by adding the following command in the beginning of the file:
(set-option :auto-config false)
These bugs have been fixed, and the fix will be available in the next release.

Resources