为什么Z3在很小的搜索空间中速度慢? [英] Why is Z3 slow for tiny search space?

查看:116
本文介绍了为什么Z3在很小的搜索空间中速度慢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试制作一个Z3程序(在Python中),该程序会生成执行某些任务(例如,将两个n位数字相加)的布尔电路,但其性能非常糟糕,以至于无法对整个对象进行强力搜索解决方案空间会更快.这是我第一次使用Z3,因此我可能会做一些会影响性能的事情,但是我的代码看起来还不错.

以下是从我的代码中复制的:此处:

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())

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

在此示例中,我生成了一个电路来计算两个1位输入的布尔或",并且该电路中提供了或"功能,因此解决方案很简单.

我的解决方案空间只有5 * 5 * 2 * 2 * 2 * 2 = 400:

  • 5个可能的功能(两个功能节点)
  • 每个功能有2个输入,每个输入都有两个可能的值

此代码需要花费几秒钟的时间运行并提供正确的答案,但是我认为它应该立即运行,因为只有400种可能的解决方案,其中很多是有效的.如果我将输入增加到两位长,则解决方案空间的大小为(5 ^ 4)*(4 ^ 8)= 40,960,000,并且永远不会在我的计算机上完成,尽管我认为使用Z3应该可以轻松做到这一点./p>

我也有效地尝试了相同的代码,但用Arrays/Store/Select代替了Python列表,并使用与selectFunc()中相同的技巧选择"了变量.该代码是此处,它在原始代码的大约同一时间运行代码,所以没有加速.

我在做会大大减慢求解器速度的事情吗?谢谢!

解决方案

您的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
 

查看这是否以任何有意义的方式改善了运行时间.如果没有,那么下一步就是尽可能多地删除数组.

I'm trying to make a Z3 program (in Python) that generates boolean circuits that do certain tasks (e.g. adding two n-bit numbers) but the performance is terrible to the point where a brute-force search of the entire solution space would be faster. This is my first time using Z3 so I could be doing something that impacts my performance, but my code seems fine.

The following is copied from my code 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())

The code basically lays out the inputs as individual bits, then at each "step" one of 5 boolean functions can operate on the values from the previous step, where the final step represents the end result.

In this example, I generate a circuit to calculate the boolean OR of two 1-bit inputs, and an OR function is available in the circuit, so the solution is trivial.

I have a solution space of only 5*5*2*2*2*2=400:

  • 5 Possible functions (two function nodes)
  • 2 Inputs for each function, each of which has two possible values

This code takes a few seconds to run and provides a correct answer, but I feel like it should run instantaneously as there are only 400 possible solutions, of which quite a few are valid. If I increase the inputs to be two bits long, the solution space has a size of (5^4)*(4^8)=40,960,000 and never finishes on my computer, though I feel this should be easily doable with Z3.

I also tried effectively the same code but substituted Arrays/Store/Select for Python lists and "selected" the variables by using the same trick I used in chooseFunc(). The code is here and it runs in around the same time the original code does, so no speedup.

Am I doing something that would drastically slow down the solver? Thanks!

解决方案

You have a duplicated __xor__ in your op_list; but that's not really the major problem. The slowdown is inevitable as you increase bit-size, but on a first look you can (and should) avoid mixing integer reasoning with booleans here. I'd code your chooseFunc as follows:

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

See if that improves run-times in any meaningful way. If not, the next thing to do would be to get rid of arrays as much as possible.

这篇关于为什么Z3在很小的搜索空间中速度慢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