FOL definitional theory in Z3 - z3

I want to put a first-order theory into Z3, an SMT solver developed by Microsoft. This theory contains two objects obj1 and obj2, a function move that takes an object and returns an action, and a one-placed predicate occurs that takes an action as argument. The theory contains the formula occurs(move(obj1)), and I want to make sure that this is the only way in which the occurs predicate is true. I do this by giving a definition of occurs:
forall (A) (occurs(A) <-> (A = move(obj1)))
This means that occurs(move(obj1)) can be inferred from the theory, but occurs(move(obj2)) cannot. To prove this, I have translated this into Z3 as follows:
(declare-datatypes () (( Obj obj1 obj2 )))
(declare-sort Action 0)
(declare-fun occurs (Action) Bool)
(declare-fun move (Obj) Action)
(assert (forall ((A Action)) (
= (occurs A) (= A (move obj1))
)))
(check-sat)
(get-value ((occurs (move obj1))))
(get-value ((occurs (move obj2))))
The problem is that this gives the following output:
sat
(((occurs (move obj1)) true))
(((occurs (move obj2)) true))
Which I do not understand, because the definition of occurs provides all the necessary and sufficient condition for the predicate to be true, so I would think that occurs(move(obj2)) cannot be true in any model. What am I doing wrong?
Update
Thanks to de Moura I have been able to find a solution for my problem. What I need to do is to provide unique names axioms for the actions, which in my case is the move function. I need to state that move will never return the same element of sort Action when it has two different arguments. This could be done in several ways, but I found this the most concise version:
(assert (forall ((o1 Obj) (o2 Obj))
(=> (not (= o1 o2)) (not (= (move o1) (move o2))))))

You are assuming constraints that you did not assert. For example, nothing prevents move to be the constant function.
The model produced by Z3 is correct. You can obtain the model by adding the command (get-model) after (check-sat).
The command (declare-sort Action 0) is declaring a unintepreted sort.
In the model produced by Z3, the interpretation of sort Action contains only one element, and occurs and move are constant functions. This is a model because all assertions in your script are satisfied by it.

Related

Z3 fails to find simple sat solution after quantified bitvector assertion

I noticed that if I create my own array type that stores bitvectors and assert the first array update axiom, simple assertions afterwards fail to find a solution (my example below neither returns sat nor unsat but just keeps running):
(declare-sort MyArray)
; Indices into the array
(declare-sort Id)
; Returns the value in the array located at the specified index
(declare-fun index
(MyArray Id)
(_ BitVec 8))
; Updates the array so that the provided value is stored at the specified index
(declare-fun upd
(MyArray Id (_ BitVec 8))
MyArray)
; First array update axiom
(assert (forall ((a MyArray) (i Id) (v (_ BitVec 8)))
(=
(index (upd a i v) i)
v)))
(declare-const x Int)
(declare-const y Int)
(echo "")
(echo "Sanity check, should be sat:")
(assert (= x y))
(check-sat)
However, if I instead specify that my array stores a custom sort z3 finds a solution very quickly:
(declare-sort MyArray)
; Indices into the array
(declare-sort Id)
; Values stored in the array
(declare-sort Elem)
; Returns the value in the array located at the specified index
(declare-fun index
(MyArray Id)
Elem)
; Updates the array so that the provided value is stored at the specified index
(declare-fun upd
(MyArray Id Elem)
MyArray)
; First array update axiom
(assert (forall ((a MyArray) (i Id) (v Elem))
(=
(index (upd a i v) i)
v)))
(declare-const x Int)
(declare-const y Int)
(echo "")
(echo "Sanity check, should be sat:")
(assert (= x y))
(check-sat)
Does anyone know why this is the case? It's possible that z3 gets caught in some kind of instantiation loop (since the upd function both takes and returns MyArray sort), but I'm surprised that it only seems to get tripped up with bitvectors as the elements. Is this related to Nikolaj's answer that the quantifier elimination tactic is currently fairly simplistic when it comes to bit-vectors?
I'm using bitvectors because my problem ultimately involves some bitvector operations (especially bvxor). Is it better just to define my own operations and essentially recreate part of the theory of bitvectors? Or is there a better way to go about this (than mixing quantifiers, bitvectors, and part of the theory of arrays)? I'm really just interested in operations on bytes so all my bitvectors are of length 8.
I don't think there's a good reason why z3 is not terminating on the first program you gave here. Running with z3 -v:10 suggests it gets into some sort of an unproductive loop, that the second version avoids. I think you should report this at https://github.com/Z3Prover/z3/issues. Even though it's not strictly speaking a "bug," it's surprising behavior and the developers might want to look at it. (Please report back what you find.)
Regarding your second question: Do not reinvent what z3 already has support for! Use the internal arrays; they have a custom decision procedure and it has gone years of tuning. The introduction of quantified axioms will no doubt create more work than is necessary. Does something go wrong if you use internal arrays? Why would you not use them anyhow? Only look at your own axiomatization, if the internal built-in one isn't working well. (Even then, I'd first check with developers to see why.)

difference in encoding of the same axiom

