"Intersection" of two Skolem functions is not as expected in Z3(Py) - z3

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?

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?

Related

A Skolem model in Z3 should be unique, but is printing several (and repeated)

I am testing how similar are "assignment"-like models and "Skolem-function"-like models in Z3.
Thus, I proposed an experiment: I will create a formula for which the unique model is y=2; and try to "imitate" this formula so that the (unique) model is a Skolem function f(x)=2. I did this by using ExistsForall quantification for the y=2 case and ForallExists quantification for the f(x)=2 case.
Thus, I first performed the following (note that the y is existentially quantified from the top-level declaration):
from z3 import *
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())
for i in range(0, 5):
if s.check() == sat:
m = s.model()[y]
print(m)
s.add(And(y != m))
This code successfully prints out y=2 as a unique model (no matter we asked for 5 more). Now, I tried the same for f(x)=2 (note that there is no y):
skolem = Function('skolem', IntSort(), IntSort())
x = Int('x')
ct0 = (x >= 2)
ct1 = (skolem(x) > 1)
ct2 = (skolem(x) <= x)
phi1 = ForAll([x], Implies(ct0, And(ct1,ct2)))
s = Solver()
s.add(phi1)
for i in range(0, 5):
if s.check() == sat:
m = s.model()
print(m)
s.add(skolem(x) != i)
This prints:
[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)]]
My question is: why is the y=2 unique, whereas we get several Skolem functions? In Skolem functions, we get (and repeatedly) some functions in which the antecedent of phi1 (i.e., (x >= 2)) is negated (e.g., x=0); but in models, we do not get stuff like x=0 implies y=1, we only get y=2 because that is the unique model that does not depend on x. In the same way, [skolem = [else -> 2]] should be the unique "Skolem model" that does not depend on x.
There's a fundamental difference between these two queries. In the first one, you're looking for a single y that acts as the value that satisfies the property. And indeed y == 2 is the only choice.
But when you have a skolem function, you have an infinite number of witnesses. The very first one is:
skolem(x) = 2
i.e., the function that maps everything to 2. (You're internally equating this to the model y=2 in the first problem, but that's misleading.)
But there are other functions too. Here's the second one:
skolem(x) = if 2 <= x then 2 else 1
You can convince yourself this is perfectly fine, since it does give you the skolem function that provides a valid value for y (i.e., 2), when the consequent matters. What it returns in the else case is immaterial. (i.e., when x < 2). And similarly, you can simply do different things when x < 2, giving you an infinite number of skolem functions that work. (Of course, the difference is not interesting, but different nonetheless.)
What you really are trying to say, I guess, is there's nothing "else" that's interesting. Unfortunately that's harder to automate, since it's hard to get a Python function back from a z3 model. But you can do it manually:
from z3 import *
skolem = Function('skolem', IntSort(), IntSort())
x = Int('x')
ct0 = (x >= 2)
ct1 = (skolem(x) > 1)
ct2 = (skolem(x) <= x)
phi1 = ForAll([x], Implies(ct0, And(ct1,ct2)))
s = Solver()
s.add(phi1)
print(s.check())
print(s.model())
# The above gives you the model [else -> 2], i.e., the function that maps everything to 2.
# Let's add a constraint that says we want something "different" in the interesting case of "x >= 2":
s.add(ForAll([x], Implies(x >= 2, skolem(x) != 2)))
print(s.check())
This prints:
sat
[skolem = [else -> 2]]
unsat
which attests to the uniqueness of the skolem-function in the "interesting" case.

How to output more than one Skolem functions in Z3(Py)

I am playing with Skolem functions in Z3-Py. In the following lines, I describe the Skolem function that satisfies the formula Forall x. Exists y. (x>=2) --> (y>1) /\ (y<=x) interpreted over integers:
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())
Which I understand that it outputs the unique 'model' possible: y=2 (i.e., f(x)=2). It is printed as follows:
sat
[skolem = [else -> 2]]
I have a preliminary question: why does the function use an else? I guess it comes from the fact that "it is only activated" if the antecedent (x>=2) holds. But which is the co-domain of the function if it is not the else branch (output 2) the one activated, but the if branch?
But now it comes my actual question. I solved the same formula, but interpreted over reals:
x = Real('x')
skolem = Function('skolem', RealSort(), RealSort())
ct_0 = (x >= 2.0)
ct_1 = (skolem(x) > 1.0)
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())
And this outputs... the same Skolem function! Is this right?
In case it is, it is still curious, since it could have chosen many others; e.g., y=1.5. Then, I would like to ask for another Skolem function to Z3, so I proceed as if I was asking for 5 new models:
phi = ForAll([x], Implies(ct_0, And(ct_1,ct_2)))
s = Solver()
s.add(phi)
for i in range(0,5):
if s.check() == sat:
m = s.model()
print(m)
s.add(Not(m))
But I get the following error: Z3Exception: True, False or Z3 Boolean expression expected. Received [skolem = [else -> 2]] of type <class 'z3.z3.ModelRef'>. I mean, I see Z3 expects Boolean for the negation (which is obvious hehe), so how can I encode the fact that "I add the negation of this Skolem function into the formula", i.e., that I want to find another Skolem function?
In your loop, you're trying to add the negation of your model m; but that's not what Not(m) means. In fact, Not(m) is meaningless: Not operates on booleans, not models; which is the bizarre error you're getting.
To do what you're trying to achieve; simply assert that you want a different skolem function:
from z3 import *
x = Real('x')
skolem = Function('skolem', RealSort(), RealSort())
ct_0 = (x >= 2)
ct_1 = (skolem(x) > 1)
ct_2 = (skolem(x) <= 2)
phi = ForAll([x], Implies(ct_0, And(ct_1,ct_2)))
s = Solver()
s.add(phi)
for i in range(0, 5):
if s.check() == sat:
m = s.model()
print(m)
s.add(skolem(x) != i)
This prints:
[skolem = [else -> 2]]
[x = 0, skolem = [else -> If(2 <= Var(0), 2, 1/2)]]
[x = 0, skolem = [else -> If(2 <= Var(0), 2, 1/2)]]
[x = 0, skolem = [else -> If(2 <= Var(0), 3/2, 1/2)]]
[x = 0, skolem = [else -> If(2 <= Var(0), 3/2, 1/2)]]
Now this is a little hard to read, but I think you can figure it out. A function model is simply a finite-mapping with an else clause, which is what a finite function is. The Var call refers to its argument. I haven't looked at the definitions z3 provides in detail to make sure they're good, but I have no reason to suspect why not. See if you can work this out for yourself; it's a valuable exercise! Feel free to ask further questions if anything isn't clear.

