def recursion_tseitins_transformation(subformula: SatFormula):
        if subformula is None or subformula.is_leaf:
            return []

        if subformula.left.is_leaf:
            l_var = subformula.left.value
        else:
            if subformula.left.idx not in new_variables:
                new_variables[subformula.left.idx] = Literal.from_name(
                    "tse{}".format(subformula.left.idx), negated=False)
            l_var = new_variables[subformula.left.idx]

        if subformula.operator is Operator.NEGATION or subformula.right is None:
            r_var = None
        else:
            if subformula.right.is_leaf:
                r_var = subformula.right.value
            else:
                if subformula.right.idx not in new_variables:
                    new_variables[subformula.right.idx] = Literal.from_name(
                        "tse{}".format(subformula.right.idx), negated=False)
                r_var = new_variables[subformula.right.idx]

        if subformula.idx not in new_variables:
            new_variables[subformula.idx] = Literal.from_name("tse{}".format(
                subformula.idx),
                                                              negated=False)

        return convert_iff_cnf(new_variables[subformula.idx], rhs1=l_var, rhs2=r_var, operation=subformula.operator) + \
               recursion_tseitins_transformation(subformula.right) + recursion_tseitins_transformation(subformula.left)
def get_complicated_graph() -> (ImplicationGraph, int, List[Variable]):
    variables = [Variable('TEMP')
                 ] + [Variable('x{}'.format(i + 1)) for i in range(10)]
    pos_l = [Literal(v, negated=False) for v in variables]
    neg_l = [Literal(v, negated=True) for v in variables]

    c0 = [pos_l[10]
          ]  # unused clause to keep the numbering aligned to the slides
    c1 = [pos_l[2], pos_l[3]]
    c2 = [pos_l[9]
          ]  # unused clause to keep the numbering aligned to the slides
    c3 = [neg_l[3], neg_l[4]]
    c4 = [neg_l[4], neg_l[2], neg_l[1]]
    c5 = [neg_l[6], neg_l[5], pos_l[4]]
    c6 = [pos_l[7], pos_l[5]]
    c7 = [neg_l[8], pos_l[7], pos_l[6]]
    conflict = [c0, c1, c2, c3, c4, c5, c6, c7]

    clauses = conflict
    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)

    g = ImplicationGraph(cnf)

    g.add_decide_node(1, pos_l[1])
    g.add_decide_node(2, pos_l[8])
    g.add_decide_node(3, pos_l[7])

    g.add_node(3, pos_l[6], None, 7)
    g.add_node(3, pos_l[5], None, 6)
    g.add_node(3, pos_l[4], None, 5)
    g.add_node(3, neg_l[3], None, 3)
    g.add_node(3, neg_l[2], None, 4)

    return g, 1, variables
def test_find_all_paths():
    vars = [Variable('x{}'.format(i + 1)) for i in range(8)]
    pos_l = [None] + [Literal(v, negated=False) for v in vars]
    neg_l = [None] + [Literal(v, negated=True) for v in vars]

    clauses = [[pos_l[1]], [neg_l[1], pos_l[5]], [neg_l[1], pos_l[3]],
               [neg_l[2], neg_l[3], pos_l[4]], [neg_l[5], pos_l[2]]]
    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)
    g = ImplicationGraph(cnf)

    g.add_decide_node(1, pos_l[1])
    g.add_node(1, pos_l[3], None, 2)
    g.add_node(1, pos_l[5], None, 1)
    g.add_node(1, pos_l[2], None, 4)
    # g.add_node(1, pos_l[2], None, 3)
    g.add_node(1, pos_l[4], None, 3)
    source_node = g._nodes[pos_l[1]]
    target_node = g._nodes[pos_l[4]]
    actual_paths = g._find_all_paths(source_node, target_node)
    expected_paths = [[
        pos_l[1].variable, pos_l[5].variable, pos_l[2].variable,
        pos_l[4].variable
    ], [pos_l[1].variable, pos_l[3].variable, pos_l[4].variable]]

    assert {(frozenset(item))
            for item in actual_paths} == {(frozenset(item))
                                          for item in expected_paths}
def test_learn_conflict_complicated():
    g, conflict_idx, variables = get_complicated_graph()

    conflict_clause = g.learn_conflict(Literal(variables[3], True),
                                       conflict_idx)
    expected_conflict_clause = [
        Literal(variables[1], True),
        Literal(variables[4], True)
    ]
    assert sorted(conflict_clause) == sorted(expected_conflict_clause)
