0

Z3(Py) does not “intersect” Skolem functions as I expected.

I will try to explain my doubt using an imaginary problem which is the one that I have been testing on.

Consider a system that has two processes A and B, and an environment chooses between A or B. When process A is “activated”, then the system must solve P1: Exists y. Forall x. (x>=2) --> (y>1) /\ (y<=x). When the process B is “activated” then the system must solve P2: Exists y. Forall x. (x<2) --> (y>1) /\ (y>x). We can see, in Z3-PY, models of both the P1 and the P2 formulae:

#P1

x,y = Ints('x y')

ct_0 = (x >= 2)
ct_1 = (y > 1)
ct_2 = (y <= x)

phi = ForAll([x], Implies(ct_0, And(ct_1,ct_2)))

s = Solver()
s.add(phi)
print(s.check())
print(s.model())

#P2
x,y = Ints('x y')

ct_0 = (x < 2)
ct_1 = (y > 1)
ct_2 = (y > x)

phi = ForAll([x], Implies(ct_0, And(ct_1,ct_2)))

s = Solver()
s.add(phi)
print(s.check())
print(s.model())

Note that the model of P1 is unique: y=2; whereas a model of P2 is y=2, but also y=3, y=4, y=5, ... ad infinitum.

Then, I consider that the "intersection" of both P1 and P2 is the model y=2. In other words, whenever the environment chooses A or B, i.e., whenever the environment forces the system to solve P1 or P2, the system can just respond y=2 and will satisfy the formula, no matter what the environment chose.

I obtained this (unique) “intersection” y=2 by performing the (unique) model of the conjunction of both formulae:

#P1 and P2 intersection
#Note that, this time, I distinguish between ct_k and ctk because I executed them in the same script, no other reason involved.

x,y = Ints('x y')

ct_0 = (x >= 2)
ct_1 = (y > 1)
ct_2 = (y <= x)

phi0 = ForAll([x], Implies(ct_0, And(ct_1,ct_2)))

ct0 = (x < 2)
ct1 = (y > 1)
ct2 = (y > x)

phi1 = ForAll([x], Implies(ct0, And(ct1,ct2)))

phiT = And(phi0,phi1)

s = Solver()
s.add(phiT)
print(s.check())
print(s.model())

for i in range(0, 5):
  if s.check() == sat:
    m = s.model()[y]
    print(m)
    s.add(And(y != m))

This outputs [y = 2] as expected.

Now, consider I have a problem: P1 and P2 are no longer Exists-Forall formulae, but Forall-Exists formulae: this means that we will not have models of y, but Skolem functions (as solved in this question What does a model mean in a universally quantified formula? Is it a function?). I implement that as follows:

#P1 Skolem

x = Int('x')

skolem = Function('skolem', IntSort(), IntSort())

ct_0 = (x >= 2)
ct_1 = (skolem(x) > 1)
ct_2 = (skolem(x) <= x)

phi = ForAll([x], Implies(ct_0, And(ct_1,ct_2)))

s = Solver()
s.add(phi)
print(s.check())
print(s.model())

#P2 Skolem

x = Real('x')

skolem = Function('skolem', RealSort(), RealSort())

ct_0 = (x < 2)
ct_1 = (skolem(x) > 1)
ct_2 = (skolem(x) > x)

phi = ForAll([x], Implies(ct_0, And(ct_1,ct_2)))

s = Solver()
s.add(phi)

As we can see, both of them return [skolem = [else -> 2]] as the first option, and then they offer other (different) options, such as [x = 0, skolem = [else -> If(1 <= Var(0), 5, 2)]] for P2.