Why is Z3 giving me unsat for the following formula?

I have the following formula and Python code trying to find the largest n satisfying some property P:
x, u, n, n2 = Ints('x u n n2')
def P(u):
return Implies(And(2 <= x, x <= u), And(x >= 1, x <= 10))
nIsLargest = ForAll(n2, Implies(P(n2), n2 <= n))
exp = ForAll(x, And(P(n), nIsLargest))
s = SolverFor("LIA")
s.reset()
s.add(exp)
print(s.check())
if s.check() == sat:
print(s.model())
My expectation was that it would return n=10, yet Z3 returns unsat. What am I missing?
You're using the optimization API incorrectly; and your question is a bit confusing since your predicate P has a free variable x: Obviously, the value that maximizes it will depend on both x and u.
Here's a simpler example that can get you started, showing how to use the API correctly:
from z3 import *
def P(x):
return And(x >= 1, x <= 10)
n = Int('n')
opt = Optimize()
opt.add(P(n))
maxN = opt.maximize(n)
r = opt.check()
print(r)
if r == sat:
print("maxN =", maxN.value())
This prints:
sat
maxN = 10
Hopefully you can take this example and extend it your use case.

Nested loop and functional programming

Please consider a C program that, given x, will return y and z such that y + z * 2 = x, for the smallest possible y. Roughly, I could create a nested loop:
for(y = 0; y < x; ++ y){
for(z = 0; z < x; ++z){
if(y + 2 * z == x){
printf("%d + 2 * %d = %d", y, z, x);
}
}
}
How could I translate this kind of nested loop in the functional way? Is it feasible? Is it reasonable or am I just misjudging the approach? My best attempt so far:
let foo x =
let rec aux (y, z, q) =
match (y + z * 2) with
r when r = q -> (y, z)
|_ -> aux(y + 1, z + 1, q) //How to check different values of z
aux(0, 0, x) //for each value of y?
It will not work, since it will just increment both y and z. How can I check different values of z, for every value of y?
You have to add those checks in the match.
See here what your code is missing:
let foo x =
let rec aux (y, z, q) =
match (y + z * 2) with
| r when r = q -> (y, z)
| _ when y = q -> failwith "not found !"
| _ when z = q -> aux (y + 1, 0, q)
| _ -> aux (y, z + 1, q)
aux (0, 0, x)
And here's a different approach, equally functional but without recursion:
let foo2 x =
let s =
{0 .. x} |> Seq.collect (fun y ->
{0 .. x} |> Seq.collect (fun z ->
seq [y, z]))
Seq.find (fun (y, z) -> y + z * 2 = x) s
which in F# can be written using seq expressions:
let foo3 x =
let s = seq {
for y in {0 .. x} do
for z in {0 .. x} do
yield (y, z)}
Seq.find (fun (y, z) -> y + z * 2 = x) s
and it resembles your original C program.

Some proofs of validity using Z3Py online and a strategy proposed by Nikolaj Bjorner

Lemma: forall x : R, x <> 0 -> (x / x) = 1.
Proof:
x = Real('x')
s = Solver()
s.add(Or(x >0, x < 0), Not(x/x ==1))
print s.check()
and the output is :
unsat
Qed.
Lemma: forall x y : R, x <> 0, y <> 0 -> (x / x + y / y) = 2.
Proof:
x, y = Reals('x y')
s = Solver()
s.add(Or(x >0, x < 0), Or(y >0, y < 0), Not(x/x + y/y ==2))
print s.check()
and the output is:
unsat
Qed.
Lemma: forall x y : R, x <> 0, y <> 0 -> (x / x + x / y) = ((x + y) / y).
Proof:
x, y = Reals('x y')
s = Solver()
s.add(Or(x >0, x < 0), Or(y >0, y < 0), Not(x/x + x/y == (x+y)/y))
print s.check()
and the output is:
unsat
Qed.
These lemmas were proved using Coq + Maple at
http://coq.inria.fr/V8.2pl1/contribs/MapleMode.Examples.html
Please let me know if my proofs with Z3Py are correct and if you know a more direct form to prove them using Z3Py. Many thanks.
There is a slightly more compact way by using the "prove" command instead of the solver object.
For example:
x, y = Reals('x y')
prove(Implies(And(Or(x >0, x < 0), Or(y >0, y < 0)), (x/x + x/y == (x+y)/y)))

Resources