def test_boolean_resolution():
    vars = [Variable('x{}'.format(i + 1)) for i in range(10)]
    pos_l = [None] + [Literal(v, negated=False) for v in vars]
    neg_l = [None] + [Literal(v, negated=True) for v in vars]

    c0 = [pos_l[2], pos_l[3]]
    c1 = [neg_l[4], neg_l[3]]
    c_tag = ImplicationGraph.boolean_resolution(c0, c1, pos_l[3].variable)
    expected_c_tag = [pos_l[2], neg_l[4]]
    assert sorted(c_tag) == sorted(expected_c_tag)

    c0 = [pos_l[2], neg_l[3], neg_l[5]]
    c1 = [neg_l[4], pos_l[3], pos_l[1]]
    c_tag = ImplicationGraph.boolean_resolution(c0, c1, pos_l[3].variable)
    expected_c_tag = [pos_l[2], neg_l[4], neg_l[5], pos_l[1]]
    assert sorted(c_tag) == sorted(expected_c_tag)
def test_backjump_simple():
    g, conflict_idx, variables = get_simple_graph()

    conflict_clause = g.learn_conflict(Literal(variables[3], False),
                                       conflict_idx)
    level = g.get_backjump_level(conflict_clause)
    expected_level = 0
    assert level == expected_level
def test_backjump_complicated():
    g, conflict_idx, variables = get_complicated_graph()

    conflict_clause = g.learn_conflict(Literal(variables[3], True),
                                       conflict_idx)
    level = g.get_backjump_level(conflict_clause)
    expected_level = 1
    assert level == expected_level
def create_random_query(num_variables=5, num_clauses=4, clause_length=3):
    assert clause_length <= num_variables
    variables = [Variable('x{}'.format(i + 1)) for i in range(num_variables)]
    pos_l = [Literal(v, negated=False) for v in variables]
    neg_l = [Literal(v, negated=True) for v in variables]

    clauses = []
    for _ in range(num_clauses):
        clause = []
        vars = sample(range(0, num_variables), clause_length)
        for i in range(clause_length):
            if random() > 0.5:
                clause.append(neg_l[vars[i]])
            else:
                clause.append(pos_l[vars[i]])
        clauses.append(clause)

    return clauses
Beispiel #9
0
def test_search_complex_unsat():
    variables = [Variable('TEMP')
                 ] + [Variable('x{}'.format(i + 1)) for i in range(10)]
    pos_l = [Literal(v, negated=False) for v in variables]
    neg_l = [Literal(v, negated=True) for v in variables]

    # x1 = T, x2 = F, x3 =
    # x3 != x2 /\ x1 != x2 /\ (!x1 \/ x2 \/ !x3) /\ (x1 \/ !x2 \/ x3)
    clauses = [[pos_l[1], pos_l[2]], [neg_l[1], neg_l[2]],
               [pos_l[3], pos_l[2]], [neg_l[3], neg_l[2]],
               [neg_l[1], pos_l[2], neg_l[3]], [pos_l[3], neg_l[2], pos_l[1]]]

    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)
    dpll = DPLL(cnf)
    search_result = dpll.search()
    # print(dpll.get_assignment())
    assert not search_result
Beispiel #10
0
def test_search_simple_unsat():
    # (x1|~x2) | (~x1 | x2)
    x1_var = Variable('x1')
    x2_var = Variable('x2')
    # x3_var = Variable('x3')

    x1 = Literal(x1_var, negated=False)
    not_x1 = Literal(x1_var, negated=True)
    x2 = Literal(x2_var, negated=False)
    not_x2 = Literal(x2_var, negated=True)

    clauses = [[x1], [x2], [not_x1, not_x2]]
    # literal_to_clauses = {x1: {0}, x2: {0}, x3: {0}} #not_x1: {2}, not_x2: {0}
    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)
    dpll = DPLL(cnf)
    search_result = dpll.search()

    assert not search_result
