Transitive Closure in Z3/Z3py - z3

Context: I'm using the Programming Z3 guide:
https://theory.stanford.edu/~nikolaj/programmingz3.html
It looks like Z3 now has built-in support for transitive-closure, but for the moment it's only accessible via Z3py:
https://theory.stanford.edu/~nikolaj/programmingz3.html#sec-transitive-closure
I have two questions:
(1) Is there any means of accessing TransitiveClosure via other APIs or the executable directly via "z3 -in", or is it just for Z3py at the moment?
(2) Is TransitiveClosure supposed to interoperate with push and pop? We built from the master branch earlier this week (commit 2788f72bbb6bfd6bdad2da2b4c37ef1bb502469d) and ran the following example:
from z3 import *
B = BoolSort()
S = DeclareSort('S')
a, b, c = Consts('a b c', S)
R = Function('R', S, S, B)
TCR = TransitiveClosure(R)
s = Solver()
s.add(R(a, b) == True)
s.push() # If this line is uncommented (or both are) the result is sat, which is wrong.
s.add(TCR(a, b) == False)
s.push() # But if THIS line is uncommented (or neither are), the result is unsat, which is correct.
print(s)
print(s.check())
As the comments indicate, a push() call between assertions about R and its transitive closure seem to break the link between the two relations. Not sure if this is a bug, or my own misunderstanding...

From the looks of it, it's also available via the C/C++ api as well:
https://github.com/Z3Prover/z3/blob/62de187d02d8d2e7a3667a31753c508f7c73aaa1/src/api/c%2B%2B/z3%2B%2B.h#L637-L639
I don't think it's available from the SMTLib interface (what you meant by z3 -in I presume), as it's itself returning a relation; and such higher-order constructs are usually not allowed in SMTLib. (But there might be a "magic" switch, of course; z3 is known to experiment with functionality that's not part of SMTLib.)
Regarding whether it should work with push/pop: I don't think the fixed-point engine in z3 allows for incremental solving; so I'm not surprised that it's behaving erratically. You should definitely report this behaviour at their issues site (https://github.com/Z3Prover/z3/issues) so they can at least issue an error message if you try to do incremental stuff, instead of spitting out misleading info. (Or maybe you hit a bug! So, that'd also be good for them to know.)

s.pop(),s.push() is not necessary.
# https://theory.stanford.edu/~nikolaj/programmingz3.html#sec-transitive-closure
from z3 import *
B = BoolSort()
S = DeclareSort('S')
R = Function('R', S, S, B)
TCR = TransitiveClosure(R)
a, b, c = Consts('a b c', S)
s = Solver()
s.add(R(a, b))
s.add(R(b, c))
s.add(Not(TCR(a, c))) # s.add(Not(R(a, c))) will be “sat”,because TCR is transitiveclosure but R is not.
print(s)
print(s.check()) # unsat

Related

How to represent a symbolic summation in Z3?

