Unexpected result after calling Exists on substituted formula - z3

When calling Z3py's Exists function subsequently on different variables and formulas, I get the exact same result. Is that some sort of Python problem or is Z3 broken here? How to fix? The following minimal example illustrates the problem:
from z3 import *
a, a0, a1, b, b0, b1 = Ints('a a0 a1 b b0 b1')
x, y = Bools('x y')
s = Solver()
formula = Implies(x, And(a>0,b1<0))
substitution1 = substitute(formula,(a1,a0),(b1,b0))
substitution2 = substitute(formula,(a1,a0),(b1,b0),(a,a1),(b,b1))
print substitution1
print substitution2
exist1 = Exists([a,b],substitution1)
exist2 = Exists([a1,b1],substitution2)
print exist1
print exist2
Output:
Implies(x, And(a > 0, b0 < 0))
Implies(x, And(a1 > 0, b0 < 0))
Exists([a, b], Implies(x, And(a > 0, b0 < 0)))
Exists([a, b], Implies(x, And(a > 0, b0 < 0)))

Thanks for reporting that. Z3 is in fact correct about this, but the output is confusing. Internally, Z3 uses deBrujin indices and the names of bound variables are immaterial. When a quantifier is created that has the same body (and patterns, no_patterns, etc) then the exactly same expression that was seen before is used, to avoid having to solve the same quantified constraint as before. This creates the confusing situation as suddenly the names of bound variables seem to have changed.
In the example given here, the bodies of both quantifiers are indeed identical and the names of variables do not matter. Z3 could use any names for those variables, but it chooses to use the ones that were used when the quantifier was created the first time. We could disable that, e.g., by adding
compare_arrays(to_quantifier(n1)->get_decl_names(),
to_quantifier(n2)->get_decl_names(),
to_quantifier(n1)->get_num_decls()) &&
at src/ast/ast.cpp:470. This however would likely have a negative impact on Z3's performance on some benchmarks, so I will not make this change. If you would like to use it, you can add it to your local copy of Z3 of course.

Related

Using a variable when specifying formatting options in string.format

I am trying to write a function that produces a string of a fixed length. There are two cases I think I need to consider:
The string is too long and must be cropped
The string is too short and must be padded with whitespace
To do this, I have written the following:
foo = "testtesttesttest"
bar = "test"
function fixed_width_a(s)
return string.format("%-6s", string.sub(s, 1, 6))
end
print(fixed_width_a(foo))
print(fixed_width_a(bar))
-- testte
-- test__ (Using underscores to denote spaces)
While I don't know if this is the best way, it works. Great!
Now, I'd like to be able to specify the width of the string as a parameter. For example,
function fixed_width_b(s, w)
w = w or 6
return string.format("%-ws", string.sub(s, 1, w))
end
Of course, this naive attempt doesn't work because "%-ws" isn't parsed correctly. Another attempt might be to specify,
string.format("%-{%d}s", w, string.sub(s, 1, w))
but obviously this makes no sense either.
Question: how do I specify formatting options using a variable in string.format?
print(("%-_._s"):gsub("_", 6):format("teststring"))
print(("%-"+6+"."+"s"):format("teststring"))
print(("%%-%d.%ds"):format(6,6):format("teststring"))
Okay, so this seems to work okay, although I don't know if it's the best option.
function fixed_width_b(s, w)
w = w or 6
tmp = string.format("%%-%ds", w)
return string.format(tmp, string.sub(s, 1, w))
end
Here, I construct the first argument to the crop/pad part of the code as a string called tmp. If w = 5, for example, then tmp = "%-5s".
Edit
For brevity, I suppose I could just write,
function fixed_width_b(s, w)
w = w or 6
return string.format(string.format("%%-%ds", w), string.sub(s, 1, w))
end
and avoid an intermediate variable.

Testing that a constant matches an interval

