Python - 如何将布尔对称树表达式解析为布尔 Z3Py 表达式

Python - How to parse Boolean Sympy tree expressions to Boolean Z3Py expressions

提问人:Aster 提问时间:2/15/2023 最后编辑:Aster 更新时间:2/17/2023 访问量:222

问:

我从 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 求解器。 为此,我认为需要:

  1. 转换为 ,并且sympy.Symbol()z3.Bool()
  2. 将 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 布尔表达式树?有没有比我举例更好的方法呢?

python-3.x sympy 布尔逻辑 z3py

评论

0赞 furushchev 8/23/2023
只是出于好奇,是什么让你尝试将sympy表达式提供给Z3求解器?
0赞 Aster 8/24/2023
因为一些研究问题:)我已经有了 sympy 表达式,然后我们想把它提供给 SAT 求解器。这样做似乎是最不费吹灰之力的。
0赞 furushchev 8/26/2023
这是有道理的!谢谢你的回答!

答:

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 以前的版本不处理常量 /。我添加了对这些的支持。查看新程序。truefalse
0赞 alias 2/17/2023
关于“更复杂”的表达式:如果你只关心布尔代数,我认为你可以改变定义,添加你需要的任何其他组合器,将它们映射到 z3 等价物。我更关心的是一般的对称:数字、函数等;其中一些可以通过这种方式转换为 Z3,但并非所有支持。(例如,/等没有任何 SMTLib 支持。只要你坚持使用布尔代数,我认为这个程序(必要时进行扩展)应该可以解决问题。tablesincostable
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
比较字符串来识别类有点脆弱(而且很慢),尽管这是一个好的开始!我已经在我的回答中发布了一个修改后的版本,它应该更强大,希望更快、更易于使用。