python z3 解决 dpll 算法

主要思路是转化为SAT问题。在求解SAT问题之前,为了方便将原子命题进行替换。需要将原始命题转换为否定范式,然后再由否定范式转化为合取范式。这样就可以保证整个命题式中只有命题变量,~,∧,∨等符号。主要的实现方法是递归,但是在每一次递归中,我们如果之前对某个原子变量做了替换,就需要先对整个命题进行简化,比如原始命题为 p ∧ q,我们在前一次递归中,将p赋值为True,那么在下一次递归中,就需要通过简化,使得原始命题变成 q 。然后最新的命题中只剩下了原子命题q,我们可以对q进行取值,最终目的是使得整个命题成立。

以下代码主要参考此篇文章:
https://www.pythonf.cn/read/98943

import unittest
from typing import List

from z3 import *

# In this problem, you will implement the DPLL algorithm as discussed
# in the class.


# a utility class to represent the code you should fill in.
class Todo(Exception):
    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return self.msg

    def __repr__(self):
        return self.__str__()


########################################
# This bunch of code declare the syntax for the propositional logic, we
# repeat here:
'''
P ::= p
    | T
    | F
    | P /\ P
    | P \/ P
    | P -> P
    | ~P
'''


class Prop:
    def __repr__(self):
        return self.__str__()


class PropVar(Prop):
    def __init__(self, var):
        self.var = var

    def __str__(self):
        return self.var

    def __hash__(self):
        return hash(self.var)

    def __eq__(self, other):
        return other.__class__ == self.__class__ and self.var == other.var


class PropTrue(Prop):
    def __init__(self):
        pass

    def __str__(self):
        return "True"

    def __eq__(self, other):
        return other.__class__ == self.__class__


class PropFalse(Prop):
    def __init__(self):
        pass

    def __str__(self):
        return "False"

    def __eq__(self, other):
        return other.__class__ == self.__class__


class PropAnd(Prop):
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def __str__(self):
        return f"({self.left} /\\ {self.right})"


class PropOr(Prop):
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def __str__(self):
        return f"({self.left} \\/ {self.right})"


class PropImplies(Prop):
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def __str__(self):
        return f"({self.left} -> {self.right})"


class PropNot(Prop):
    def __init__(self, p):
        self.p = p

    def __str__(self):
        return f"~{self.p}"


# we can convert the above defined syntax into Z3's representation, so
# that we can check it's validity easily:
def to_z3(prop):
    if isinstance(prop, PropVar):
        return Bool(prop.var)
    if isinstance(prop, PropImplies):
        return Implies(to_z3(prop.left), to_z3(prop.right))
    if isinstance(prop, PropTrue):
        return True
    if isinstance(prop, PropFalse):
        return False
    if isinstance(prop, PropAnd):
        return And(to_z3(prop.left), to_z3(prop.right))
    if isinstance(prop, PropOr):
        return Or(to_z3(prop.left), to_z3(prop.right))
    if isinstance(prop, PropNot):
        return Not(to_z3(prop.p))



#####################
# TODO: please implement the nnf(), cnf() and dpll() algorithm, as discussed
# in the class.
#转换成否定范式
def nnf(prop: Prop) -> Prop:
    # 如果含有"->"
    if isinstance(prop, PropImplies):
        return PropOr(PropNot(nnf(prop.left)), nnf(prop.right))
    # 如果含有And
    if isinstance(prop, PropAnd):
        return PropAnd(nnf(prop.left), nnf(prop.right))
    # 如果含有Or
    if isinstance(prop, PropOr):
        return PropOr(nnf(prop.left), nnf(prop.right))
    # 如果含有Not
    if isinstance(prop, PropNot):
        if isinstance(prop.p, PropVar):
            return PropNot(nnf(prop.p))
        if isinstance(prop.p, PropNot):
            return nnf(prop.p.p)
        if isinstance(prop.p, PropTrue):
            return PropFalse()
        if isinstance(prop.p, PropFalse):
            return PropTrue()
        if isinstance(prop.p, PropOr):
            return PropAnd(nnf(PropNot(prop.p.left)), nnf(PropNot(prop.p.right)))
        if isinstance(prop.p, PropAnd):
            return PropOr(nnf(PropNot(prop.p.left)), nnf(PropNot(prop.p.right)))
        if isinstance(prop.p, PropImplies):
            return PropAnd(nnf(prop.p.left), nnf(PropNot(prop.p.right)))
    # 对于命题变量,true,false,直接返回
    return prop


def is_atom(nnf_prop: Prop) -> bool:
    if isinstance(nnf_prop, PropOr) or isinstance(nnf_prop, PropAnd):
        return False
    return True


