Z3 :named supported in z3 api - z3

my question is that i do not see that the unsat_core tracks any assertions that are provided in an entire chunk using the api
f = Z3_parse_smtlib2_string(c, "unsat_core_example1.smt2",0,0,0,0,0,0);
params p(c);
p.set(":unsat-core", true);
s.set(p);
// enabling unsat core tracking
expr r = to_expr(c, f);
unsat_core_example1.smt2:
(declare-fun p () Bool)
(declare-fun q () Bool)
(declare-fun r () Bool)
(declare-fun s () Bool)
(assert (! (or p q) :named a1))
(assert (! (implies r s) :named a2))
(assert (! (implies s (iff q r)) :named a3))
(assert (! (or r p) :named a4))
(assert (! (or r s) :named a5))
(assert (! (not (and r q)) :named a6))
(assert (! (not (and s p)) :named a7))
it seems like the annotation :named is not being processed, as the unsat_core vector returned is always empty.
However, this is not the case if i were to use z3.exe and input the file in.
Any idea what could be the cause?

The Z3_parse_smtlib2_file function does not support all of SMTLIB2, it's really just a convenience function; it's output is not guaranteed to completely cover all of the language (e.g., it doesn't execute commands like check-sat or some set-option commands). It was also written as an extension of a previous SMTLIB1 parser which was written long before the goal/tactic/solver architecture was introduced and consequently not all information is carried over to this new architecture.
In this particular case, the assertion names are indeed saved inside the context, but Z3_parse_smtlib2_file does not return a set of assertions and names; it returns a single expression which is unnamed. To accurately represent an SMTLIB2 benchmark the signature of the function would have to change significantly.
In the example given, the expression which is asserted into the solver is r, i.e., we would have something like
s.add(r);
this essentially asks the solver the assert the unnamed assertion r, but there is no support for "subnames" for expressions inside of r. We can still name r itself however, e.g., by calling
s.add(r, "top")
which results in the correct unsat core.

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.

Creating a transitive and not reflexive function in Z3

I'm trying to create a function in Z3 that is transitive but not reflexive. I.e. if (transitive a b) and (transitive b c)hold then (transitive a c) should hold, but (transitive a a) should not.
I've tried to do it the following way, with 5 "tests". The first does what I expect, but the second one fails and results in unknown.
(declare-datatypes () ((T T1 T2 T3)))
(declare-fun f (T T) Bool)
(assert(f T1 T2))
(assert(f T2 T3))
; Make sure that f is not reflexive
(assert
(forall ((x T))
(not (f x x))))
; Now we create the transitivity function ourselves
(define-fun-rec transitive ((x T) (y T)) Bool
(or
(f x y)
(exists ((z T))
(and
(f x z)
(transitive z y)))))
; This works and gives sat
(push)
(assert (not (transitive T1 T1)))
(assert (not (transitive T2 T2)))
(assert (not (transitive T3 T3)))
(check-sat)
(pop)
; This fails with "unknown" and the verbose flag gives: (smt.mbqi "max instantiations 1000 reached")
(push)
(assert
(forall ((x T))
(not (transitive x x))))
(check-sat)
(pop)
My question is: how does the second test differ from the first? Why does the last one give unknown, whereas the one before that works just fine?
The "verbose" message is a hint here. mbqi stands for model-based-quantifier-instantiation. It's a method of dealing with quantifiers in SMT solving. In the first case, MBQI manages to find a model. But your transitive function is just too complicated for MBQI to handle, and thus it gives up. Increasing the limit will not likely address the problem, nor it's a long term solution.
Short story long, recursive definitions are difficult to deal with, and recursive definitions with quantifiers are even harder. The logic becomes semi-decidable, and you're at the mercy of heuristics. Even if you found a way to make z3 compute a model for this, it would be brittle. These sorts of problems are just not suitable for SMT solving; better use a proper theorem prover like Isabelle, Hol, Coq, Lean. Agda, etc. Almost all these tools offer "tactics" to dispatch subgoals to SMT solvers, so you have the best of both worlds. (Of course you lose full automation, but with quantifiers present, you can't expect any better.)

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.

Datatypes and quantifier patterns/triggers

