Creating a transitive and not reflexive function in Z3 - 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.)

Related

How to declare forall quantifiers in SMTLIB / Z3 / CVC4?

I'm stuck on how to how to create a statement in SMTLIB2 that asserts something like
forall x < 100, f(x) = 100
This property would be check a function that adds 1 to all numbers less than 100 recursively:
(define-fun-rec incUntil100 ((x Int)) Int
(ite
(= x 100)
100
(incUntil100 (+ x 1))
)
)
I read through the Z3 tutorial on quantifiers and patterns, but that didn't seem to get me much anywhere.
In SMTLib, you'd write that property as follows:
(assert (forall ((x Int)) (=> (< x 100) (= (incUntil100 x) 100))))
(check-sat)
But if you try this, you'll see that z3 will loop forever and CVC4 will tell you unknown as the answer. While you can define and assert these sorts of functions in SMTLib, solver support for actual proofs is rather weak, as they do not do induction out-of-the box.
If proving properties of recursive functions is your goal, SMT-solvers are not a good choice; instead look into theorem provers such as Isabelle, HOL, Coq, Lean, ACL2, etc.; which are built for that very purpose.

How to represent logarithmic formula in z3py

I am very new to z3py. I am trying to code the following logarithmic expressions in z3py .
log(x,y)
I did search stack overflow a lot and came across a similar question, but unfortunately I could not get a satisfactory enough answer. Please help me!
More generally, how can we define logs with Z3?
The only way I have gotten any traction at all is to use an approximate value for e, define exp(x) as (^ e x), and then define log as a total function that is the inverse of exp. In SMT-LIB 2:
(define-fun exp ((x Real)) Real (^ 2.718281828459045 x))
(declare-fun log (Real) Real)
(assert (forall ((x Real)) (= (log (exp x)) x)))
(assert (forall ((x Real)) (= (exp (log x)) x)))
In Z3Py:
from z3 import *
from math import e
# This is an approximation
def Z3_exp(x):
return e ** x
s = Solver()
# We define Z3_log as a total function that is the inverse of Z3_exp
Z3_log = Function('log', RealSort(), RealSort())
x = Real('x')
s.add(ForAll([x], Z3_log(Z3_exp(x)) == x))
s.add(ForAll([x], Z3_exp(Z3_log(x)) == x))
The obvious problem with this is that it introduces an approximation for e, which will cause some incorrect results depending on what you are trying to prove. Also, because it uses uninterpreted functions to define log, the most powerful nonlinear solver (nlsat) will not be used, and also, because functions are total in SMT-LIB, there will be the typical weird domain issues for negative arguments.
An alternative would be to simply bound e, but this is still not exact, and is likely to have worse behavior. There is also an undocumented built-in symbol euler in Z3, but at the moment it is essentially non-functional.

Z3 start values [duplicate]

How to specify initial 'soft' values for the model? This initial model is the result of solving a similar query, and it is likely that this model has a correct pieces or even may be true for the current query.
Currently I am simulating this with an incremental solving and hard/soft constraints:
(define-fun trans_assumed ((a Int)) Int
; an initial model, which may be (partially) true
)
(declare-fun trans_sought ((a Int)) Int)
(declare-const p Bool)
(assert (=> p (forall ((a Int)) (= (trans_assumed a) (trans_sought a)))))
(check-sat p) ; in hope that trans_assumed values will be used as initial below
; add here the main constraints for trans_sought function
(check-sat) ; Z3 will use trans_assumed as a starting point for trans_sought
Does this really specify initial values for trans_sought to be trans_assumed?
Incremental mode of solving is slow compared to sequential. Any better ways of introducing initial values?
I think this is a good approach, but you may consider using more Boolean variables. Right now, it is a "all" or "nothing" approach. In your script, when (check-sat p) is executed, Z3 will look for a model where trans_assumed and trans_sought have the same interpretation. If such model does not exist, it will return with the unsat core containing p. When (check) is executed, Z3 is free to assign p to false, and the universal quantifier is essentially a don't care. That is, trans_assumed and trans_sought can be completely different.
If you use multiple Boolean variables to control the interpretation of trans_sought, you will have more flexibility.
If the rest of your problem is quantifier free, you should consider dropping the universal quantifier. This can be done if you only care about the value of trans_sought in a finite number of points.
Suppose we have that trans_assumed(0) = 1 and trans_assumed(1) = 10. Then, we can write:
assert (=> p0 (= (trans_sought 0) 1)))
assert (=> p1 (= (trans_sought 1) 10)))
In this encoding, we can query (check-sat p0 p1), (check-sat p0), (check-sat p1)

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.

Specifying initial model values for Z3

How to specify initial 'soft' values for the model? This initial model is the result of solving a similar query, and it is likely that this model has a correct pieces or even may be true for the current query.
Currently I am simulating this with an incremental solving and hard/soft constraints:
(define-fun trans_assumed ((a Int)) Int
; an initial model, which may be (partially) true
)
(declare-fun trans_sought ((a Int)) Int)
(declare-const p Bool)
(assert (=> p (forall ((a Int)) (= (trans_assumed a) (trans_sought a)))))
(check-sat p) ; in hope that trans_assumed values will be used as initial below
; add here the main constraints for trans_sought function
(check-sat) ; Z3 will use trans_assumed as a starting point for trans_sought
Does this really specify initial values for trans_sought to be trans_assumed?
Incremental mode of solving is slow compared to sequential. Any better ways of introducing initial values?
I think this is a good approach, but you may consider using more Boolean variables. Right now, it is a "all" or "nothing" approach. In your script, when (check-sat p) is executed, Z3 will look for a model where trans_assumed and trans_sought have the same interpretation. If such model does not exist, it will return with the unsat core containing p. When (check) is executed, Z3 is free to assign p to false, and the universal quantifier is essentially a don't care. That is, trans_assumed and trans_sought can be completely different.
If you use multiple Boolean variables to control the interpretation of trans_sought, you will have more flexibility.
If the rest of your problem is quantifier free, you should consider dropping the universal quantifier. This can be done if you only care about the value of trans_sought in a finite number of points.
Suppose we have that trans_assumed(0) = 1 and trans_assumed(1) = 10. Then, we can write:
assert (=> p0 (= (trans_sought 0) 1)))
assert (=> p1 (= (trans_sought 1) 10)))
In this encoding, we can query (check-sat p0 p1), (check-sat p0), (check-sat p1)

Resources