I'm wondering what is the difference between these two encoding of the same list axiom:
(define-sort T1 () Int)
(declare-fun list_length ( (List T1) ) Int)
(assert (forall ( (i T1) (l (List T1)) )
(ite (= l (as nil (List T1)))
(= (list_length l) 0)
(= (list_length (insert i l)) (+ 1 (list_length l))))))
and
(define-sort T1 () Int)
(declare-fun list_length ( (List T1) ) Int)
(assert (= (list_length (as nil (List T1))) 0))
(assert (forall ( (i T1) (l (List T1)) )
(= (list_length (insert i l)) (+ 1 (list_length l)))))
For this benchmark:
(declare-const a T1)
(declare-const b T1)
(assert (not
(= (list_length (insert b (insert a (as nil (List T1))))) 2)))
(check-sat)
Somehow z3 is able to reason about the second version but not the first (where it seems to just loop forever).
Edit: same with cvc4 with the first version returning unknown.
First-order logic with quantifiers is essentially semi-decidable. In the SMT context, this means that there is no decision procedure to answer every query correctly as sat/unsat.
(Theoretical aside, not that it's that important: If you completely ignore efficiency considerations, then there are algorithms that can answer all satisfiable queries correctly, but there are no algorithms that can correctly deduce unsat. In this latter case, they'd loop forever. But this is a digression.)
So, to deal with quantifiers, SMT solvers usually employ a technique known as E-matching. Essentially, when they form a ground term mentioning uninterpreted functions, they try to instantiate quantified axioms to match them and rewrite accordingly. This technique can be quite effective in practice and scales well with typical software verification problems, but it obviously is not a panacea. For details, see this paper: https://pdfs.semanticscholar.org/4eb2/c5e05ab5c53f20c6050f8252a30cc23561be.pdf.
Regarding your question: Essentially, when you have the ite form of the axiom, the e-matching algorithm simply fails to find the proper substitution to instantiate your axiom. For efficiency considerations, the e-matcher really looks at almost "exact" matches. (Take this with a grain of salt; it's smarter than that, but not by much.) Being too smart here hardly ever pays off in practice, since you can end up generating way too many matchings and end up exploding your search space. As usual, it's a balance between practicality, efficiency, and covering as many cases as possible.
Z3 allows specifying patterns to guide that search to a certain extent, but patterns are rather tricky to use and fragile. (I'd have pointed you to the right place in the documentation for patterns, alas the z3 documentation site is down for the time being as you yourself noticed!) You might want to play around with them to see if they give you better results. But the rule of thumb is to keep your quantified axioms as simple and obvious as possible. And your second variant precisely does that, as compared to the first. For this particular problem, definitely split the axiom into two parts, and assert both separately to cover the nil/insert cases. Combining them into one rule simply exceeds the capabilities of the current e-matcher.

How to define enumerated types in SMT-LIB for Z3

I previously used Z3's API to define an enumerated type like so
let T = ctx.MkEnumSort("T", [| "a"; "b"; "c"|])
which enumerates the elements of a type T as being "a" "b" and "c" (and nothing else). However I am now trying to do something similar but via SMT-LIB rather than the API and I am running into a problem of Z3 complaining about quantifiers. The program I am using (Boogie) generates the following smt
...
(declare-sort T#T 0)
(declare-fun a() T#T)
(declare-fun b() T#T)
(declare-fun c() T#T)
(assert (forall ((x T#T) )
(! (or
(= x a)
(= x b)
(= x c)
)
:qid |gen.28:15|
:skolemid |1|
)))
...
The assertion is the type closure axiom that asserts that the type has no other members. But when I send this (along with other stuff) to Z3, after thinking about it a bit, returns
WARNING: relevacy must be enabled to use option CASE_SPLIT=3, 4 or 5
unknown
(:reason-unknown (incomplete quantifiers))
Notes: 1. I have MBQI turned on. 2. Boogie has an option called "z3types" but it doesn't seem to make any difference
What is the SMT-LIB equivalent of the MkEnumSort API call?
thanks
P.S. I have tried with RELEVANCY set to both 1 and 2 and I still get the warning about relevancy (CASE_SPLIT is set to 3)
Use
(declare-datatypes () ((T#T (a) (b) (c)))
There is a tutorial with more details: http://rise4fun.com/z3/tutorialcontent/guide#h27
Use this for SMTLib v2:
(declare-datatypes ((T#T 0)) (((a) (b) (c))))
where T#T is the name of your type, and a, b, c its possible values.

Dynamically call get-value only when check-sat returns "sat"

The SMT2 standard states that calling get-value is only legal after a call to check-sat and only when check-sat returns "sat" or "unknown".
Here is a simple example of an unsat problem:
(set-option :produce-models true)
(set-logic QF_BV)
(set-info :smt-lib-version 2.0)
(declare-fun a ()(_ BitVec 4))
(declare-fun b ()(_ BitVec 4))
(declare-fun c ()(_ BitVec 4))
(declare-fun z ()(_ BitVec 4))
(assert (= #b1111 z))
(assert (= a b))
(assert (= (bvxor a z) c))
(assert (= b c))
(check-sat)
(get-value ( a ))
(get-value ( b ))
(get-value ( c ))
Since the problem is unsat, the get-value commands are illegal. Take out any one assert and it becomes sat and the get-value commands become legal. So my question is, how do you write a SMT2 script that checks the return value of check-sat and only calls get-value if it returned sat?
Illegally calling get-value is an issue for me as I am running different smt solvers in a flow and checking the program's return value and then parsing their output. CVC4 change's its return value to an error state if get-value is called illegally.
I don't think there's a good way, if what you want is to have one "SMT" file to rule the whole transaction.
This issue comes up often in interacting with SMT-solvers from other languages. The solution I adopted is that I keep an open pipe with the solver and feed it the lines of the script, read the responses, and decide on what to send next based on the responses I get. Essentially programmed interaction. (This is what the Haskell SBV library does, for instance.)
However, I do agree that this is a pain; and it would be nice if there was an SMT2-lib sanctioned way of handling such common interaction.
For CVC4 being run from the command line, add the flag
--dump-models output models after every SAT/INVALID/UNKNOWN
response [*]
This is not as specific as get-value. This option is non-standard and CVC4 currently does not support setting this flag from the SMT2 parser. (Let us know if you would like this to be supported.)

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