Search code examples
variablesz3

Unique variable names in Z3


I need to rely on the fact that two Z3 variables can not have the same name. To be sure of that, I've used tuple_example1() from test_capi.c in z3/examples/c and changed the original code from:

// some code before that ...
x    = mk_real_var(ctx, "x");
y    = mk_real_var(ctx, "y"); // originally y is called "y"
// some code after that ...

to:

// some code before that ...
x    = mk_real_var(ctx, "x");
y    = mk_real_var(ctx, "x"); // but now both x and y are called "x"
// some code after that ...

And (as expected) the output changed from:

tuple_example1
tuple_sort: (real, real)
prove: get_x(mk_pair(x, y)) = 1 implies x = 1
valid
disprove: get_x(mk_pair(x, y)) = 1 implies y = 1
invalid
counterexample:
y -> 0.0
x -> 1.0

to:

tuple_example1
tuple_sort: (real, real)
prove: get_x(mk_pair(x, y)) = 1 implies x = 1
valid
disprove: get_x(mk_pair(x, y)) = 1 implies y = 1
valid
BUG: unexpected result.

However, when I looked closer, I found out that Z3 did not really fail or anything, it is just a naive (driver) print out to console. So I went ahead and wrote the exact same test with y being an int sort called "x". To my surprise, Z3 could handle two variables with the same name when they have different sorts:

tuple_example1
tuple_sort: (real, real)
prove: get_x(mk_pair(x, y)) = 1 implies x = 1
valid
disprove: get_x(mk_pair(x, y)) = 1 implies y = 1
invalid
counterexample:
x -> 1.0
x -> 0

Is that really what's going on? or is it just a coincidence?? Any help is very much appreciated, thanks!


Solution

  • In general, SMT-Lib does allow repeated variable names, so long as they have different sorts. See page 27 of the standard. In particular, it says:

    Concretely, a variable can be any symbol, while a function symbol can be any identifier (i.e., a symbol or an indexed symbol). As a consequence, contextual information is needed during parsing to know whether an identifier is to be treated as a variable or a function symbol. For variables, this information is provided by the three binders which are the only mechanism to introduce variables. Function symbols, in contrast, are predefined, as explained later. Recall that every function symbol f is separately associated with one or more ranks, each specifying the sorts of f’s arguments and result. To simplify sort checking, a function symbol in a term can be annotated with one of its result sorts σ. Such an annotated function symbol is a qualified identifier of the form (as f σ).

    Also on page 31 of the same document, it further clarifies "ambiguity" thusly:

    Except for patterns in match expressions, every occurrence of an ambiguous function symbol f in a term must occur as a qualified identifier of the form (as f σ) where σ is the intended output sort of that occurrence

    So, in SMT-Lib lingo, you'd write like this:

    (declare-fun x () Int)
    (declare-fun x () Real)
    
    (assert (= (as x Real) 2.5))
    (assert (= (as x Int) 2))
    
    (check-sat)
    (get-model)
    

    This produces:

    sat
    (model
      (define-fun x () Int
        2)
      (define-fun x () Real
        (/ 5.0 2.0))
    )
    

    What you are observing in the C-interface is essentially a rendering of the same. Of course, how much "checking" is enforced by the interface is totally solver specific as SMT-Lib says nothing about C API's or API's for other languages. That actually explains the BUG line you see in the output there. At this point, the behavior is entirely solver specific.

    But long story short, SMT-Lib does indeed allow two variables have the same name used so long as they have different sorts.