Testing that a constant matches an interval - z3

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.

Related

Modifying the divide and conquer SAT search in Z3-Python

I am trying to modify the divide and conquer SAT search in Z3 (Python), but feel completely lost. I will explain my question in three parts.
Introduction
I am performing a SAT search loop in Z3 using its default incremental options (i.e. using add).
However, I was made aware that my search is a simple enumeration of type "find me all-solutions", which consists on "add the negation of the previous model and iterate". Like the following:
...
phi = a_formula
s = Solver()
s.add(phi)
while s.check() == sat:
s.check()
m = s.model()
phi = add_modelNegation(m)
s.add(phi) #in order not to explore the same model again
...
This is clearly a 'brute-force' implementation.
Also I was noted that, instead of it, I could use a "divide-and-conquer" approach, in order to make the search faster. To do so, I was offered the following link (Z3Py) checking all solutions for equation (see the last answer by #alias). I tested this code works at In Z3-Python, I get "builtin_function_or_method' object is not iterable" when performing model search. The code is essentially the following:
def all_smt(s, initial_terms):
def block_term(s, m, t):
s.add(t != m.eval(t, model_completion=True))
def fix_term(s, m, t):
s.add(t == m.eval(t, model_completion=True))
def all_smt_rec(terms):
if sat == s.check():
m = s.model()
yield m
for i in range(len(terms)):
s.push()
block_term(s, m, terms[i])
for j in range(i):
fix_term(s, m, terms[j])
yield from all_smt_rec(terms[i:])
s.pop()
yield from all_smt_rec(list(initial_terms))
The problem
My SAT problem is not that 'basic' one in which I just enumerate (or rapidly enumerate, if I follow the code above) all solutions. I have a 'booster' that allows me to add more restrictions if a given model holds some properties. This booster is a kind of heuristic. The schema is as follows:
...
s = Solver()
s.add(True)
while s.check() == sat:
s.check()
m = s.model()
phi = add_modelNegation(m)
s.add(phi) #in order not to explore the same model again
if holds_property(m): #if the model holds a property
add_moreConstraints(s,m) #add other constrains to the formula
...
I would like to add the all_smt ideas to this version, accurately making use of push and pop. My problem is that I do not perfectly understand the usage of push and pop and, since my code does not fix variables like the fast one does, I do not not how to 'merge' both ideas. How can I adapt my code to be faster?
A concrete coding example:
I offer a concrete example below. This is an example made in order to facilitate your coding, but it makes no sense in reality.
For instance, imagine that the booster or heuristic is that if the number of False assignments in the model is greater than 1, then we can add the 'reversed' model to the solver, where the 'reversed' model just changes the value of each assignment to the opposite. For instance, if the model is phi=[c0=True, c1=False, c2=False, c3=True], then phi holds the property and, thus, its 'reverse' is added, where phi_reversed = [c0=False, c1=True, c2=True, c3=False]. On the other side, phi'=[c0=True, c1=True, c2=False, c3=True] does not hold the property, so nothing additional is added to the solver.
I repeat that this booster makes no sense, but it is enough for the sake of coding. So let us make that function:
def holds_property(m, varss):
numFalses = 0
for x in varss:
if m[x] == False:
numFalses = numFalses + 1
if numFalses > 1:
return True
else:
return False
And also the function that creates the 'reversed' version of a model:
def make_reverse(m, varss):
for x in varss:
if m[x] == True:
m[x] = False
elif m[x] == False:
m[x] = True
return m
I will also invent a formula and a set of variables. Thus, my 'brute-force' implementation is (this is not pseudo-code):
a,b,c = Bools('a b 'c)
varss = [a,b,c]
phi = And(a, Or(b,c))
s = Solver()
s.add(phi)
models = []
while s.check() == sat:
s.check()
m = s.model()
models.append(m)
phi = add_modelNegation(m)
s.add(phi)
if holds_property(m, varss):
s.add(make_reverse(m, varss))
return models
The question is, again: how can I insert (or modify) the ideas of the 'fast' version to this one? I would like to add 'fixing' with push and pop, basically.
Any help? I know the question is dense but tried to explain it calmly and with examples :) Anyway, do not hesitate to ask more and I will edit the post.
When you push, you're creating a nesting level in your assertions. That is, the solver puts a marker on the assertion stack, which you can jump back to later on with a call to pop. What this means, of course, is that when you do the pop, you'll lose all the assertions that you inserted after the last push.
Since you have extra constraints, simply keep track of them, and manage them between calls to push and pop. Something like this:
from z3 import *
def pruned_all_smt(s, initial_terms, holds_property, more_constraints):
global extra_constraints
extra_constraints = []
def block_term(m, t):
s.add(t != m.eval(t, model_completion=True))
def fix_term(m, t):
s.add(t == m.eval(t, model_completion=True))
def custom_pop():
global extra_constraints
s.pop()
for ec in extra_constraints:
s.add(ec)
def custom_model():
global extra_constraints
m = s.model()
if holds_property(m):
extra_constraints = more_constraints(m) + extra_constraints
for ec in extra_constraints:
s.add(ec)
return m
def pruned_all_smt_rec(terms):
if sat == s.check():
m = custom_model()
yield m
for i in range(len(terms)):
s.push()
block_term(m, terms[i])
for j in range(i):
fix_term(m, terms[j])
yield from pruned_all_smt_rec(terms[i:])
custom_pop()
yield from pruned_all_smt_rec(list(initial_terms))
Note that we made the the hold_property and more_constraint parameters, so you need to supply those separately. Here's a template use:
def check_prop(m):
return True;
def more_constraints(m):
return []
Given this, you'd use it as:
print(list(pruned_all_smt(s, varss, check_prop, more_constraints)))
Give this a try and see how far it takes you. I haven't really tested this with anything meaningful, so there might be some gotchas!

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!

