Python — как анализировать логические выражения дерева Sympy в логические выражения Z3Py

У меня есть несколько 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 в виде логических ограничений. Для этого, я думаю, необходимо:

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

Как мы могли бы автоматически анализировать деревья логических выражений Sympy для Z3Py? Есть ли лучший способ сделать это, чем то, что я представил в качестве примера?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы на правильном пути. По сути, вам нужно «скомпилировать» 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, чтобы расширить его по своему усмотрению.

Хороший! Это действительно более идиоматично по сравнению с моим наброском.

Axel Kemper 16.02.2023 08:19

Потрясающе, спасибо! Что бы вы посоветовали, если логические выражения больше, чем в примере? (Здесь вы говорили о том, что нужно быть «достаточно простым», но что, если это уже не так?)

Aster 16.02.2023 10:45

Пока все, что у вас есть, это &, |, ~, программа, которую я дал, будет работать, никаких проблем. Но если у вас есть более сложные выражения SymPy, вам придется добавить для них собственные переводы, надеюсь, в Z3 есть что-то похожее. Попробуйте и дайте нам знать, если у вас возникнут проблемы.

alias 16.02.2023 14:34

@Aster Предыдущая версия не обрабатывала константы true/false. Я добавил поддержку для тех. Ознакомьтесь с новой программой.

alias 16.02.2023 21:01

Что касается «более сложных» выражений: если все, что вам нужно, это булева алгебра, я думаю, вы можете просто изменить определение table, чтобы добавить любые другие комбинаторы, которые вам нужны, сопоставив их с эквивалентами z3. Мои опасения были больше связаны с общими симпатиями: числами, функциями и т. д .; некоторые из них могут быть переведены на z3 таким образом, но не все из них будут поддерживаться. (Например, sin/cos и т. д. не имеют поддержки SMTLib.) Пока вы придерживаетесь булевой алгебры, я думаю, что эта программа (с расширенным table при необходимости) должна помочь.

alias 16.02.2023 21:03

Я лучше понимаю, что вы имеете в виду! Это не мой случай, на самом деле. Спасибо большое за ваши подробные ответы!

Aster 16.02.2023 22:10

Я не удержался и реализовал простейший преобразователь.

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!")

Сравнение строк для идентификации классов немного ненадежно (и медленно), хотя это хорошее начало! Я опубликовал модифицированную версию в своем ответе, которая должна быть более надежной и, надеюсь, быстрее и проще в использовании.

alias 16.02.2023 06:47

Другие вопросы по теме