Given the following simple example:
s = Solver()
Z = IntSort()
a = Const('a', Z)
s.add(a >= 0)
s.add(a < 10)
print(s.check(a > 5)) # sat
Up until the last line, a has an implied range of 0 <= a < 10 - for which a > 5 does not satisfy. However, .check() tells z3 that "this is true" - which is not what I'm after.
Is there a way to ask z3 to test if a > 5 given the existing set of constraints, and have z3 interpret this as "make sure this is true given everything else we know about a"?
EDIT: Okay I think I figured it out. Since z3 tries to find a model that satisfies all constraints, I should instead check for the inverse to see if it finds a solution - in which case, my check would not hold.
In this case, my "test" function would become:
def test(s, expr):
return s.check(Not(expr)) == unsat
thus...
s = Solver()
Z = IntSort()
a = Const('a', Z)
s.add(a >= 0)
s.add(a < 10)
print(s.check(a > 5)) # sat
print(s.check(Not(a > 5)) == unsat) # false, test failed
s.add(a > 8)
print(s.check(Not(a > 5)) == unsat) # true, test passed
Is that correct?
What you are doing is essentially correct, though not idiomatic. When you issue s.check(formula), you're telling z3 to show the satisfiability of all the other constraints you add'ed, along with formula; i.e., their conjunction. So, the way you set it up, it gives you the correct result.
Here's some more detail. Since what you want to prove looks like:
Implies(And(lower, upper), required)
the typical way of doing this would be to assert its negation, and then check if it is satisfiable. That is, you'd check if:
Not(Implies(And(lower, upper), required))
is satisfiable. Recall that Implies(a, b) is equivalent to Or(Not(a), b), so a little bit of Boolean logic transforms the above to:
And(And(lower, upper), Not(required))
What you are doing is to add the first conjunct above (i.e., And(lower, upper)) to the solver itself, and then use the second conjunct Not(required) as an argument to check to see if you get unsat. If you do, then you get a "proof" that required always holds. Otherwise you get a counterexample.
I should add, however, that this is not idiomatic usage of z3py and calls to check with a formula. The latter is usually used with a list of assumptions, and figuring out which subset of them was unsatisfiable with a call to get-unsat-assumptions. These applications typically come from model-checking problems. For details, see Section 4.2.5 of https://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2021-05-12.pdf which discusses the semantics of check-sat-assuming, which is the SMTLib equivalent of calling check with extra assumptions.
To solve your problem in more idiomatic z3, one would instead write something like:
from z3 import *
s = Solver()
a = Int('a')
lower = a >= 0
upper = a < 10
required = a > 5
formula = Implies(And(lower, upper), required)
# assert the negation
s.add(Not(formula))
r = s.check()
if r == sat:
print("Not valid, counterexample:")
print(s.model())
elif r == unsat:
print("Valid!")
else:
print("Solver said:", r)
This prints:
Not valid, counterexample:
[a = 0]
And if you change to lower = a >= 8, it'll print:
Valid!
Bottom line, what you're doing is correct but not very idiomatic usage for check-sat-assuming. A simpler way is to simply assert the negation of the implication that your lower/upper bounds imply the required inequality and check if the result is unsat instead.

What is the most efficient way of checking N-way equation equivalence in Z3?

