У меня есть несколько CNF логических выражений из логического модуля в Sympy.
Я получаю их деревья выражений 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.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'))))
Как мы могли бы автоматически анализировать деревья логических выражений Sympy для Z3Py? Есть ли лучший способ сделать это, чем то, что я представил в качестве примера?
Вы на правильном пути. По сути, вам нужно «скомпилировать» SymPy в Z3. Этого можно добиться разными способами, но это недешево и не просто, поскольку вам нужно будет анализировать большие участки кода SymPy. Однако похоже, что ваши выражения достаточно «просты», поэтому вам может сойти с рук простой переводчик. Начните с изучения того, как рекурсивно обрабатывать деревья SymPy: https://docs.sympy.org/latest/tutorials/intro-tutorial/manipulation.html#recursing-through-an-expression-tree
Если вы спешите, вы можете использовать программу Акселя, приведенную в другом ответе. Вот версия, которая, вероятно, немного более идиоматична, ее легче расширять и она более надежна:
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
, чтобы расширить его по своему усмотрению.
Потрясающе, спасибо! Что бы вы посоветовали, если логические выражения больше, чем в примере? (Здесь вы говорили о том, что нужно быть «достаточно простым», но что, если это уже не так?)
Пока все, что у вас есть, это &
, |
, ~
, программа, которую я дал, будет работать, никаких проблем. Но если у вас есть более сложные выражения SymPy, вам придется добавить для них собственные переводы, надеюсь, в Z3 есть что-то похожее. Попробуйте и дайте нам знать, если у вас возникнут проблемы.
@Aster Предыдущая версия не обрабатывала константы true
/false
. Я добавил поддержку для тех. Ознакомьтесь с новой программой.
Что касается «более сложных» выражений: если все, что вам нужно, это булева алгебра, я думаю, вы можете просто изменить определение table
, чтобы добавить любые другие комбинаторы, которые вам нужны, сопоставив их с эквивалентами z3. Мои опасения были больше связаны с общими симпатиями: числами, функциями и т. д .; некоторые из них могут быть переведены на z3 таким образом, но не все из них будут поддерживаться. (Например, sin
/cos
и т. д. не имеют поддержки SMTLib.) Пока вы придерживаетесь булевой алгебры, я думаю, что эта программа (с расширенным table
при необходимости) должна помочь.
Я лучше понимаю, что вы имеете в виду! Это не мой случай, на самом деле. Спасибо большое за ваши подробные ответы!
Я не удержался и реализовал простейший преобразователь.
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!")
Сравнение строк для идентификации классов немного ненадежно (и медленно), хотя это хорошее начало! Я опубликовал модифицированную версию в своем ответе, которая должна быть более надежной и, надеюсь, быстрее и проще в использовании.
Хороший! Это действительно более идиоматично по сравнению с моим наброском.