I'm working on a Z3 Python script where I'm trying to change a variable y
based on a condition involving another variable x
. Here's a simplified version of the problem:
from z3 import *
x = BitVec('x', 64)
y = BitVec('y', 64) # or y = BitVecVal(0, 64)
solver = Solver()
solver.add(x > 5555)
lsvec = [...]
for i in range(1, 64):
for j in range(i, 64):
solver.add(y == If(x > 0, y + 1, y)) # I want to increment y if x > 0
# Of course don't work because y == y + 1 cannot be satisfied
if solver.check() == sat:
model = solver.model()
x_value = model[x].as_long()
y_value = model[y].as_long()
The goal is to increment y
if x > 0
. However, the current formulations lead to unsatisfiable problems.
I'm seeking guidance on how to correctly model the conditional increment of y
based on the value of x
and other conditions using Z3.
In reality, I'm trying to solve a function similar to the one below. However I face the same issue with the XOR operation.
def f_evaluate2(x, bits):
y = 0
cursor = 0
for i in range(1, bits + 1): # 1-64
for j in range(i, 65):
if (x & (1 << (64 - i))) and (x & (1 << (64 - j))):
y ^= lsvec[cursor] # unsat
cursor += 1
return y
Any suggestions or examples on how to handle these types of constraints in Z3 would be greatly appreciated!
Thanks in advance for your assistance.
This sort of thing happens often enough in symbolic programming and code-generation/compiler algorithms. The standard solution is to use the so called "static single-assignment form," aka SSA. See https://en.wikipedia.org/wiki/Static_single-assignment_form for a discussion.
The basic idea is you first write your algorithm so that each variable is assigned only once. You achieve this by creating a new variable for each assignment to an existing variable. So, in your case:
y = y + 1
will become:
y1 = y + 1
and then you use y1
for the next reference to y
. When you have a conditional, you "merge" the assignments based on the conditions. (So called Φ functions.) When you have a loop, you "unroll" the loop, which requires you to know how many times the loop will iterate (or at least an upper-bound on the number of iterations), so you can do the unrolling on the fly.
You can calculate dominant nodes to minimize the SSA representation. Luckily, you don't always have to do this in general: Most of the time you can simply linearize your code and do the translation on the fly, assuming the control-structure of the problem is sufficiently simple enough.
Here's a simple example, using your question about modeling
y = if x > 0 then y+1 else y
You'd write it as:
y1 = If(x > 0, y+1, y)`
and use y1
in the subsequent expressions.
Hopefully this'll get you started. Translating imperative programs so they are symbolic friendly can be tricky, but if you use the SSA style exclusively, you should have an easier time. (i.e., first write your original program in SSA form, and once you're sure it is correct, then turn it into constraints.)