def get_simple_graph() -> (ImplicationGraph, int, List[Variable]):
    variables = [Variable('TEMP')
                 ] + [Variable('x{}'.format(i + 1)) for i in range(8)]
    pos_l = [Literal(v, negated=False) for v in variables]
    neg_l = [Literal(v, negated=True) for v in variables]

    # x1 =True --> x3=True, x5=True  --> x2 = True,
    clauses = [[pos_l[1]], [neg_l[1], pos_l[5]], [neg_l[1], pos_l[3]],
               [neg_l[2], neg_l[3]], [neg_l[5], pos_l[2]]]
    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)
    g = ImplicationGraph(cnf)

    g.add_decide_node(1, pos_l[1])
    g.add_node(1, pos_l[3], None, 2)
    g.add_node(1, pos_l[5], None, 1)
    g.add_node(1, neg_l[2], None, 4)
    # g.add_node(1, pos_l[2], None, 3)
    # g.add_node(1, pos_l[4], None, 3)

    return g, 3, variables
 def __init__(self, cnf_formula: CnfFormula):
     self._nodes = {}  # type: Dict[Variable, Node]
     self._edges = {}  # type: Dict[Node, List[Edge]]
     self._nodes_order = {}  # type: Dict[Variable, int]
     self._level_to_nodes = defaultdict(
         set)  # type: Dict[int, Set[Variable]]
     self._incoming_edges = defaultdict(
         list)  # type: Dict[Node, List[Edge]]
     self._conflict_var = Literal(Variable('Conflict'), False)
     self._conflict_node = Node(self._conflict_var, -1)
     self._last_decide_node = None
     self._formula = cnf_formula
Beispiel #13
0
def test_up_unsat():
    # (x1|~x2|x3)&x2&(~x1|x3)&(x2|~x3) --> UNSAT
    x1_var = Variable('x1')
    x2_var = Variable('x2')
    x3_var = Variable('x3')

    x1 = Literal(x1_var, negated=False)
    not_x1 = Literal(x1_var, negated=True)
    x2 = Literal(x2_var, negated=False)
    not_x2 = Literal(x2_var, negated=True)
    x3 = Literal(x3_var, negated=False)
    not_x3 = Literal(x3_var, negated=True)

    clauses = [[x1, not_x2, x3], [x2], [not_x1, x3], [not_x2, not_x3]]
    # literal_to_clauses = {x1: {0}, not_x1: {2}, not_x2: {0, 3}, x2: {1}, x3: {0, 2}, not_x3: {3}}

    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)

    dpll = DPLL(cnf)
    actual_cnf = dpll.unit_propagation()

    assert actual_cnf is None
    assert not dpll.get_full_assignment()[x3_var]
    assert dpll.get_full_assignment()[x2_var]
Beispiel #14
0
def test_multi_level_conflict_sat():
    vars = [Variable('x{}'.format(i + 1)) for i in range(8)]
    pos_l = [None] + [Literal(v, negated=False) for v in vars]
    neg_l = [None] + [Literal(v, negated=True) for v in vars]

    c1 = [pos_l[2], pos_l[3]]
    # c2 = [pos_l[1], pos_l[4], neg_l[8]]
    c3 = [neg_l[3], neg_l[4]]
    c4 = [neg_l[4], neg_l[2], neg_l[1]]
    c5 = [neg_l[6], neg_l[5], pos_l[4]]
    c6 = [pos_l[7], pos_l[5]]
    c7 = [neg_l[8], pos_l[7], pos_l[6]]
    conflict = [c3, c4, c5, c6, c7, c1]  # c2,
    # If we implement pure_literal will need to change this
    # this is just to make sure the order decisions will be: x1=True, x2=True, x3=True, the conflict is because x1
    n_temps = 4
    temp_literals = [
        Literal(Variable('x1_temp{}'.format(idx)), negated=False)
        for idx in range(n_temps)
    ]
    x1_clauses = [[pos_l[1], l] for l in temp_literals]
    temp_literals = [
        Literal(Variable('x8_temp{}'.format(idx)), negated=False)
        for idx in range(n_temps)
    ]
    x8_clauses = [[pos_l[8], l] for l in temp_literals[:-1]]
    temp_literals = [
        Literal(Variable('x7_temp{}'.format(idx)), negated=False)
        for idx in range(n_temps)
    ]
    x7_clauses = [[neg_l[7], l] for l in temp_literals[:-2]]

    clauses = x1_clauses + x8_clauses + x7_clauses + conflict
    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)
    dpll = DPLL(cnf)
    search_result = dpll.search()

    assert search_result