Suppose I have a set of Z3 expressions:
exprs = [A, B, C, D, E, F]
I want to check whether any of them are equivalent and, if so, determine which. The most obvious way is just an N×N comparison (assume exprs is composed of some arbitrarily-complicated boolean expressions instead of the simple numbers in the example):
from z3 import *
exprs = [IntVal(1), IntVal(2), IntVal(3), IntVal(4), IntVal(3)]
for i in range(len(exprs) - 1):
for j in range(i+1, len(exprs)):
s = Solver()
s.add(exprs[i] != exprs[j])
if unsat == s.check():
quit(f'{(i, j)} are equivalent')
Is this the most efficient method, or is there some way of quantifying over a set of arbitrary expressions? It would also be acceptable for this to be a two-step process where I first learn whether any of the expressions are equivalent, and then do a longer check to see which specific expressions are equivalent.
As with anything performance related, the answer is "it depends." Before delving into options, though, note that z3 supports Distinct, which can check whether any number of expressions are all different: https://z3prover.github.io/api/html/namespacez3py.html#a9eae89dd394c71948e36b5b01a7f3cd0
Though of course, you've a more complicated query here. I think the following two algorithms are your options:
Explicit pairwise checks
Depending on your constraints, the simplest thing to do might be to call the solver multiple times, as you alluded to. To start with, use Distinct and make a call to see if its negation is satisfiable. (i.e., check if some of these expressions can be made equal.) If the answer comes unsat, you know you can't make any equal. Otherwise, go with your loop as before till you hit the pair that can be made equal to each other.
Doing multiple checks together
You can also solve your problem using a modified algorithm, though with more complicated constraints, and hopefully faster.
To do so, create Nx(N-1)/2 booleans, one for each pair, which is equal to that pair not being equivalent. To illustrate, let's say you have the expressions A, B, and C. Create:
X0 = A != B
X1 = A != C
X2 = B != C
Now loop:
Ask if X0 || X1 || X2 is satisfiable.
If the solver comes back unsat, then all of A, B, and C are equivalent. You're done.
If the solver comes back sat, then at least one of the disjuncts X0, X1 or X2 is true. Use the model the solver gives you to determine which ones are false, and continue with those until you get unsat.
Here's a simple concrete example. Let's say the expressions are {1, 1, 2}:
Ask if 1 != 1 || 1 != 2 || 1 != 2 is sat.
It'll be sat. In the model, you'll have at least one of these disjuncts true, and it won't be the first one! In this case the last two. Drop them from your list, leaving you with 1 != 1.
Ask again if 1 != 1 is satisfiable. The answer will be unsat and you're done.
In the worst case you'll make Nx(N-1)/2 calls to the solver, if it happens that none of them can be made equivalent with you eliminating one at a time. This is where the first call to Not (Distinct(A, B, C, ...)) is important; i.e., you will start knowing that some pair is equivalent; hopefully iterating faster.
Summary
My initial hunch is that the second algorithm above will be more performant; though it really depends on what your expressions really look like. I suggest some experimentation to find out what works the best in your particular case.
A Python solution
Here's the algorithm coded:
from z3 import *
exprs = [IntVal(i) for i in [1, 2, 3, 4, 3, 2, 10, 10, 1]]
s = Solver()
bools = []
for i in range(len(exprs) - 1):
for j in range(i+1, len(exprs)):
b = Bool(f'eq_{i}_{j}')
bools.append(b)
s.add(b == (exprs[i] != exprs[j]))
# First check if they're all distinct
s.push()
s.add(Not(Distinct(*exprs)))
if(s.check()== unsat):
quit("They're all distinct")
s.pop()
while True:
# Be defensive, bools should not ever become empty here.
if not bools:
quit("This shouldn't have happened! Something is wrong.")
if s.check(Or(*bools)) == unsat:
print("Equivalent expressions:")
for b in bools:
print(f' {b}')
quit('Done')
else:
# Use the model to keep bools that are false:
m = s.model()
bools = [b for b in bools if not(m.evaluate(b, model_completion=True))]
This prints:
Equivalent expressions:
eq_0_8
eq_1_5
eq_2_4
eq_6_7
Done
which looks correct to me! Note that this should work correctly even if you have 3 (or more) items that are equivalent; of course you'll see the output one-pair at a time. So, some post-processing might be needed to clean that up, depending on the needs of the upstream algorithm.
Note that I only tested this for a few test values; there might be corner case gotchas. Please do a more thorough test and report if there're any bugs!

Z3 - how assumptions works

