为什么 Z3 的微小搜索速度很慢 space?

Why is Z3 slow for tiny search space?

我正在尝试制作一个 Z3 程序(在 Python 中),它生成执行某些任务(例如,添加两个 n 位数字)的布尔电路,但性能非常糟糕,以至于无法正常工作-force 搜索整个解决方案 space 会更快。这是我第一次使用 Z3,所以我可能会做一些影响我性能的事情,但我的代码看起来不错。

以下是从我的代码中复制过来的here:

from z3 import *

BITLEN = 1 # Number of bits in input
STEPS = 1 # How many steps to take (e.g. time)
WIDTH = 2 # How many operations/values can be stored in parallel, has to be at least BITLEN * #inputs

# Input variables
x = BitVec('x', BITLEN)
y = BitVec('y', BITLEN)

# Define operations used
op_list = [BitVecRef.__and__, BitVecRef.__or__, BitVecRef.__xor__, BitVecRef.__xor__]
unary_op_list = [BitVecRef.__invert__]
for uop in unary_op_list:
    op_list.append(lambda x, y : uop(x))

# Chooses a function to use by setting all others to 0
def chooseFunc(i, x, y):
    res = 0
    for ind, op in enumerate(op_list):
        res = res + (ind == i) * op(x, y)
    return res

s = Solver()
steps = []

# First step is just the bits of the input padded with constants
firststep = Array("firststep", IntSort(), BitVecSort(1))
for i in range(BITLEN):
    firststep = Store(firststep, i * 2, Extract(i, i, x))
    firststep = Store(firststep, i * 2 + 1, Extract(i, i, y))
for i in range(BITLEN * 2, WIDTH):
    firststep = Store(firststep, i, BitVec("const_0_%d" % i, 1))
steps.append(firststep)

# Generate remaining steps
for i in range(1, STEPS + 1):
    this_step = Array("step_%d" % i, IntSort(), BitVecSort(1))
    last_step = steps[-1]

    for j in range(WIDTH):
        func_ind = Int("func_%d_%d" % (i,j))
        s.add(func_ind >= 0, func_ind < len(op_list))

        x_ind = Int("x_%d_%d" % (i,j))
        s.add(x_ind >= 0, x_ind < WIDTH)

        y_ind = Int("y_%d_%d" % (i,j))
        s.add(y_ind >= 0, y_ind < WIDTH)

        node = chooseFunc(func_ind, Select(last_step, x_ind), Select(last_step, y_ind))
        this_step = Store(this_step, j, node)

    steps.append(this_step)

# Set the result to the first BITLEN bits of the last step
if BITLEN == 1:
    result = Select(steps[-1], 0)
else:
    result = Concat(*[Select(steps[-1], i) for i in range(BITLEN)])

# Set goal
goal = x | y
s.add(ForAll([x, y], goal == result))

print(s)
print(s.check())
print(s.model())

代码基本上将输入布置为单独的位,然后在每个 "step" 5 个布尔函数中的一个可以对上一步的值进行操作,其中最后一步代表最终结果。

在这个例子中,我生成了一个电路来计算两个 1 位输入的布尔 OR,电路中有一个 OR 函数,所以解决方案很简单。

我有一个解 space 只有 5*5*2*2*2*2=400:

此代码需要几秒钟才能 运行 并提供正确答案,但我觉得它应该立即 运行 因为只有 400 种可能的解决方案,其中有相当一部分是有效的.如果我将输入增加到两位长,解决方案 space 的大小为 (5^4)*(4^8)=40,960,000 并且永远不会在我的计算机上完成,尽管我觉得这应该很容易做到与 Z3.

我也有效地尝试了相同的代码,但使用我在 chooseFunc() 中使用的相同技巧将 Arrays/Store/Select 替换为 Python 列表和 "selected" 变量。代码是 here 并且它 运行 与原始代码的执行时间大致相同,因此没有加速。

我是否在做一些会大大降低求解器速度的事情?谢谢!

您的 op_list 中有重复的 __xor__;但这并不是真正的主要问题。随着位大小的增加,速度变慢是不可避免的,但乍一看,您可以(并且 应该 )避免在此处将整数推理与布尔值混合使用。我会将您的 chooseFunc 编码如下:

def chooseFunc(i, x, y):
    res = False;
    for ind, op in enumerate(op_list):
        res = If(ind == i, op (x, y), res)
    return res

看看这是否以任何有意义的方式提高了 运行 倍。如果没有,接下来要做的就是尽可能地摆脱数组。