Horn clauses with multiplication in Z3 - z3

I've just started digging into Z3's fixed point solver and I've cooked up an example that hangs when using multiplication but completes when defining multiplication as a series of additions. As I'm new to working with Horn clauses, there could be something I don't get here. Is there a reason "native" multiplication is so slow whereas multiplication defined as a series of additions produces a satisfying result in a reasonable timeframe? Thanks!
def test_mseq_hangs():
mul = Function('mul', IntSort(), IntSort(), IntSort(), BoolSort())
mc = Function('mc', IntSort(), IntSort(), BoolSort())
n, m, p = Ints('m n p')
fp = Fixedpoint()
fp.declare_var(n,m,p)
fp.register_relation(mc, mul)
fp.fact(mul(m, n, m * n))
fp.rule(mc(m, 1), m <= 1)
fp.rule(mc(m, n), [m > 1 , mc(m-1, p), mul(m, p, n)])
assert fp.query(And(mc(m,n),n < 1)) == unsat
assert fp.query(And(mc(m,n),n < 2)) == sat
assert fp.query(And(mc(m,n),n > 100 )) == sat
assert fp.query(mc(5,120)) == sat
assert fp.query(mc(5,24)) == unsat
def test_mseq():
mul = Function('mul', IntSort(), IntSort(), IntSort(), BoolSort())
add = Function('add', IntSort(), IntSort(), IntSort(), BoolSort())
neg = Function('neg', IntSort(), IntSort(), BoolSort())
mc = Function('mc', IntSort(), IntSort(), BoolSort())
n, m, p, o = Ints('m n p o')
fp = Fixedpoint()
fp.declare_var(n,m,p,o)
fp.register_relation(mc, add, mul, neg)
fp.fact(add(m, n, m + n))
fp.fact(neg(m, -m))
fp.rule(mul(m, n, 0), n == 0)
fp.rule(mul(m, n, m), n == 1)
fp.rule(mul(m, n, o), [n < 0, mul(m,n,p), neg(p,o)])
fp.rule(mul(m, n, o), [n > 1, mul(m,n-1,p), add(m,p,o)])
fp.rule(mc(m, 1), m <= 1)
fp.rule(mc(m, n), [m > 1 , mc(m-1, p), mul(m, p, n)])
assert fp.query(And(mc(m,n),n < 1)) == unsat
assert fp.query(And(mc(m,n),n < 2)) == sat
assert fp.query(And(mc(m,n),n > 100 )) == sat
assert fp.query(mc(5,120)) == sat
assert fp.query(mc(5,24)) == unsat

This isn't very surprising since multiplying variables leads to non-linear arithmetic, while repeated addition leaves it in the linear fragment. Non-linear arithmetic is not decidable, while there are efficient decision procedures (such as Presburger) for the linear fragment.
I'm not entirely sure how the Fixed-Point engine comes into play here, but the above holds true of the general queries; I'm guessing the same reasoning applies.
Having said that, Z3 does have a non-linear arithmetic solver, called nlsat. You might want to give that a try, though I wouldn't hold my breath. See this question on how to trigger it: (check-sat) then (check-sat-using qfnra-nlsat)
NB. I'm not sure if it's possible to use the nlsat engine from the FixedPoint engine via Python though, so you might have to do some digging to find out what the proper incantation would be, if it is possible to start with.

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.

z3python: no XOR operator?

I have this code in Z3 python:
x = Bool('x')
y = Bool('y')
z = Bool('z')
z == (x xor y)
s = Solver()
s.add(z == True)
print s.check()
But this code reports below error when running:
c.py(4): error: invalid syntax
If I replace xor with and, there is no problem. So this means XOR is not supported?
You should use Xor(a, b). Moreover, to create the Z3 expression that represents the formula a and b, we must use And(a, b). In Python, we can't overload the operators and and or.
Here is an example with the Xor (available online at rise4fun).
x = Bool('x')
y = Bool('y')
z = Xor(x, y)
s = Solver()
s.add(z)
print s.check()
print s.model()

Z3Python: example of array?

i am looking for some code examples using theory of array in Z3 python, but cannot find any.
please could somebody provide some code examples?
thanks!
Here is an example showing array declarations and accessing items by indices http://rise4fun.com/Z3Py/7jAj:
x = Int('x')
a = Array('a', IntSort(), BoolSort())
b = Array('b', IntSort(), BoolSort())
c = Array('c', BoolSort(), BoolSort())
e = ForAll(x, Or(Not(a[x]), c[b[x]]))
print e
solver = Solver()
solver.add(e)
c = solver.check()
print c
Here is another example using Select and Store on array theory http://rise4fun.com/Z3Py/2CAn:
x = Int('x')
y = Int('y')
a = Array('a', IntSort(), IntSort())
s = Solver()
s.add(Select(a, x) == x, Store(a, x, y) == a)
print s.check()
print s.model()
That said, there are a few array examples floating around StackOverflow. You can try to search on the site using "z3py array" keyword for more information.

Resources