提问人:Aster 提问时间:2/15/2023 最后编辑:Aster 更新时间:2/17/2023 访问量:222
Python - 如何将布尔对称树表达式解析为布尔 Z3Py 表达式
Python - How to parse Boolean Sympy tree expressions to Boolean Z3Py expressions
问:
我从 Sympy 的逻辑模块中获得了一些布尔表达式的 CNF。
我得到了他们的 Sympy 表达式树(参见文档)。srepr()
在下面找到一个包含两个 CNF 的示例。
from sympy import Symbol
from sympy.logic.boolalg import And, Or, Not
# (a | ~c) & (a | ~e) & (c | e | ~a)
expr_1 = And(Or(Symbol('a'), Not(Symbol('c'))), Or(Symbol('a'), Not(Symbol('e'))), Or(Symbol('c'), Symbol('e'), Not(Symbol('a'))))
# (b | ~d) & (b | ~e) & (d | e | ~b)
expr_2 = And(Or(Symbol('b'), Not(Symbol('d'))), Or(Symbol('b'), Not(Symbol('e'))), Or(Symbol('d'), Symbol('e'), Not(Symbol('b'))))
我想将这些表达式树作为布尔约束提供给 Z3Py 求解器。 为此,我认为需要:
- 转换为 ,并且
sympy.Symbol()
z3.Bool()
- 将 sympy 逻辑算子转换为 Z3 逻辑算子(例如,将
sympy.logic.boolalg.And()
z3.And()
)
然后,我将约束添加到 Z3 求解器以输出解决方案。
如果我们继续这个例子,正如我所看到的,我们将有以下两个约束(我明确写道,我使用 Z3 布尔运算符以避免与 Sympy 运算符混淆):
import z3 as z3
from z3 import Bool
const_1 = z3.And(z3.Or(Bool('a'), z3.Not(Bool('c'))), z3.Or(Bool('a'), z3.Not(Bool('e'))), z3.Or(Bool('c'), Bool('e'), z3.Not(Bool('a'))))
const_2 = z3.And(z3.Or(Bool('b'), z3.Not(Bool('d'))), z3.Or(Bool('b'), z3.Not(Bool('e'))), z3.Or(Bool('d'), Bool('e'), z3.Not(Bool('b'))))
我们如何以自动化的方式解析 Z3Py 的 Sympy 布尔表达式树?有没有比我举例更好的方法呢?
答:
4赞
alias
2/16/2023
#1
你走在正确的轨道上。从本质上讲,您需要将 SymPy “编译”为 Z3。这可以通过多种方式实现,但这不是一件便宜/容易的事情,因为您需要分析大量的 SymPy 代码。但是,看起来您的表达方式足够“简单”,因此您可以使用简单的翻译器。首先看看如何递归处理 SymPy 树: https://docs.sympy.org/latest/tutorials/intro-tutorial/manipulation.html#recursing-through-an-expression-tree
如果你赶时间,你可以使用Axel的程序,在另一个答案中给出。这是一个可能更惯用、更易于扩展和更强大的版本:
import sympy
import z3
# Sympy vs Z3. Add more correspondences as necessary!
table = { sympy.logic.boolalg.And : z3.And
, sympy.logic.boolalg.Or : z3.Or
, sympy.logic.boolalg.Not : z3.Not
, sympy.logic.boolalg.Implies: z3.Implies
}
# Sympy vs Z3 Constants
constants = { sympy.logic.boolalg.BooleanTrue : z3.BoolVal(True)
, sympy.logic.boolalg.BooleanFalse: z3.BoolVal(False)
}
def compile_to_z3(exp):
"""Compile sympy expression to z3"""
pexp = sympy.parsing.sympy_parser.parse_expr(exp)
pvs = {v: z3.Bool(str(v)) for v in pexp.atoms() if type(v) not in constants}
def cvt(expr):
if expr in pvs:
return pvs[expr]
texpr = type(expr)
if texpr in constants:
return constants[texpr]
if texpr in table:
return table[texpr](*map(cvt, expr.args))
raise NameError("Unimplemented: " + str(expr))
return cvt(pexp)
if __name__ == '__main__':
z3.solve(compile_to_z3("false"))
z3.solve(compile_to_z3("a & ~b | c"))
z3.solve(compile_to_z3("false >> (a & ~b | c)"))
这打印:
no solution
[c = False, b = False, a = True]
[]
您可以添加新函数以根据需要对其进行扩展。table
评论
0赞
Axel Kemper
2/16/2023
好!与我的素描相比,这确实更惯用。
0赞
Aster
2/16/2023
太棒了,谢谢!如果布尔表达式大于示例,您有什么建议?(你在这里谈到了“足够简单”,但如果不再是这样了怎么办?
0赞
alias
2/16/2023
只要你所拥有的只是,,,我给出的程序就可以了,没有问题。但是,如果您有更复杂的 SymPy 表达式,则必须为它们添加自定义翻译,希望 Z3 中有一些非常匹配的东西。试试看,如果您遇到麻烦,请告诉我们。&
|
~
1赞
alias
2/17/2023
@Aster 以前的版本不处理常量 /。我添加了对这些的支持。查看新程序。true
false
0赞
alias
2/17/2023
关于“更复杂”的表达式:如果你只关心布尔代数,我认为你可以改变定义,添加你需要的任何其他组合器,将它们映射到 z3 等价物。我更关心的是一般的对称:数字、函数等;其中一些可以通过这种方式转换为 Z3,但并非所有支持。(例如,/等没有任何 SMTLib 支持。只要你坚持使用布尔代数,我认为这个程序(必要时进行扩展)应该可以解决问题。table
sin
cos
table
1赞
Axel Kemper
2/16/2023
#2
我无法抗拒并实施了一个基本的转换器。
from sympy import symbols
from sympy.parsing.sympy_parser import parse_expr
from z3 import *
# extract all variables and define a SymPy expression
def create_sympy_expression(expr):
declare_sympy_symbols(expr)
return parse_expr(expr)
# assume all single-character operands as SymPy variables
dicz3sym = {}
def declare_sympy_symbols(expr):
for c in expr:
if 'a' <= c <= 'z':
if not c in dicz3sym:
dicz3sym[c] = z3.Bool(c)
def transform_sympy_to_z3(exp):
params = [transform_sympy_to_z3(arg) for arg in exp.args]
func = str(exp.func)
if func == "And":
return z3.And(params)
elif func == "Not":
return z3.Not(params[0])
elif func == "Or":
return z3.Or(params)
elif exp.name in dicz3sym:
return dicz3sym[exp.name]
else:
raise NameError("unknown/unimplemented operator: " + func)
if __name__ == '__main__':
exp = create_sympy_expression("a & ~b | c")
z3exp = transform_sympy_to_z3(exp)
s = Solver()
s.add(z3exp)
if s.check() == sat:
m = s.model()
print("Solution found:")
for v in dicz3sym:
print(f"{v} = {m[dicz3sym[v]]}")
else:
print("No solution. Sorry!")
评论
0赞
alias
2/16/2023
比较字符串来识别类有点脆弱(而且很慢),尽管这是一个好的开始!我已经在我的回答中发布了一个修改后的版本,它应该更强大,希望更快、更易于使用。
评论