how to use z3 to get valid range of a variable

I want to find the range of valid values that a variable can have, given some constraints. Eg,
x = Int('x')
s = Solver()
s.add(x >= 1)
s.add(x < 5+2)
Is there some way that I can get z3 to print 1..6 for this variable?
I tried using the following, but range() applies only to declarations.
print("x.range():", x.range()) # this does not work
Note: 1. This question seems to ask the same, but I did not understand its answers, and I am looking for python answer.
in reply to #Malte: I am not looking for all the answers, I just want to simplify multiple constraints in to a valid range. If constraints on both sides of the variable cannot be merged, then at least only on one side as is mentioned in above mentioned question.
This question comes up occasionally, and the answer isn't very trivial, unfortunately. It really depends on what your constraints are and exactly what you are trying to do. See:
Is it possible to get a legit range info when using a SMT constraint with Z3
And
(Sub)optimal way to get a legit range info when using a SMT constraint with Z3
Essentially, the problem is too difficult (and I'd say not even well defined) if you have multiple variables. If you have exactly one variable, you can use the optimizer to some extent, assuming the variable is indeed bounded. In case you have multiple variables, one idea might be to fix all but one to satisfying constants, and compute the range of that last variable based on the constant assignment to the others. But again, it depends on what you're really trying to achieve.
Please take a look at the above two answers and see if it helps you. If not, please show us what you tried: Stack-overflow works the best when you post some code and see how it can be improved/fixed.
As a SAT/SMT solver, Z3 "only" needs to find a single model (satisfying assignment) to show that a formula is satisfiable. Finding all models is therefore not directly supported.
The question comes up regularly, though, and the solution is to repeatedly find and then block (assume in negated form) models until no further model can be found. For example, for your snippet of code:
x = Int('x')
s = Solver()
s.add(x >= 1)
s.add(x < 5+2)
result = s.check()
while result == sat:
m = s.model()
print("Model: ", m)
v_x = m.eval(x, model_completion=True)
s.add(x != v_x)
result = s.check()
print(result, "--> no further models")
Executing the script yields the solution you asked for, albeit in a less concise form:
Model: [x = 1]
Model: [x = 2]
Model: [x = 3]
Model: [x = 4]
Model: [x = 5]
Model: [x = 6]
unsat --> no further models
In general,
you would have iterate over all variables (here: just x)
model completion is necessary for variables whose value doesn't affect satisfiability; since any value will do, they won't be explicit in the model
Related questions whose answers provide additional details:
(Z3Py) checking all solutions for equation
Why Z3Py does not provide all possible solutions
Getting all solutions of a boolean expression in Z3Py never ends

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.

Need help in understading the counter example

Can somebody please explain why I am getting a counter example with this py code.
a = Int('a')
def X(a):
if (a == 3):
return 1
else:
return 2
z3.prove( X(a) == If (a == 3, 1, 2) )
counterexample
[a = 3]
Your function 'X' will always return '2' for all symbolic variables. That is:
from z3 import *
a = Int('a')
def X(a):
if (a == 3):
return 1
else:
return 2
print X(a)
will always print:
2
This is precisely why Z3 provides the If function. Python's 'if' does not work for symbolic values and the tests will simply take the else branch.
Aside:
I'd call this a weakness of the Python interface to Z3, since it's really weakly typed. Ideally, this program should be rejected because of the illegal use of if-then-else, but Python isn't a sufficiently strongly-typed language to give you that level of safety. Bindings to Z3 from other languages would reject the equivalent expression as type incorrect at compile time.
X(.) is a Python function, not a logical or Z3 function. It is evaluated when it is called. This means that the value of X(a) in
z3.prove( X(a) == If (a == 3, 1, 2) )
is determined to be 2 before z3.prove is called, because a is an Int('a') (and not 3). In this particular case, it's easy to debug such problems by simply printing the Z3 expressions, e.g.
print( X(a) == If (a == 3, 1, 2) )
reports
If(a == 3, 1, 2) == 2

Resources