0

Note the following Z3-Py code:

x, y = Ints('x y')

negS0= (x >= 2)
s1 = (y > 1)
s2 = (y <= x)

s = Solver()
phi = Exists([y],ForAll([x], Implies(negS0, And(s1,s2))))
s.add(phi)
print(s.check())
print(s.model())

This prints:

sat
[]

My question is: why is the model empty? I mean, I think y=2 should be a model...

Note that the same result happens with x and y being Real.

Theo Deep
  • 666
  • 4
  • 15

1 Answers1

1

z3 will not include any quantified variable (in your case neither y nor x) in its model. Note that you cannot put x in a model anyhow, because the formula is true for all x: That's the meaning of universal quantification. For the outer-most existentials (like your y), z3 can indeed print the model value for that, but it chooses not to do so since it can be confusing: Imagine you had a phi2, which also had an outer-most existential named y: How would you know which y it would be that it prints in the model?

So, z3 keeps things simple and simply prints the top-level declared variables in the model. And since a top-level declaration is equivalent to an outermost existential, you can simply drop it:

from z3 import *

x, y = Ints('x y')

negS0= (x >= 2)
s1 = (y > 1)
s2 = (y <= x)

s = Solver()
phi = ForAll([x], Implies(negS0, And(s1,s2)))
s.add(phi)
print(s.check())
print(s.model())

This prints:

sat
[y = 2]

like you predicted. Note that this y is unambiguous, since it's declared at the top-level. (Of course, you can redefine it to be something else due to the loosely typed nature of Python bindings and still get yourself confused, but that's a different discussion.)

alias
  • 28,120
  • 2
  • 23
  • 40
  • This is a perfect answer, thanks as always! Just in case someone does not understand the answer, allow me to copy your reply in (https://stackoverflow.com/questions/75082515/in-z3-i-cannot-understand-result-of-quantifier-elimination-of-exists-y-forall/), which I think that really clarifies this. "An outer-most existentially quantified variable in a formula, and a top-level declaration are equivalent. `(Exists([x], ...)`, vs. `x = Int('x'))`. The only difference is that you'll get a value in the models with the latter, and not for the former. So, always prefer the latter." – Theo Deep Jan 12 '23 at 18:00
  • 1
    To be precise: All your `s.add(phi)` formulas are conjuncted, and then closed over with an `exists` declaration for every top-level variable. This is the meaning of a "formula" as you build the solver. Note that, confusingly, for each quantified variable, you also have to declare it at the top-level as well, but this is a mere technicality. I wish they didn't do that, but I also see that it's a simple solution to the classic "binder" problem: In a more typed embedding, one can use a HOAS (higher-order-abstract-syntax) to capture such variables. But Python isn't that nice. – alias Jan 13 '23 at 02:43