4

I'm playing around with Z3's QBVF solver, and wondering if it's possible to extract values from an existential assertion. To wit, let's say I have the following:

(assert (exists ((x (_ BitVec 16))) (forall ((y (_ BitVec 16))) (bvuge y x))))

This basically says that there is a "least" 16-bit unsigned value. Then, I can say:

(check-sat)
(get-model)

And Z3-3.0 happily responds:

sat
(model  (define-fun x!0 () (_ BitVec 16)
#x0000)
)

Which is really cool. But what I want to do is to be able to extract pieces of that model via get-value. Unsurprisingly, none of the following seem to work

(get-value (x))
(get-value (x!0))

In each case Z3 rightly complains there's no such constant. Clearly Z3 has that information as evidenced by the response to the (check-sat) call. Is there any way to access the existential value automatically via get-value, or some other mechanism?

Thanks..

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445

1 Answers1

4

In Z3, get-value only allows the user to reference “global” declarations. The existential variable x is a local declaration. Thus, it can’t be accessed using get-value. By default, Z3 eliminates existential variables using a process called “skolemization”. The idea is to replace existential variables with fresh constants and function symbols. For example, the formula

exists x. forall y. exists z. P(x, y, z)

is converted into

forall y. P(x!1, y, z!1(y))

Note that z becomes a function because the choice of z may depend on y. Wikipedia has an entry on skolem normal form

That being said, I never found a satisfactory solution for the problem you described. For example, a formula may have many different existential variables with the same name. So, it is not clear how to reference each instance in the get-value command in a non-ambiguous way.

A possible workaround for this limitation is to apply the skolemization step “by hand”, or at least for the variables you want to know the value. For example,

(assert (exists ((x (_ BitVec 16))) (forall ((y (_ BitVec 16))) (bvuge y x))))

is written as:

(declare-const x (_ BitVec 16))
(assert (forall ((y (_ BitVec 16))) (bvuge y x)))
(check-sat)
(get-value x)

If the existential variable is nested in a universal quantifier such as:

(assert (forall ((y (_ BitVec 16))) (exists ((x (_ BitVec 16))) (bvuge y x))))
(check-sat)
(get-model)

A fresh skolem function can be used to obtain the value of x for each y. The example above becomes:

(declare-fun sx ((_ BitVec 16)) (_ BitVec 16))
(assert (forall ((y (_ BitVec 16))) (bvuge y (sx y))))
(check-sat)
(get-model)

In this example, sx is the fresh function. The model, produced by Z3, will assign an interpretation for sx. In version 3.0, the interpretation is the identity function. This function can be used to obtain the value of x for each y.

Leonardo de Moura
  • 21,065
  • 2
  • 47
  • 53
  • 1
    Thanks Leonardo.. That's especially a nice trick if existentials precede universals, in which case skolemization is trivial. In the general case of nested/alternating quantifiers, a better solution might be for the user to "mark" them in some explicit way, similar to hints in ACL2. It would be upto the user to make sure the nodes are marked uniquely, I suppose. Something like (pseudo SMT-Lib2 syntax): `exists ((x (_ BitVec 16) :model_name "x")) ...` This might break pure SMT-Lib compatibility, but it might be a good compromise given how Z3 is pushing the boundaries anyhow. – Z3-Experimenter Aug 24 '11 at 21:29
  • 1
    Thanks for the suggestion. I will consider the possibility for future versions of Z3. However, the user will not have control over the signature of the skolem function symbol generated by Z3. Z3 performs many simplifications before skolemization, and the skolemization step tries to minimize the number of dependencies on universal variables. I updated my answer with an example on how to extract an existential variable nested in a universal quantifier. – Leonardo de Moura Aug 24 '11 at 23:49