I'm trying to represent sum of integers from an integer a to an integer b of the form
\sum_{i=a}^b i
Of courese there is a closed form solution for this version, but in general I'd like to sum over expressions parameterized by i. I've currently tried to define a function symSum and described its behavior using universal quantifiers:
from z3 import *
s = Solver()
symSum = Function('symSum', IntSort(), IntSort(), IntSort())
a = Int('a')
b = Int('b')
s.add(ForAll([a,b],If(a > b,symSum(a,b) == 0,symSum(a,b) == a + symSum(a+1,b))))
x = Int('x')
s.add(x == symSum(1,5))
print(s.check())
print(s.model())
I have not gotten this code to terminate (I have only allowed it to run for a couple minutes at max though). Is this outside the capabilities of Z3?
EDIT:
Looking into this a bit more I was able to use recursive functions to define this!
from z3 import *
ctx = Context()
symSum = RecFunction('symSum', IntSort(ctx), IntSort(ctx), IntSort(ctx))
a = Int('a',ctx)
b = Int('b',ctx)
RecAddDefinition(symSum, [a,b], If(a > b, 0, a + symSum(a+1,b)))
x = Int('x',ctx)
s = Solver(ctx=ctx)
s.add(symSum(1,5) == x)
print(s.check())
print(s.model())
Yes; this is beyond the current capabilities of SMT solvers.
Imagine how you'd prove something like this by hand. You'd have to do induction on the natural numbers. SMT solvers do not perform induction, at least not out-of-the box. You can coax them to do so with a lot of helper lemmas and careful guiding via what's known as patterns; but it's not something they're designed for, or even good at. At least not for the time being.
See this answer for more details regarding a relevant question: Is it possible to prove this defined function is an involution in z3?
Having said that, recent versions of SMTLib does include capabilities for definitions recursive functions. (See https://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf, Section 4.2.3.) And z3 and other solvers have some support for the new syntax, though they can't really prove any interesting properties of these functions. I suspect the direction of the community is that eventually some level of (user-guided) induction will become part of the toolbox offered by these solvers, but that is currently not the case for the time being.

Using sets and SetHasSize on intersections in z3

I've been trying to enumerate solutions to the following problem. I've started with simple approach first.
Below I have two sets of size k, intersection between them is of size 1 and I want to see how sets A and B look:
Els, elems = EnumSort('Els',['a1', 'a2', 'a3'])
A, B = Consts('A B', SetSort(Els))
k, c = Ints('k c')
s = Solver()
s.add(SetHasSize(A, k))
s.add(SetHasSize(B, k))
s.add(k == 2, c == 1)
s.add(SetHasSize(SetIntersect(A, B), c))
s.check()
s.model()
Here, the solution should be A == ['a1', 'a2'] and B == ['a2', 'a3'] but satisfiability was not reached.
Even a simple task like one below results in never ending execution:
V, _ = EnumSort('Els',['a1', 'a2', 'a3'])
A = Const('A', SetSort(V))
k = Int('k')
s = SimpleSolver()
s.add(SetHasSize(A, k))
s.add(k == IntVal(2))
s.check()
s.model()
Changing k == IntVal(2) to k <= IntVal(2) makes the problem satisfiable and returns [A = ∃k!0 : k!0 = a1 ∨ k!0 = a2, k = 2] as a model of the set. I'm not sure if there is a faster approach.
If I run your program, I get:
WARNING: correct handling of finite domains is TBD
WARNING: correct handling of finite domains is TBD
WARNING: correct handling of finite domains is TBD
before it starts looping. This is a known issue in the implementation: Z3 cannot really deal with sets that have a finite domain.
Alas, replacing it with an infinite domain doesn't help either. Making this change:
A, B = Consts('A B', SetSort(IntSort()))
You get:
unsat
which is clearly bogus. I strongly suspect this is related to the following issue: https://github.com/Z3Prover/z3/issues/3854 (In short, the SMTLib frontend does not support set-has-size. For whatever reason, they left it in the Python and C/C++ interfaces, but clearly it's not really functional.)
So, strictly speaking, this is a bug. But more realistic answer is that set-has-size is no longer supported by z3. You should file an issue at https://github.com/Z3Prover/z3/issues so the authors are aware of this problem and perhaps remove it from the API completely if it won't ever be supported.
UPDATE:
As of this commit, z3 no longer accepts the set-has-size predicate; it is no longer supported.

z3py: assumptions from (check-sat ...) statement

Is there a way to pass assumptions from (check-sat ...) statement of SMT2 formula into the solver ?
Consider the following example formula stored in ex.smt2:
# cat ex.smt2
(declare-fun p () Bool)
(assert (not p))
(check-sat p)
Running z3 on it gives unsat, as expected. Now, I'd like to solve with assumptions (p) through z3py interface:
In [30]: ctx = z3.Context()
In [31]: s = z3.Solver(ctx=ctx)
In [32]: f = z3.parse_smt2_file("ex.smt2", ctx=ctx)
In [33]: s.add(f)
In [34]: s.check()
Out[34]: sat
Is there an API to get assumptions (i.e. (p) in this example) from the parser ? Or even better, just tell the solver to solve with the assumptions read from the input file ?
No, there is no such API. The parse_smt2_file API is very simple, and only provides access to the assertions in the input file. Extending this API is in the TODO list, but nobody is currently working on that.

How does Z3 deal with forall proposition with uninterpreted functions in it?

Suppose we have two uninterpreted functions func1 and func2:
stuct_sort func1(struct_sort);
stuct_sort func2(struct_sort ,int).
And they have the relationship:
func2(p,n)=func1(p) if n==1
func2(p,n)=func1(func2(p,n-1)) if n>1
What I want to know is that if the following proposition :
((forall i:[1,m].func2(p,i)==Z)&&(q==func1(p))) implies (forall i:[1,m-1].func2(q,i)==Z)
can be proved to be true in Z3?
In my program, the prove result is Z3_L_UNDEF.
When I assign m with a value such as 3, the proposition now is
((forall i:[1,3].func2(p,i)==Z)&&(q==func1(p))) implies (forall i:[1,3-1].func2(q,i)==Z);
the result is Z3_L_UNDEF.
But when I rewrite the case separately(not using forall) as follows, the result is true.
(func2(p,1)==Z)&&(func2(p,2)==Z)&&(func2(p,3)==Z)&&(q==func1(p)) implies (func2(q,1))&&(func2(q,2)).
I can't find out the reason and looking forward to your answer
I encoded your problem using the Z3 Python interface, and Z3 solved it. It found a counterexample for the conjecture.
Of course, I may have made a mistake when I encoded the problem. The Python code is in the end of the post. We can try it online at rise4fun. BTW, which version of Z3 are you using? I'm assuming you are using the C API. If that is the case, could you provide the C code you used to create the Z3 formulas? Another possibility is to create a log that records the interaction of your application and Z3. To create a log file, we have to execute Z3_open_log("z3.log"); before you execute any other Z3 API. We can use the log file to replay all the interaction between your application and Z3.
from z3 import *
# Declare stuct_sort
S = DeclareSort('stuct_sort')
I = IntSort()
# Declare functions func1 and func2
func1 = Function('func1', S, S)
func2 = Function('func2', S, I, S)
# More declarations
p = Const('p', S)
n = Int('n')
m = Int('m')
i = Int('i')
q = Const('q', S)
Z = Const('Z', S)
# Encoding of the relations
# func2(p,n)=func1(p) if n==1
# func2(p,n)=func1(func2(p,n-1)) if n>1
Relations = And(func2(p, 1) == func1(p),
ForAll([n], Implies(n > 1, func2(p, n) == func1(func2(p, n - 1)))))
# Increase the maximum line width for the Z3 Python formula pretty printer
set_option(max_width=120)
print Relations
# Encoding of the conjecture
# ((forall i:[1,m].func2(p,i)==Z)&&(q==func1(p))) implies (forall i:[1,m-1].func2(q,i)==Z)
Conjecture = Implies(And(q == func1(p), ForAll([i], Implies(And(1 <= i, i <= m), func2(p, i) == Z))),
ForAll([i], Implies(And(1 <= i, i <= m - 1), func2(q, i) == Z)))
print Conjecture
prove(Implies(Relations, Conjecture))

Asymmetric behavior in ctx-solver-simplify

I'm seeing rather surprising behavior from ctx-solver-simplify, where the order of parameters to z3.And() seems to matter, using the latest version of Z3 from the master branch of https://git01.codeplex.com/z3 (89c1785b):
#!/usr/bin/python
import z3
a, b = z3.Bools('a b')
p = z3.Not(a)
q = z3.Or(a, b)
for e in z3.And(p, q), z3.And(q, p):
print e, '->', z3.Tactic('ctx-solver-simplify')(e)
produces:
And(Not(a), Or(a, b)) -> [[Not(a), Or(a, b)]]
And(Or(a, b), Not(a)) -> [[b, Not(a)]]
Is this a bug in Z3?
No, this is not a bug. The tactic ctx-solver-simplify is very expensive and inherently asymmetric. That is, the order the sub-formulas are visited affect the final result. This tactic is implemented in the file src/smt/tactic/ctx_solver_simplify_tactic.cpp. The code is quite readable. Note that, it uses a "SMT" solver (m_solver), and makes several calls to m_solver.check() while traversing the input formula. Each one of these calls can be quite expensive. For particular problem domains, we can implement a version of this tactic that is even more expensive, and avoids the asymmetry described in your question.
EDIT:
You may also consider the tactic ctx-simplify, it is cheaper than ctx-solver-simplify, but it is symmetric. The tactic ctx-simplify will essentially applies rules such as:
A \/ F[A] ==> A \/ F[false]
x != c \/ F[x] ==> F[c]
Where F[A] is a formula that may contain A. It is cheaper than ctx-solver-simplify because it does not invoke a SMT solver when it is traversing the formula. Here is an example using this tactic (also available online):
a, b = Bools('a b')
p = Not(a)
q = Or(a, b)
c = Bool('c')
t = Then('simplify', 'propagate-values', 'ctx-simplify')
for e in Or(c, And(p, q)), Or(c, And(q, p)):
print e, '->', t(e)
Regarding humand-readability, this was never a goal when implementing any tactic. Please, tell us if the tactic above is not good enough for your purposes.
I think it will be better to write a custom tactic because there are
other tradeoffs when you essentially ask for canonicity.
Z3 does not have any tactics that convert formulas into a canonical form.
So if you want something that always produces the same answer for formulas
that are ground equivalent you will have to write a new normalizer that
ensures this.
The ctx_solver_simplify_tactic furthermore makes some tradeoffs when simplifying formulas:
it avoids simplifying the same sub-formula multiple times. If it did, it could increase
the size of the resulting formula significantly (exponentially).

Resources