I have the following code in Z3py
import z3
x = z3.BitVec('x', 32)
a = z3.BitVec('a', 32)
solver=z3.Solver()
solver.set(unsat_core=True)
d=z3.Bool('d')
solver.assert_and_track(x!=a,d)
solver.add(x==a)
print solver.check(d)
print solver.check(z3.Not(d))
I would expect it to print unsat and sat. However it prints always unsat. Should the call solver.check(z3.Not(d)) effectively disable the assertion x!=a?
assert_and_track simply associates the answer-literal d with the formula x!=a in your example. Since the context contains x==a and x!=a at the same time, it is unsat. No assumptions will make it satisfiable.
But I do agree that this is very confusing. I think the issue goes back to the fact that assert_and_track is not really intended for what you are trying to do, but merely to make unsat-core-extraction possible. See the discussion here: https://stackoverflow.com/a/14565535/936310
To really achieve what you want, simply assert the implication yourself. That is, use:
solver.add(Implies(d, x!=a))
For instance, the following script:
from z3 import *
x = z3.BitVec('x', 32)
a = z3.BitVec('a', 32)
solver=z3.Solver()
solver.set(unsat_core=True)
d=z3.Bool('d')
solver.add(Implies(d, x!=a))
solver.add(x==a)
print solver.check(d)
print solver.unsat_core()
print solver.check(z3.Not(d))
print solver.model()
produces:
unsat
[d]
sat
[a = 0, d = False, x = 0]
which is what you are trying to achieve, I believe.

Can Z3 call python function during decision making of variables?

I am trying to solve a problem, for example I have a 4 point and each two point has a cost between them. Now I want to find a sequence of nodes which total cost would be less than a bound. I have written a code but it seems not working. The main problem is I have define a python function and trying to call it with in a constraint.
Here is my code: I have a function def getVal(n1,n2): where n1, n2 are Int Sort. The line Nodes = [ Int("n_%s" % (i)) for i in range(totalNodeNumber) ] defines 4 points as Int sort and when I am adding a constraint s.add(getVal(Nodes[0], Nodes[1]) + getVal(Nodes[1], Nodes[2]) < 100) then it calls getVal function immediately. But I want that, when Z3 will decide a value for Nodes[0], Nodes[1], Nodes[2], Nodes[3] then the function should be called for getting the cost between to points.
from z3 import *
import random
totalNodeNumber = 4
Nodes = [ Int("n_%s" % (i)) for i in range(totalNodeNumber) ]
def getVal(n1,n2):
# I need n1 and n2 values those assigned by Z3
cost = random.randint(1,20)
print cost
return IntVal(cost)
s = Solver()
#constraint: Each Nodes value should be distinct
nodes_index_distinct_constraint = Distinct(Nodes)
s.add(nodes_index_distinct_constraint)
#constraint: Each Nodes value should be between 0 and totalNodeNumber
def get_node_index_value_constraint(i):
return And(Nodes[i] >= 0, Nodes[i] < totalNodeNumber)
nodes_index_constraint = [ get_node_index_value_constraint(i) for i in range(totalNodeNumber)]
s.add(nodes_index_constraint)
#constraint: Problem with this constraint
# Here is the problem it's just called python getVal function twice without assiging Nodes[0],Nodes[1],Nodes[2] values
# But I want to implement that - Z3 will call python function during his decission making of variables
s.add(getVal(Nodes[0], Nodes[1]) + getVal(Nodes[1], Nodes[2]) + getVal(Nodes[2], Nodes[3]) < 100)
if s.check() == sat:
print "SAT"
print "Model: "
m = s.model()
nodeIndex = [ m.evaluate(Nodes[i]) for i in range(totalNodeNumber) ]
print nodeIndex
else:
print "UNSAT"
print "No solution found !!"
If this is not a right way to solve the problem then could you please tell me what would be other alternative way to solve it. Can I encode this kind of problem to find optimal sequence of way points using Z3 solver?
I don't understand what problem you need to solve. Definitely, the way getVal is formulated does not make sense. It does not use the arguments n1, n2. If you want to examine values produced by a model, then you do this after Z3 returns from a call to check().
I don't think you can use a python function in your SMT logic. What you could alternatively is define getVal as a Function like this
getVal = Function('getVal',IntSort(),IntSort(),IntSort())
And constraint the edge weights as
s.add(And(getVal(0,1)==1,getVal(1,2)==2,getVal(0,2)==3))
The first two input parameters of getVal represent the node ids and the last integer represents the weight.

Resources