I observed a difference in Z3's quantifier triggering behaviour (I tried 4.4.0 and 4.4.2.3f02beb8203b) that I cannot explain. Consider the following program:
(set-option :auto_config false)
(set-option :smt.mbqi false)
(declare-datatypes () ((Snap
(Snap.unit)
(Snap.combine (Snap.first Snap) (Snap.second Snap))
)))
(declare-fun fun (Snap Int) Bool)
(declare-fun bar (Int) Int)
(declare-const s1 Snap)
(declare-const s2 Snap)
(assert (forall ((i Int)) (!
(> (bar i) 0)
:pattern ((fun s1 i))
)))
(assert (fun s2 5))
(assert (not (> (bar 5) 0)))
(check-sat) ; unsat
As far as my understanding goes, the unsat is unexpected: Z3 should not be able to trigger the forall since it is guarded by the pattern (fun s1 i), and Z3 should not be able (and actually isn't) to prove that s1 = s2.
In contrast, if I declare Snap to be an uninterpreted sort, then the final check-sat yields unknown - which is what I would expect:
(set-option :auto_config false)
(set-option :smt.mbqi false)
(declare-sort Snap 0)
...
(check-sat) ; unknown
If I assume s1 and s2 to be different, i.e.
(assert (not (= s1 s2)))
then the final check-sat yields unknown in both cases.
For convenience, here is the example on rise4fun.
Q: Is the difference in behaviour a bug, or is it intended?
The assertion (not (= s1 s2)) is essential. With pattern based quantifier instantiation, the pattern matches if the current state of the search satisfies s1 = s2. In the case of algebraic data-types, Z3 tries to satisfy formulas with algebraic data-types by building a least model in terms of constructor applications. In the case of Snap as an algebraic data-type the least model for s1, s2 have them both as Snap.unit. At that point, the trigger is enabled because the terms E-match. In other words, modulo the congruences, the variable I can be instantiated such that (fun s1 I) matches (fun s2 5), but setting I <- 5. After the trigger is enabled, the quantifier is instantiated and the axiom
(=> (forall I F(I)) (F(5)))
is added (where F is the formula under the quantifier).
This then enables to infer the contradiction and infer unsat.
When Snap is uninterpreted, Z3 attempts to construct a model where terms s1 and s2 are different. Since there is nothing to force these terms to be equal they remain distinct
It's not a bug since z3 doesn't say unsat for a sat formula (or sat for an unsat one). In presence of quantified formulas, SMT solvers are (in general) not complete. So they sometimes answer unknown when they are not sure that the input formula in sat.
For your example:
a - It's normal that, with matching techniques, z3 does not prove the formula when you assume that s1 and s2 are different. In fact, there is no ground term of the form (fun s1 5) that matches the pattern (fun s1 i), and that would allow the generation of the useful instance (> (bar 5) 0) from your quantified formula;
b - When you don't assume that s1 and s2 are different, you would not be able to get the proof too. Except that z3 probably assumes internally that s1 = s2 when Snap is a datatype. This is correct as long as there is nothing that contradicts s1 = s2. Thanks to this and to matching modulo equality, the ground term (fun s2 5) matches the pattern (fun s1 i), and the needed instance to prove unsatisfiability is generated.

Using Z3 QFNRA tactic with datatypes: interaction or inlining

In Non-linear arithmetic and uninterpreted functions, Leonardo de Moura states that the qfnra-nlsat tactic hasn't been fully integrated with the rest of Z3 yet. I thought that the situation has changed in two years, but apparently the integration is still not very complete.
In the example below, I use datatypes purely for "software engineering" purposes: to organize my data into records. Even though there are no uninterpreted functions, Z3 still fails to give me a solution:
(declare-datatypes () (
(Point (point (point-x Real) (point-y Real)))
(Line (line (line-a Real) (line-b Real) (line-c Real)))))
(define-fun point-line-subst ((p Point) (l Line)) Real
(+ (* (line-a l) (point-x p)) (* (line-b l) (point-y p)) (line-c l)))
(declare-const p Point)
(declare-const l Line)
(assert (> (point-y p) 20.0))
(assert (= 0.0 (point-line-subst p l)))
(check-sat-using qfnra-nlsat)
(get-model)
> unknown
(model
)
However, if I manually inline all the functions, Z3 finds a model instantly:
(declare-const x Real)
(declare-const y Real)
(declare-const a Real)
(declare-const b Real)
(declare-const c Real)
(assert (> y 20.0))
(assert (= 0.0 (+ (* a x) (* b y) c)))
(check-sat-using qfnra-nlsat)
(get-model)
> sat
(model
(define-fun y () Real
21.0)
(define-fun a () Real
0.0)
(define-fun x () Real
0.0)
(define-fun b () Real
0.0)
(define-fun c () Real
0.0)
)
My question is, is there a way to perform such an inlining automatically? I'm fine with either one of these workflows:
Launch Z3 with a tactic that says "Inline first, then apply qfnra-nlsat. I haven't found a way to do so, but maybe I wasn't looking well enough.
Launch Z3 using some version of simplify to do the inlining. Launch Z3 the second time on the result of the first invocation (the inlined version).
In other words, how to make qfnra-nlsat work with tuples?
Thank you!
That's correct, the NLSAT solver is still not integrated with the other theories. At the moment, we can only use it if we eliminate all datatypes (or elements of other theories) before running it. I believe there is no useful existing tactic inside of Z3 at the moment though, so this would have to be done beforehand. In general it's not hard to compose tactics, e.g., like this:
(check-sat-using (and-then simplify qfnra-nlsat))
but the simplifier is not strong enough to eliminate the datatype constants in this problem. (The respective implementation files are datatype_rewriter.cpp and datatype_simplifier_plugin.cpp.)

Resources