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

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.

Related

defining list concat in smtlib

I defined my own version of list concat based on Haskell as below:
(declare-datatypes ((MyList 1))
((par (T) ((cons (head T) (tail (MyList T))) (nil)))))
(declare-fun my-concat ( (MyList T1) (MyList T1) ) (MyList T1))
(assert (forall ((xs (MyList T1)) (ys (MyList T1)) (x T1))
(ite (= (as nil (MyList T1)) xs)
(= (my-concat xs ys) ys)
(= (my-concat (cons x xs) ys) (cons x (my-concat xs ys))))))
I am wondering why z3 is not able to reason about the following?
(assert (not (= (my-concat (cons 4 (as nil (MyList Int))) (as nil (MyList Int)))
(cons 4 (as nil (MyList Int))))))
(check-sat) ; runs forever
Loading your file
If I load your program to z3 as you've given it, it says:
(error "line 3 column 33: Parsing function declaration. Expecting sort list '(': unknown sort 'T1'")
(error "line 4 column 29: invalid sorted variables: unknown sort 'T1'")
(error "line 8 column 79: unknown function/constant my-concat")
That's because you cannot define "polymorphic" functions in SMTLib. At the user-level, only fully monomorphic functions are allowed. (Though internally SMTLib does provide polymorphic constants, there's no way for the user to actually create any polymorphic constants.)
So, I'm not sure how you got to load that file.
Monomorphisation
Looks like you only care about integer-lists anyhow, so let's just modify our program to work on lists of integers. This process is called monomorphisation, and is usually automatically done by some front-end tool before you even get to the SMT-solver, depending on what framework you're working in. That's just a fancy way of saying create instances of all your polymorphic constants at the mono-types they are used. (Since you mentioned Haskell, I'll just throw in that while monomorphisation is usually possible, it's not always feasible: There might be way too many variants to generate making it impractical. Also, if you have polymoprhic-recursion then monomorphisation doesn't work. But that's a digression for the time being.)
If I monomorphise your program to Int's only, I get:
(declare-datatypes ((MyList 1))
((par (T) ((cons (head T) (tail (MyList T))) (nil)))))
(declare-fun my-concat ( (MyList Int) (MyList Int) ) (MyList Int))
(assert (forall ((xs (MyList Int)) (ys (MyList Int)) (x Int))
(ite (= (as nil (MyList Int)) xs)
(= (my-concat xs ys) ys)
(= (my-concat (cons x xs) ys) (cons x (my-concat xs ys))))))
(assert (not (= (my-concat (cons 4 (as nil (MyList Int))) (as nil (MyList Int)))
(cons 4 (as nil (MyList Int))))))
(check-sat)
(get-info :reason-unknown)
When I run z3 on this, I get:
unknown
(:reason-unknown "smt tactic failed to show goal to be sat/unsat (incomplete quantifiers)")
So, it doesn't loop at all like you mentioned; but your file didn't really load in the first place. So maybe you were working with some other contents in the file as well, but that's a digression for the current question.
But it still doesn't prove it!
Of course, you wanted unsat for this trivial formula! But z3 said it's too hard for it to deal with. The reason-unknown is "incomplete quantifiers." What does that mean?
In short, SMTLib is essentially logic of many-sorted first order formulas. Solvers are "complete" for the quantifier-free fragment of this logic. But adding quantifiers makes the logic semi-decidable. What that means is that if you give enough resources, and if smart heuristics are in play, the solver will eventually say sat for a satisifiable formula, but it may loop forever if given an unsat one. (It might get lucky and say unsat as well, but most likely it'll loop.)
There are very good reasons why above is the case, but keep in mind that this has nothing to do with z3: First-order logic with quantifiers is semi-decidable. (Here's a good place to start reading: https://en.wikipedia.org/wiki/Decidability_(logic)#Semidecidability)
What usually happens in practice, however, is that the solver will not even answer sat, and will simply give up and say unknown as z3 did above. Quantifiers are simply beyond the means of SMT-solvers in general. You can try using patterns (search stack-overflow for quantifiers and pattern triggers), but that usually is futile as patterns and triggers are tricky to work with and they can be quite brittle.
What course of action do you have:
Honestly, this is one of those cases where "give up" is good advice. An SMT solver is just not a good fit for this sort of a problem. Use a theorem-prover like Isabelle, ACL2, Coq, HOL, HOL-Light, Lean, ... where you can express quantifiers and recursive functions and reason with them. They are built for this sort of thing. Don't expect your SMT solver to handle these sorts of queries. It's just not the right match.
Still, is there anything I can do?
You can try SMTLib's recursive-function definition facilities. You'd write:
(declare-datatypes ((MyList 1))
((par (T) ((cons (head T) (tail (MyList T))) (nil)))))
(define-fun-rec my-concat ((xs (MyList Int)) (ys (MyList Int))) (MyList Int)
(ite (= (as nil (MyList Int)) xs)
ys
(cons (head xs) (my-concat (tail xs) ys))))
(assert (not (= (my-concat (cons 4 (as nil (MyList Int))) (as nil (MyList Int)))
(cons 4 (as nil (MyList Int))))))
(check-sat)
Note the define-fun-rec construct, which allows for recursive definitions.
And voila, we get:
unsat
But this does not mean z3 will be able to prove arbitrary theorems regarding this concat function. If you try anything that requires induction, it'll either give up (saying unknown) or loop-forever. Of course, as the capabilities improve some induction-like proofs might be possible in z3 or other SMT-solvers, but that's really beyond what they're designed for. So, use with caution.
You can read more about recursive definitions in Section 4.2.3 of http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf
To sum up
Do not use an SMT-solver for reasoning with quantified or recursive definitions. They just don't have the necessary power to deal with such problems, and it's unlikely they'll ever get there. Use a proper theorem prover for these tasks. Note that most theorem-provers use SMT-solvers as underlying tactics, so you get the best of both worlds: You can do some manual work to guide the inductive proof, and have the prover use an SMT-solver to handle most of the goals automatically for you. Here's a good paper to read to get you started on the details: https://people.mpi-inf.mpg.de/~jblanche/jar-smt.pdf
alias makes valid points, but I don't completely agree with the final conclusion to " not use an SMT-solver for reasoning with quantified or recursive definitions". In the end, it depends on which kind of properties you need to reason about, what kind of answers you need (is unsat/unknown OK, or do you need unsat/sat and a model?), and how much work you're willing to invest :-)
For example, SMT-based program verifiers such as Dafny and Viper quickly verify the following assertions about lists:
assert [4] + [] == [4]; // holds
assert [4,1] + [1,4] == [4,1,1,4]; // holds
assert [4] + [1] == [1,4]; // fails
Both tools can be used online, but the websites are rather slow and not terribly reliable. You can find the Dafny example here and the Viper example here.
Here is the relevant SMT code that Viper generates:
(set-option :auto_config false) ; Usually a good idea
(set-option :smt.mbqi false)
;; The following definitions are an excerpt of Viper's sequence axiomatisation,
;; which is based on Dafny's sequence axiomatisation.
;; See also:
;; https://github.com/dafny-lang/dafny
;; http://viper.ethz.ch
(declare-sort Seq<Int>) ;; Monomorphised sort of integer sequences
(declare-const Seq_empty Seq<Int>)
(declare-fun Seq_length (Seq<Int>) Int)
(declare-fun Seq_singleton (Int) Seq<Int>)
(declare-fun Seq_index (Seq<Int> Int) Int)
(declare-fun Seq_append (Seq<Int> Seq<Int>) Seq<Int>)
(declare-fun Seq_equal (Seq<Int> Seq<Int>) Bool)
(assert (forall ((s Seq<Int>)) (!
(<= 0 (Seq_length s))
:pattern ((Seq_length s))
)))
(assert (= (Seq_length (as Seq_empty Seq<Int>)) 0))
(assert (forall ((s1 Seq<Int>) (s2 Seq<Int>)) (!
(implies
(and
(not (= s1 (as Seq_empty Seq<Int>)))
(not (= s2 (as Seq_empty Seq<Int>))))
(= (Seq_length (Seq_append s1 s2)) (+ (Seq_length s1) (Seq_length s2))))
:pattern ((Seq_length (Seq_append s1 s2)))
)))
(assert (forall ((s Seq<Int>)) (!
(= (Seq_append (as Seq_empty Seq<Int>) s) s)
:pattern ((Seq_append (as Seq_empty Seq<Int>) s))
)))
(assert (forall ((s Seq<Int>)) (!
(= (Seq_append s (as Seq_empty Seq<Int>)) s)
:pattern ((Seq_append s (as Seq_empty Seq<Int>)))
)))
(assert (forall ((s1 Seq<Int>) (s2 Seq<Int>) (i Int)) (!
(implies
(and
(not (= s1 (as Seq_empty Seq<Int>)))
(not (= s2 (as Seq_empty Seq<Int>))))
(ite
(< i (Seq_length s1))
(= (Seq_index (Seq_append s1 s2) i) (Seq_index s1 i))
(= (Seq_index (Seq_append s1 s2) i) (Seq_index s2 (- i (Seq_length s1))))))
:pattern ((Seq_index (Seq_append s1 s2) i))
:pattern ((Seq_index s1 i) (Seq_append s1 s2))
)))
(assert (forall ((s1 Seq<Int>) (s2 Seq<Int>)) (!
(=
(Seq_equal s1 s2)
(and
(= (Seq_length s1) (Seq_length s2))
(forall ((i Int)) (!
(implies
(and (<= 0 i) (< i (Seq_length s1)))
(= (Seq_index s1 i) (Seq_index s2 i)))
:pattern ((Seq_index s1 i))
:pattern ((Seq_index s2 i))
))))
:pattern ((Seq_equal s1 s2))
)))
(assert (forall ((s1 Seq<Int>) (s2 Seq<Int>)) (!
(implies (Seq_equal s1 s2) (= s1 s2))
:pattern ((Seq_equal s1 s2))
)))
; ------------------------------------------------------------
; assert Seq(4) ++ Seq[Int]() == Seq(4)
(push)
(assert (not
(Seq_equal
(Seq_append (Seq_singleton 4) Seq_empty)
(Seq_singleton 4))))
(check-sat) ; unsat -- good!
(pop)
; assert Seq(4, 1) ++ Seq(1, 4) == Seq(4, 1, 1, 4)
(push)
(assert (not
(Seq_equal
(Seq_append
(Seq_append (Seq_singleton 4) (Seq_singleton 1))
(Seq_append (Seq_singleton 1) (Seq_singleton 4)))
(Seq_append
(Seq_append
(Seq_append (Seq_singleton 4) (Seq_singleton 1))
(Seq_singleton 1))
(Seq_singleton 4)))))
(check-sat) ; unsat -- good!
(pop)
; assert Seq(4) ++ Seq(1) == Seq(1, 4)
(push)
(assert (not (Seq_equal
(Seq_append (Seq_singleton 4) (Seq_singleton 1))
(Seq_append (Seq_singleton 1) (Seq_singleton 4)))))
(check-sat) ; unknown -- OK, since property doesn't hold
(pop)

Should naming an assertion affect the satisfiability of a check in Z3?

We have encountered constraints for which Z3 returns sat but if we then add a certain named assertion then Z3 returns unknown. One example is:
(declare-fun a () Real)
(declare-fun b () Bool)
(assert (and (<= 0.1 a) (<= a 10.0)))
(assert (= b (= 1.0 (/ 1.0 a))))
(check-sat)
Z3 reports that this is satisfiable as expected. We can additionally assert that b is either true or false, both of which are satisfiable as expected. However, if we use a named assertion for the value of b then the result of the satisfiability check can become unknown depending on the value. With the value true, Z3 still returns sat:
(set-option :produce-unsat-cores true)
(declare-fun a () Real)
(declare-fun b () Bool)
(assert (and (<= 0.1 a) (<= a 10.0)))
(assert (= b (= 1.0 (/ 1.0 a))))
(assert (! (= b true) :named c))
(check-sat)
Using the value false, Z3 returns unknown:
(set-option :produce-unsat-cores true)
(declare-fun a () Real)
(declare-fun b () Bool)
(assert (and (<= 0.1 a) (<= a 10.0)))
(assert (= b (= 1.0 (/ 1.0 a))))
(assert (! (= b false) :named c))
(check-sat)
Checking the reason for the unknown result (with (get-info :reason-unknown)) returns (incomplete (theory arithmetic)).
It appears that z3 is just picking the wrong tactic in the latter case. Replace your check-sat call with:
(check-sat-using qfnra-nlsat)
And z3 says sat in both cases.
Why z3 ends up picking a different tactic is a difficult question to answer; I'd recommend filing this as a ticket at their github site; they might be missing a heuristic.
Side note: I found this by running both cases with -v:10 and skimming through the verbose output. It's not a bad way to see what z3 is doing for short-enough benchmarks.

Different check-sat answers when asserting same property in between

Given the following input
(set-option :auto_config false)
(set-option :smt.mbqi false)
(declare-fun len (Int) Int)
(declare-fun idx (Int Int) Int)
(declare-const x Int)
(define-fun FOO () Bool
(forall ((i Int)) (!
(implies
(and (<= 0 i) (< i (len x)))
(exists ((j Int)) (!
(implies
(and (<= 0 j) (< j (len x)))
(> (idx x j) 0))))))))
(assert FOO)
; (push)
(assert (not FOO))
(check-sat)
; (pop)
; (push)
(assert (not FOO))
(check-sat)
; (pop)
Z3 4.3.2 x64 reports unsat for the first check-sat (as expected), but unknown for the second. If the commented push/pops are uncommented, both check-sats yield unknown.
My guess is that this is either a bug, or a consequence of Z3 switching to incremental mode when it reaches the second check-sat. The latter could also explain why both check-sats yield unknown if push/pop is used because Z3 will (as far as I understand) switch to incremental mode on first push.
Question: Is it a bug or an expected consequence?
Good example.
It is a limitation of how Z3 processes the formulas:
1. When using push/pop it does not detect the contradiction among the asserted formulas, instead it converts formulas to negation normal form and skolemizes quantified formulas.
2. When calling check-sat the second time, it does not keep track that the state was not retracted from a previous unsatisfiable state.
It isn't an unsoundness bug, but sure the behavior is not what a user would expect.
In addition to Nikolaj's answer: Yes, this is because Z3 switches to a different solver, which will give up earlier. We can get the same effect by setting (set-option :combined_solver.ignore_solver1 true).

Disabling introduction of Skolem function in linear arithmetic proof

Given the following SMT2 script:
(set-option :produce-proofs true)
(set-logic AUFLIRA)
(declare-sort Complex$ 0)
(declare-fun r$ () Real)
(declare-fun s$ () Complex$)
(declare-fun re$ (Complex$) Real)
(declare-fun norm$ (Complex$) Real)
(assert (! (not (=> (and (forall ((?v0 Complex$)) (<= (ite (< (re$ ?v0) 0.0) (- (re$ ?v0)) (re$ ?v0)) (norm$ ?v0))) (<= (norm$ s$) r$)) (<= (ite (< (re$ s$) 0.0) (- (re$ s$)) (re$ s$)) (+ r$ 1.0)))) :named a0))
(check-sat)
(get-proof)
Z3 (unstable version) produces a proof that contains a Skolem function "norm$0". This function is introduced in a rewrite step:
(ALL v0. (if 0 <= Re v0 then Re v0 else - 1 * Re v0) <= cmod v0) =
((ALL v0. cmod v0 = (if 0 <= Re v0 then Re v0 else - 1 * Re v0) + norm_0 v0) & (ALL v0. 0 <= norm_0 v0))
Can this behavior be suppressed by a command-line switch? That is, is there an option such that Z3 produces a proof without such a Skolem function? This should, in principle, be possible, as Z3 version 3.2 finds a proof that does not require a Skolem function.
The skolem constant is introduced by the MBQI (model-based quantifier instantiation) module. This is probably the "new part" that makes you see the different behavior.
MBQI is much more powerful than the pattern-based quantifier instantiation.
For your example, however, the pattern-based quantifier instantiation strategy works. So you can try this.
In other words run Z3 as follows to suppress MBQI:
z3 skolem-example.smt2 smt.mbqi=false auto-config=false

Horn clauses in Z3

Z3 now supports solving for inductive invariants (implying a desired property) if the semantics of the program to analyze is given as Horn clauses.
The version in the master branch of the Z3 source code on z3.codeplex.com however does not support this feature. Since Z3 solves these Horn clauses problems by the PDR algorithm, which uses interpolation, I compiled instead the interp branch (d8b31773b809), which supports (set-logic HORN).
As far as I understood, a Horn-clause problem is to be specified with unknown predicates representing invariants, and a predicate over X×Y is just a function from X×Y to Bool. So far so good.
The first example I tried is just a problem of inferring an inductive invariant for a for(int i=0; i<=10; i++) loop.
(set-logic HORN)
(declare-fun inv (Int) Bool)
(assert (inv 0))
(assert (forall ((I Int)) (or (> I 10) (not (inv I)) (inv (+ I 1)))))
(check-sat)
So far so good, got sat. Now just added (assert (not (inv 15)) and I got unsat. I then tried
(set-logic HORN)
(declare-fun inv (Int) Bool)
(assert (inv 0))
(assert (not (inv 15)))
(check-sat)
and got unsat.
What am I doing wrong?
Use the "unstable" branch.
The "interp" branch is for internal development and the state of this branch can fluctuate.
I get the answer "sat" on your second problem.
A slightly more interesting version of the first problem is:
(set-logic HORN)
(declare-fun inv (Int) Bool)
(assert (inv 0))
(assert (forall ((I Int)) (or (> I 10) (not (inv I)) (inv (+ I 1)))))
(assert (forall ((I Int)) (=> (inv I) (<= I 11))))
(check-sat)
(get-model)
It produces the obvious inductive invariant.
If you replace the last assertion by
(assert (forall ((I Int)) (=> (inv I) (<= I 10))))
Instead you get a (hard to read) proof.

Resources