def cnf(nnf_prop: Prop) -> Prop:
    def cnf_d(left: Prop, right: Prop) -> Prop:
        if isinstance(left, PropAnd):
            return PropAnd(cnf_d(left.left, right), cnf_d(left.right, right))
        if isinstance(right, PropAnd):
            return PropAnd(cnf_d(left, right.left), cnf_d(left, right.right))
        return PropOr(left, right)
    if isinstance(nnf_prop, PropAnd):
        return PropAnd(cnf(nnf_prop.left), cnf(nnf_prop.right))
    if isinstance(nnf_prop, PropOr):
        return cnf_d(cnf(nnf_prop.left), cnf(nnf_prop.right))
    if isinstance(nnf_prop, PropVar):
        return nnf_prop
    if isinstance(nnf_prop, PropNot):
        return PropNot(nnf_prop.p)


def flatten(cnf_prop: Prop) -> List[List[Prop]]:
    """Flatten CNF Propositions to nested list structure .

    The CNF Propositions generated by `cnf` method is AST.
    This method can flatten the AST to a nested list of Props.
    For example: "((~p1 \\/ ~p3) /\\ (~p1 \\/ p4))" can be
    transfer to "[[~p1, ~p3], [~p1, p4]]".

    Parameters
    ----------
    cnf_prop : Prop
        CNF Propositions generated by `cnf` method.

    Returns
    -------
    List[List[Prop]
        A nested list of Props, first level lists are connected by `And`,
        and second level lists is connected by `Or`.

    """

    def get_atom_from_disjunction(prop: Prop) -> List[Prop]:
        if is_atom(prop):
            return [prop]

        if isinstance(prop, PropOr):
            return get_atom_from_disjunction(prop.left) + get_atom_from_disjunction(prop.right)

    if isinstance(cnf_prop, PropAnd):
        return flatten(cnf_prop.left) + flatten(cnf_prop.right)
    elif isinstance(cnf_prop, PropOr):
        return [get_atom_from_disjunction(cnf_prop)]
    elif is_atom(cnf_prop):
        return [[cnf_prop]]



def dpll(prop: Prop) -> dict:
    result = dict()
    flatten_cnf = flatten(cnf(nnf(prop)))

    #初始化字典
    for props in flatten_cnf:
        for pp in props:
            if isinstance(pp, PropVar):
                result[pp.var] = False
            if isinstance(pp, PropNot):
                result[pp.p.var] = False

    # implement the dpll algorithm we've discussed in the lecture
    # you can deal with flattened cnf which generated by `flatten` method for convenience,
    # or, as another choice, you can process the original cnf destructure generated by `cnf` method
    #
    # your implementation should output the result as dict like :
    # {"p1": True, "p2": False, "p3": False, "p4": True} if there is solution;
    # output "unsat" if there is no solution
    #
    # feel free to add new method.
    # 挑选原子命题
    def select_atomic(prop: Prop) -> Prop:
        if isinstance(prop, PropVar):
            return prop
        if isinstance(prop, PropAnd) or isinstance(prop, PropOr):
            if not isinstance(select_atomic(prop.left), PropTrue) and not isinstance(select_atomic(prop.left),
                                                                                     PropFalse):
                return select_atomic(prop.left)
            if not isinstance(select_atomic(prop.right), PropTrue) and not isinstance(select_atomic(prop.right),
                                                                                      PropFalse):
                return select_atomic(prop.right)
        if isinstance(prop, PropNot):
            return select_atomic(prop.p)
    # 简化命题,比如已经用某个命题中的原子命题已经被赋值,那么就可以简化该命题,直接返回,或进行下一轮的原子命题替换
    def unitProp(prop):
        if isinstance(prop, PropTrue) or isinstance(prop, PropFalse) or isinstance(prop, PropVar):
            return prop
        if isinstance(prop, PropAnd):
            if isinstance(prop.left, PropFalse) or isinstance(prop.right, PropFalse):
                return PropFalse()
            elif isinstance(prop.left, PropTrue):
                return unitProp(prop.right)
            elif isinstance(prop.right, PropTrue):
                return unitProp(prop.left)
            else:
                return PropAnd(unitProp(prop.left), unitProp(prop.right))
        if isinstance(prop, PropOr):
            if isinstance(prop, PropTrue) or isinstance(prop, PropTrue):
                return PropTrue()
            elif isinstance(prop.left, PropFalse):
                return unitProp(prop.right)
            elif isinstance(prop.right, PropFalse):
                return unitProp(prop.left)
            else:
                return PropOr(unitProp(prop.left), unitProp(prop.right))
        if isinstance(prop, PropNot):
            if isinstance(prop, PropTrue):
                return PropFalse()
            if isinstance(prop, PropFalse):
                return PropTrue()
            else:
                return prop
    # 将原子命题替换为某个值
    def set_var_value(prop: Prop, x: Prop, value: PropTrue or PropFalse):
        if isinstance(prop, PropVar):
            if prop.var == x.var:
                return value
        if isinstance(prop, PropNot):
            prop.p = set_var_value(prop.p, x, value)
            if isinstance(prop.p, PropTrue):
                return PropFalse()
            if isinstance(prop.p, PropFalse):
                return PropTrue()
        if isinstance(prop, PropOr):
            prop.left = set_var_value(prop.left, x, value)
            prop.right = set_var_value(prop.right, x, value)
            if isinstance(prop.left, PropTrue) or isinstance(prop.right, PropTrue):
                return PropTrue()
            if isinstance(prop.left, PropFalse):
                return prop.right
            if isinstance(prop.right, PropFalse):
                return prop.left
        if isinstance(prop, PropAnd):
            prop.left = set_var_value(prop.left, x, value)
            prop.right = set_var_value(prop.right, x, value)
            if isinstance(prop.left, PropFalse) or isinstance(prop.right, PropFalse):
                return PropFalse()
            if isinstance(prop.left, PropTrue):
                return prop.right
            if isinstance(prop.right, PropTrue):
                return prop.left
        return prop

    # 对命题进行拷贝,用于回溯
    def copy(prop):
        if isinstance(prop, PropVar):
            return PropVar(prop.var)
        if isinstance(prop, PropAnd):
            return PropAnd(copy(prop.left), copy(prop.right))
        if isinstance(prop, PropOr):
            return PropOr(copy(prop.left), copy(prop.right))
        if isinstance(prop, PropNot):
            return PropNot(copy(prop.p))

    def dpll1(prop: Prop, assignment: Prop) -> Prop:
    	#实际使用中,unitProp()可以注释掉,因为该功能被包含在了set_var_value()中
        unitProp(prop)
        if isinstance(prop, PropTrue):
            return prop
        elif isinstance(prop, PropFalse):
            return prop
        p = select_atomic(prop)
        if p is None:
            return prop
        if isinstance(assignment, PropTrue):
            result[p.var] = True
        elif isinstance(assignment, PropFalse):
            result[p.var] = False

        prop = set_var_value(prop, p, assignment)
        prop_copy = copy(prop)
        if isinstance(dpll1(prop, PropTrue()), PropTrue):
            return PropTrue()
        return dpll1(prop_copy, PropFalse())



    newProp = cnf(nnf(prop))
    dpll1(newProp, PropTrue())
    print(result)
    return result



