Why does smtlib/z3/cvc4 allow to declare the same constant more than once? - z3

I have a question about declare-const in smtlib.
For example,
In z3/cvc4, the following program doesn't report an error:
C:\Users\Chansey>z3 -in
(declare-const x Int)
(declare-const x Bool)
In the smt-lib-reference, it says that
(declare-fun f (s1 ... sn) s) ... The command reports an error if a function symbol with name f is already present in the current signature.
So the sort s is included in the entire signature of the x, is that right?
But why is it so? What is the motivation behind it?
In my understanding, the x is variable identifier and in general (e.g. in some general programming languages) we are not allowed to declare the same variable with different types. So I think the above code is best to report an error.
I once thought that perhaps z3/smtlib can support redefinition?, but not...
C:\Users\Chansey>z3 -in
(declare-const x Int)
(declare-const x Bool)
(assert (= x true))
(error "line 3 column 11: ambiguous constant reference, more than one constant with the same sort, use a qualified expre
ssion (as <symbol> <sort>) to disambiguate x")
So the above code is definitely wrong, why not report the error earlier?
PS. If I use the same sort, then it will report an error (that great, I hope the Bool case can also report the error):
C:\Users\Chansey>z3 -in
(declare-fun x () Int)
(declare-fun x () Int)
(error "line 2 column 21: invalid declaration, constant 'x' (with the given signature) already declared")
Thanks.

In SMTLib, a symbol is identified not just by its name, but also by its sort. And it's perfectly fine to use the same name, so long as you have a different sort, as you observed. Here's an example:
(set-logic ALL)
(set-option :produce-models true)
(declare-fun x () Int)
(declare-fun x () Bool)
(assert (= (as x Int) 4))
(assert (= (as x Bool) true))
(check-sat)
(get-model)
(get-value ((as x Int)))
(get-value ((as x Bool)))
This prints:
sat
(
(define-fun x () Bool
true)
(define-fun x () Int
4)
)
(((as x Int) 4))
(((as x Bool) true))
Note how we use the as construct to disambiguate between the two x's. This is explained in Section 3.6.4 of http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2021-05-12.pdf
Having said that, I do agree the part of the document you quoted isn't very clear about this, and perhaps can use a bit of a clarifying text.
Regarding what the motivation is for allowing this sort of usage: There are two main reasons. The first is o simplify generating SMTLib. Note that SMTLib is usually not intended to be hand-written. It's often generated from a higher-level system that uses an SMT solver underneath. So being flexible in allowing symbols to share name so long as they can be distinguished by explicit sort annotations can be beneficial when you use SMTLib as an intermediate language between a higher-level system and the solver itself. But when you're writing SMTLib by hand, you should probably avoid this sort of duplication if you can, for clarity if nothing else.
The second reason is to allow for a limited form of "overloading" to be used. For instance, think about the SMTLib function distinct. This function can operate on any type of object (Int, Bool, Real etc.), yet it's always called distinct. (We don't have distinct-int, distinct-bool etc.) The solver "distinguishes" which one you meant by doing a bit of an analysis, but in cases it cannot, you can help it along with an as declaration as well. So, theory symbols can be overloaded in this way, which is also the case for =, +, *, etc. Of course, SMTLib does not allow for users to define such overloaded names, but as the document states in footnote 29 on page 91, this restriction might be removed in the future.

Related

Confused about a simple SAT matching problem using Z3

