Skip to content

Commit 339f3e6

Browse files
committed
Create fewer auxiliary variables when converting to CNF
We don't need auxiliary variables for nodes that are required to be true for the sentence to be true. Resolves #20.
1 parent 57c8a33 commit 339f3e6

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

nnf/tseitin.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@ def process_node(node: NNF) -> Var:
2626

2727
assert isinstance(node, Internal)
2828

29-
aux = Var.aux()
3029
children = {process_node(c) for c in node.children}
3130

31+
if len(children) == 1:
32+
[child] = children
33+
return child
34+
35+
aux = Var.aux()
36+
3237
if any(~var in children for var in children):
3338
if isinstance(node, And):
3439
clauses.append(Or({~aux}))
@@ -50,8 +55,36 @@ def process_node(node: NNF) -> Var:
5055

5156
return aux
5257

53-
root = process_node(theory)
54-
clauses.append(Or({root}))
58+
@memoize
59+
def process_required(node: NNF) -> None:
60+
"""For nodes that have to be satisfied.
61+
62+
This lets us perform some optimizations.
63+
"""
64+
if isinstance(node, Var):
65+
clauses.append(Or({node}))
66+
return
67+
68+
assert isinstance(node, Internal)
69+
70+
if len(node.children) == 1:
71+
[child] = node.children
72+
process_required(child)
73+
74+
elif isinstance(node, Or):
75+
children = {process_node(c) for c in node.children}
76+
if any(~var in children for var in children):
77+
return
78+
clauses.append(Or(children))
79+
80+
elif isinstance(node, And):
81+
for child in node.children:
82+
process_required(child)
83+
84+
else:
85+
raise TypeError(node)
86+
87+
process_required(theory)
5588
ret = And(clauses)
5689
NNF._is_CNF_loose.set(ret, True)
5790
NNF._is_CNF_strict.set(ret, True)

test_nnf.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,7 @@ def test_tseitin(sentence: nnf.NNF):
858858
T = tseitin.to_CNF(sentence)
859859
assert T.is_CNF()
860860
assert T.is_CNF(strict=True)
861+
assert tseitin.to_CNF(T) == T
861862
assert T.forget_aux().equivalent(sentence)
862863

863864
models = list(complete_models(T.models(), sentence.vars() | T.vars()))
@@ -868,6 +869,20 @@ def test_tseitin(sentence: nnf.NNF):
868869
assert len(models) == sentence.model_count()
869870

870871

872+
@given(CNF())
873+
def test_tseitin_preserves_CNF(sentence: And[Or[Var]]):
874+
assert sentence.to_CNF() == sentence
875+
876+
877+
def test_tseitin_required_detection():
878+
assert a.to_CNF() == And({Or({a})})
879+
assert And().to_CNF() == And()
880+
assert Or().to_CNF() == And({Or()})
881+
assert (a | b).to_CNF() == And({a | b})
882+
assert And({a | b, b | c}).to_CNF() == And({a | b, b | c})
883+
assert And({And({Or({And({~a})})})}).to_CNF() == And({Or({~a})})
884+
885+
871886
@given(models())
872887
def test_complete_models(model: nnf.And[nnf.Var]):
873888
m = {v.name: v.true for v in model}

0 commit comments

Comments
 (0)