#####################
# test cases:

# p -> (q -> ~p)
test_prop_1 = PropImplies(PropVar('p'), PropImplies(PropVar('q'), PropVar('p')))

# ~((p1 \/ ~p2) /\ (p3 \/ ~p4))
test_prop_2 = PropNot(PropAnd(
    PropOr(PropVar("p1"), PropNot(PropVar("p2"))),
    PropOr(PropVar("p3"), PropNot(PropVar("p4")))
))


# #####################
class TestDpll(unittest.TestCase):

    def test_to_z3_1(self):
        self.assertEqual(str(to_z3(test_prop_1)), "Implies(p, Implies(q, p))")

    def test_to_z3_2(self):
        self.assertEqual(str(to_z3(test_prop_2)), "Not(And(Or(p1, Not(p2)), Or(p3, Not(p4))))")

    def test_nnf_1(self):
        self.assertEqual(str(nnf(test_prop_1)), "(~p \\/ (~q \\/ p))")

    def test_nnf_2(self):
        self.assertEqual(str(nnf(test_prop_2)), "((~p1 /\\ p2) \\/ (~p3 /\\ p4))")

    def test_cnf_1(self):
        self.assertEqual(str(cnf(nnf(test_prop_1))), "(~p \\/ (~q \\/ p))")

    def test_cnf_2(self):
        self.assertEqual(str(cnf(nnf(test_prop_2))),
                         "(((~p1 \\/ ~p3) /\\ (~p1 \\/ p4)) /\\ ((p2 \\/ ~p3) /\\ (p2 \\/ p4)))")

    def test_cnf_flatten_1(self):
        self.assertEqual(str(flatten(cnf(nnf(test_prop_1)))), "[[~p, ~q, p]]")

    def test_cnf_flatten_2(self):
        self.assertEqual(str(flatten(cnf(nnf(test_prop_2)))),
                         "[[~p1, ~p3], [~p1, p4], [p2, ~p3], [p2, p4]]")

    def test_dpll_1(self):
        s = Solver()
        res = dpll(test_prop_1)
        self.assertEqual(str(s.check()), "sat")
        s.add(Not(Implies(res["p"], Implies(res["q"], res["p"]))))
        self.assertEqual(str(s.check()), "unsat")

    def test_dpll_2(self):
        s = Solver()
        res = dpll(test_prop_2)
        s.add(Not(Not(And(Or(res["p1"], Not(res["p2"])), Or(res["p3"], Not(res["p4"]))))))
        self.assertEqual(str(s.check()), "unsat")


if __name__ == '__main__':
    unittest.main()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值