Beispiel #15
0
def test_up_simple():
    # (x1|~x2|x3)&x2&(~x1|x3) --> (x1|x3) & (~x1|x3)
    x1_var = Variable('x1')
    x2_var = Variable('x2')
    x3_var = Variable('x3')

    x1 = Literal(x1_var, negated=False)
    not_x1 = Literal(x1_var, negated=True)
    x2 = Literal(x2_var, negated=False)
    not_x2 = Literal(x2_var, negated=True)
    x3 = Literal(x3_var, negated=False)

    clauses = [[x1, not_x2, x3], [x2], [not_x1, x3]]

    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)
    dpll = DPLL(cnf)
    actual_cnf = dpll.unit_propagation()
    expected_cnf = [[x1, not_x2, x3], [not_x1, x3]]

    assert dpll.get_full_assignment()[x2_var]
    actual_cnf_real = [cl for cl in actual_cnf.clauses if cl != []]
    assert actual_cnf_real == expected_cnf, dpll.get_full_assignment()
    def conflict(self, partial_assignment: Dict[Variable,
                                                bool]) -> List[Literal]:
        '''
        Check if there is a conflict, is so return the reason clause (as a list of literals) which is a negation of the
        current assignment
        otherwise, empty list
        :param partial_assignment:
        :return:
        '''

        conflict = []

        # No conflicts
        if self.satisfied(partial_assignment):
            return conflict

        for v, value in partial_assignment.items():
            if v not in self.var_equation_mapping:
                continue
            equation = self.equations[self.var_equation_mapping[v]]
            negated_value = value
            literal = Literal(equation.fake_variable, negated_value)
            conflict.append(literal)
        return conflict
Beispiel #17
0
def test_search_simple():
    # (x1|x2|~x3) | (x3 | ~x2)
    x1_var = Variable('x1')
    x2_var = Variable('x2')
    x3_var = Variable('x3')

    x1 = Literal(x1_var, negated=False)
    not_x1 = Literal(x1_var, negated=True)
    x2 = Literal(x2_var, negated=False)
    not_x2 = Literal(x2_var, negated=True)
    x3 = Literal(x3_var, negated=False)
    not_x3 = Literal(x3_var, negated=True)

    clauses = [[x1, x2, not_x3], [x3, not_x2]]
    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)
    dpll = DPLL(cnf)
    search_result = dpll.search()
    assert search_result
Beispiel #18
0
def test_multi_level_deduction_sat():
    x1_var = Variable('x1')
    x2_var = Variable('x2')
    x3_var = Variable('x3')

    x1 = Literal(x1_var, negated=False)
    not_x1 = Literal(x1_var, negated=True)
    x2 = Literal(x2_var, negated=False)
    not_x2 = Literal(x2_var, negated=True)
    x3 = Literal(x3_var, negated=False)
    not_x3 = Literal(x3_var, negated=True)

    clauses = [[x1], [x3, x2], [not_x2, not_x3, not_x1]]
    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)
    dpll = DPLL(cnf)
    search_result = dpll.search()

    assert search_result
 def _negate_assignment(assignment: Dict[Variable, bool]) -> List[Literal]:
     res = []
     for k, v in assignment.items():
         res.append(Literal(k, negated=not v))
     return res
