Skip to content

Commit e2d554d

Browse files
authored
Merge pull request #22 from QuMuLab/smarter-tseitin
Create fewer auxiliary variables when converting to CNF
2 parents 57c8a33 + 339f3e6 commit e2d554d

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)