My question is: how can I perform the “intersection” of these two formulae? In general, how can I perform intersection of n skolem functions (in Z3)? I tried as follows (using the same idea as before when seeking y=2:

#P1 and P2 Skolem intersection
#Once again, I distinguish ct_k and ctk for execution reasons.

x,y = Ints('x y')

ct_0 = (x >= 2)
ct_1 = (skolem(x) > 1)
ct_2 = (skolem(x) <= x)

phi0 = ForAll([x], Implies(ct_0, And(ct_1,ct_2)))

ct0 = (x < 2)
ct1 = (skolem(x) > 1)
ct2 = (skolem(x) > x)

phi1 = ForAll([x], Implies(ct0, And(ct1,ct2)))

phiT = And(phi0,phi1)

s = Solver()
s.add(phiT)
print(s.check())
print(s.model())

for i in range(0, 5):
  if s.check() == sat:
    m = s.model()
    print(m)
    s.add(skolem(x) != i)

And results were as follows:

[skolem = [else -> 3/2]]
[skolem = [else -> 3/2]]
[x = 0, skolem = [else -> 2]]
[x = 0, skolem = [else -> 2]]
[x = 0, skolem = [else -> 3/2]]
[x = 0, skolem = [else -> 3/2]]

What does this mean? Why does it not output just [skolem = [else -> 2]]? In mean, in the same way that y=2 was the model with the other quantifier alternation.

Also, how does it offer 3/2 if we are in the domain of integers?

PS: In the Forall-Exists version of P1, if we print several Skolem functions, then the result is as follows:

[skolem = [else -> 2]]
[skolem = [else -> 2]]
[x = 0, skolem = [else -> If(2 <= Var(0), 2, 1)]]
[x = 0, skolem = [else -> If(2 <= Var(0), 2, -1)]]
[x = 0, skolem = [else -> If(2 <= Var(0), 2, -1)]]
[x = 0, skolem = [else -> If(2 <= Var(0), 2, -1)]]

How is this so?

Theo Deep
  • 666
  • 4
  • 15

1 Answers1

1

In one of your programs, you have the declaration:

skolem = Function('skolem', RealSort(), RealSort())

I think you confused yourself by somehow leaving this in? It's hard to tell when you don't post code segments that are fully loadable by themselves. In any case, the following:

from z3 import *

skolem = Function('skolem', IntSort(), IntSort())

#P1 and P2 Skolem intersection
#Once again, I distinguish ct_k and ctk for execution reasons.

x,y = Ints('x y')

ct_0 = (x >= 2)
ct_1 = (skolem(x) > 1)
ct_2 = (skolem(x) <= x)

phi0 = ForAll([x], Implies(ct_0, And(ct_1,ct_2)))

ct0 = (x < 2)
ct1 = (skolem(x) > 1)
ct2 = (skolem(x) > x)

phi1 = ForAll([x], Implies(ct0, And(ct1,ct2)))

phiT = And(phi0,phi1)

s = Solver()
s.add(phiT)

for i in range(0, 5):
  if s.check() == sat:
    m = s.model()
    print(m)
    s.add(skolem(x) != i)

Prints:

[skolem = [else -> 2]]
[x = 0,
 skolem = [else ->
           If(And(1 <= Var(0), 2 <= Var(0)),
              2,
              If(1 <= Var(0), 4, 2))]]
[x = 0,
 skolem = [else ->
           If(And(1 <= Var(0), 2 <= Var(0)),
              2,
              If(1 <= Var(0), 4, 2))]]
[x = 0,
 skolem = [else ->
           If(And(1 <= Var(0), 2 <= Var(0)),
              2,
              If(1 <= Var(0), 5, 3))]]
[x = 0,
 skolem = [else ->
           If(And(1 <= Var(0), 2 <= Var(0)),
              2,
              If(1 <= Var(0), 6, 4))]]

which looks fine to me. Does this help resolve your issue?

alias
  • 28,120
  • 2
  • 23
  • 40
  • Oh, I see I left that `skolem = Function('skolem', RealSort(), RealSort())` and, indeed, also declared one of the `x` as `Real` (note `P2`). That was totally my fault. That solves my problem of why I was getting rational responses. – Theo Deep Jan 25 '23 at 17:42
  • Now, as for the output you received (which I also checked in my machine and is not the same, but similar), I still have the same question: why is not `[skolem = [else -> 2]]` the unique answer in `Forall*Exists*`, in the same way that `y=2` is the unique answer in `Exists*Forall*`? Maybe I am getting confused in a silly way... but the key of this "experiment" I was doing was to prove that the "model" with one quantifier alternation would be "semantically similar" to the "Skolem model" with the other quantifier alternation... (Cont.) – Theo Deep Jan 25 '23 at 17:43
  • (Cont.) My idea was to perform "Skolem function intersection" but I see my problem comes earlier: with `P1` in `Forall*Exists*`. I thought the only response should be `[skolem = [else -> 2]]`, but it turns out to be (as shown in the PS of the question) `[skolem = [else -> 2]]`, `[skolem = [else -> 2]]`, `[x = 0, skolem = [else -> If(2 <= Var(0), 2, 1)]]`, `[x = 0, skolem = [else -> If(2 <= Var(0), 2, -1)]]`, etc. So I make a more concrete question: why if the model of `P1` with `EA` is (unique) `y=2`, then (Skolem) model of `P1` with `AE` is not (unique) `[skolem = [else -> 2]]`? (Cn.) – Theo Deep Jan 25 '23 at 17:47
  • (Cn.) Note that I call `AE` to `Forall*Exists*` and `EA` to `Exists*Forall*`. – Theo Deep Jan 25 '23 at 17:50
  • 1
    Please ask a "single" question, separately. Comments are really not suitable for this sort of discussion. I understand your desire to ask many questions at once, but it's best to simply focus on one problem at a time on stack-overflow. – alias Jan 25 '23 at 18:20
  • Gotcha, I just made it and tried to be simple. Thanks again! https://stackoverflow.com/questions/75240191/a-skolem-model-in-z3-should-be-unique-but-is-printing-several-and-repeated – Theo Deep Jan 25 '23 at 21:44