I am trying to solve a simple matching problem using Z3 which it claims is unsat. I have set this up the following way:
p_{x}_{y} to match up offer x with request y.
sum_{x} {p_x_y} <= 1 meaning y can only be matched once (using PbLe)
sum_{y} {p_x_y} <= 1 meaning x can only be matched once (using PbLe)
whether p_x_y is a valid match comes from an external computation that returns a Bool, so for this I have p_x_y => computation_result (i.e. if paired, then computation_result).
finally, I want to maximize the number of matchings. So I have:
maximize sum_{x} ( sum_{y} p_x_y ) (I do this with p_x_y.ite(Int(1), Int(0))).
I was able to whip this up quite quickly using z3-rs in Rust (not sure if that makes a difference). And this is the solver state before I run check on it:
Solver: (declare-fun p_0_0 () Bool)
(declare-fun p_1_0 () Bool)
(declare-fun k!0 () Int)
(declare-fun k!1 () Int)
(assert (=> p_0_0 true))
(assert (=> p_1_0 true))
(assert ((_ at-most 1) p_0_0))
(assert ((_ at-most 1) p_1_0))
(assert ((_ at-most 1) p_0_0 p_1_0))
(maximize (+ (ite p_1_0 k!1 k!0) (ite p_0_0 k!1 k!0)))
(check-sat)
Z3 claims this is Unsat and I am quite stumped. I don't see why p_0_0 = T, p_1_0 = F doesn't satisfy this formula.
Thank you very much for the help.
I can't replicate this. When I run your program, z3 prints: (after adding (get-model) at the end)
sat
(
(define-fun p_0_0 () Bool
true)
(define-fun p_1_0 () Bool
false)
(define-fun k!1 () Int
0)
(define-fun k!0 () Int
(- 1))
)
which matches your expectations.
Couple of things to make sure:
Is your z3 version "new" enough? 4.11.3 is the latest master I think
You mentioned you use it from Rust. Perhaps you didn't use the rust-API correctly? Or, maybe Rust interface has a bug.
I'd start by running it manually on your machine using the SMTLib script you've given. If you get SAT (which you should!), perhaps ask at the Rust forum as the bug is likely either in your Rust program or the Rust bindings itself. If you get UNSAT, try upgrading your z3 installation and possibly recompile the Rust bindings if that's relevant. (I'm not familiar with the Rust bindings to say if it needs a recompile or not if you upgrade your z3. It could be either way.)
A guess
Without seeing the details, it's hard to opine further. However, notice that you've posed this as an optimization problem; and asked z3 to maximize the addition of two uninterpreted integers. So, it's possible the Rust bindings are adding a call of the form:
(get-objectives)
at the end, to which z3 will respond:
sat
(objectives
((+ (ite p_1_0 k!1 k!0) (ite p_0_0 k!1 k!0)) oo)
)
That is, the objective you're maximizing is unbounded. This means there's no value for k!0 and k!1 the solver can present to you: The goal gets arbitrarily large as these get larger. It's possible the Rust interface is internally treating this as "unsat" since it cannot find the values for these constants. But that's just my guess without knowing the specifics of how the Rust bindings work.

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.)

z3 with LIA logic can return real?

I am experimenting with z3. I am surprised that, when setting the logic to LIA, z3 can return real numbers. For example, the following:
from z3 import *
sol = SolverFor("LIA")
vB = Real('vB')
sol.add(vB < 3)
sol.add(vB > 2)
sol.check()
print(sol.model())
returns:
[vB = 5/2]
Can someone please explain how it can be the case?
Strictly speaking, this is a "bug" in z3. Here's the equivalent SMT script:
(set-logic LIA)
(declare-fun vB () Real)
(assert (< vB 3.0))
(assert (> vB 2.0))
(check-sat)
(get-model)
If you run this with cvc4/cvc5, you get:
(error "Parse Error: a.smt2:2.23: Symbol 'Real' not declared as a type
(declare-fun vB () Real)
^
")
And Yices says:
(error "at line 2, column 20: undefined sort: Real")
CVC and Yices are correct here, since you restricted the logic to "LIA", the name "Real" is no longer defined. Z3, however, is playing loose here, and allowing reals by default. (It internally uses logics to pick which algorithms to run, but it's not always consistent in making sure the logic restricts the names available.)
You can report this at the z3 issue tracker here: https://github.com/Z3Prover/z3/issues. I suspect they're already aware of this, but would be good to have it recorded.

Defining Rules for Bit Vectors in SMT2

I have switched from using Int to Bit Vectors in SMT. However, the logic QF_BV does not allow the use of any quantifiers in your script, and I need to define FOL rules.
I know how to eliminate existential quantifiers, but universal quantifiers? How to do that?
Imagine a code like that:
(set-logic QF_AUFBV)
(define-sort Index () (_ BitVec 3))
(declare-fun P (Index) Bool)
(assert (forall ((i Index)) (= (P (bvadd i #b001)) (not (P i)) ) ) )
Strictly speaking, you're out-of-luck. According to http://smtlib.cs.uiowa.edu/logics.shtml, there's no logic that contains quantifiers and bit-vectors at the same time.
Having said that, most solvers will allow non-standard combinations. Simply leave out the set-logic command, and you might get lucky. For instance, Z3 takes your query just fine without the set-logic part; I just tried..

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.

Resources