Beispiel #20
0
    def search(self) -> bool:
        '''
        :return: True if Sat else False
        '''

        self.rec_num += 1
        if self.formula is None:
            # Formula is unsat
            return False
        real_formula = [f for f in self.formula.clauses if f != []]
        if not real_formula:
            if not self.check_theory_conflict():
                # The current assignment satisfies all clauses but theory finds a conflict
                return False
            # Found valid assignment
            self.unsat = False
            return True

        # it and try again, the second literal might not be very helpful
        self.check_theory_conflict()
        if self.propagate_helper:
            smt_res = self.propagate_helper(self._assignment[-1])
            # smt_res will be None if partial_assignment is empty
            if smt_res:
                self._assignment[-1].update(smt_res)
                for variable, val in smt_res.items():
                    if val:
                        lit = Literal(variable, False)
                    else:
                        lit = Literal(variable, True)

                    if not self.remove_clauses_by_assignment(lit):
                        self.formula.clauses = [[]]
                        return self.formula

                if not smt_res:
                    # Conflict
                    return False

        next_decision_lit = self.decision_heuristic()
        if next_decision_lit is None:
            return False
        assert isinstance(next_decision_lit, Literal)
        next_decision = next_decision_lit.variable
        assert isinstance(next_decision, Variable)

        assignment_order = [False, True
                            ] if next_decision_lit.negated else [True, False]
        for d in assignment_order:
            if self.backjump and self.backjump > len(self._assignment) - 1:
                # Skip this level
                self.implication_graph.remove_level(len(self._assignment) - 1)
                return False
            elif self.backjump == len(self._assignment) - 1:
                # Finished to do backjump
                self.backjump = None
            # current_assignment = copy(self._assignment)
            cur_assignment = self._assignment
            cur_assignment.append(copy(self._assignment[-1]))

            dpll = DPLL(deepcopy(self.formula),
                        watch_literals=self.watch_literals,
                        partial_assignment=cur_assignment,
                        implication_graph=self.implication_graph,
                        is_first_run=False,
                        conflict_helper=self.conflict_helper,
                        propagate_helper=self.propagate_helper,
                        rec_num=self.rec_num)
            dpll.assign_true_to_literal(Literal(next_decision, not d),
                                        reason=None)

            sat = dpll.search()
            if dpll.learned_sat_conflicts:
                self.formula.add_clause(dpll.learned_sat_conflicts)
                self.create_watch_literals(len(self.formula.clauses) - 1)
                self.backjump = dpll.backjump

            if sat:
                if not self.check_theory_conflict():
                    # The current assignment satisfies all clauses but theory finds a conflict
                    return False
                self.unsat = False
                return sat

            # clear the last decision
            decision_level = len(self._assignment) - 1
            self.implication_graph.remove_level(decision_level)
            self._assignment.pop()

        return False
Beispiel #21
0
import pytest

from sat_solver.cnf_formula import CnfFormula
from sat_solver.preprocessor import remove_redundant_literals, delete_trivial_clauses
from sat_solver.sat_formula import Literal, Variable

v1 = Variable(name='v1')
v2 = Variable(name='v2')

l1 = Literal(v1, negated=False)
l2 = Literal(v1, negated=True)
l3 = Literal(v2, negated=False)
l4 = Literal(v2, negated=True)


def test_literal_to_clauses():
    formula = CnfFormula([[l1, l2], [l1, l1, l3]])
    expected_literal_to_clause = {l1: {0, 1}, l2: {0}, l3: {1}}

    actual_formula = remove_redundant_literals(formula)

    assert str(expected_literal_to_clause) == str(
        dict(actual_formula.literal_to_clauses))


def test_remove_redundant_literals():
    formula = CnfFormula([[l1, l2], [l1, l1, l3]])
    expected_formula = CnfFormula([[l1, l2], [l1, l3]])

    actual_formula = remove_redundant_literals(formula)
Beispiel #22
0
def test_search_complex():
    # (~x|z) & (~x|~z|~y) & (~z|w) & (~w|~y) (lec3, slide 18)
    # (~x1 | x2) & (~x1 | ~x2 | ~x3) & (~x2 | x4) & (~x4| ~x3)
    x1_var = Variable('x1')
    x2_var = Variable('x2')
    x3_var = Variable('x3')
    x4_var = Variable('x4')
    x5_var = Variable('x5')
    x6_var = Variable('x6')

    x1 = Literal(x1_var, negated=False)
    not_x1 = Literal(x1_var, negated=True)
    x2 = Literal(x2_var, negated=False)
    not_x2 = Literal(x2_var, negated=True)
    x3 = Literal(x3_var, negated=False)
    not_x3 = Literal(x3_var, negated=True)
    x4 = Literal(x4_var, negated=False)
    not_x4 = Literal(x4_var, negated=True)

    x5 = Literal(x5_var, negated=False)
    not_x5 = Literal(x5_var, negated=True)
    x6 = Literal(x6_var, negated=False)
    not_x6 = Literal(x6_var, negated=True)
    # (~x1 | x2) & (~x1 | ~x2 | ~x3) & (~x2 | x4) & (~x4| ~x3)
    clauses = [[not_x1, x2], [not_x1, not_x2, not_x3], [not_x2, x4],
               [not_x4, not_x3], [x5, x6], [not_x5, not_x6]]
    cnf = CnfFormula(clauses)
    cnf = preprocess(cnf)
    dpll = DPLL(cnf)
    search_result = dpll.search()
    assert search_result
 def add_literal(self, negated: bool):
     self.fake_literals[negated] = Literal(self.fake_variable, negated)