-
Notifications
You must be signed in to change notification settings - Fork 0
/
cnf.py
156 lines (144 loc) · 5.01 KB
/
cnf.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import parser
debug = True
def rimp(tree):
'''
Replaces all implications and equivalences from the given
tree, replacing them as follows:
A -> B => ¬A V B
A <-> B => (A & B) V (¬A & ¬B)
'''
if(len(tree) == 1): # Literal
if debug:
print("[rimp] Literal: " + parser.tostring(tree))
return tree
if tree[0] == ':': # Implication
ret_val = ('|', ('-', rimp(tree[1])), rimp(tree[2]))
if debug:
print("[rimp] Removing Implication: " + parser.tostring(tree) + " => " + parser.tostring(ret_val))
return ret_val
if tree[0] == '=': # Equivalence
ret_val = ('|', ('&', rimp(tree[1]), rimp(tree[2])),
('&', ('-', rimp(tree[1])), ('-', rimp(tree[2]))))
print("[rimp] Removing Equivalence: " + parser.tostring(tree) + " => " + parser.tostring(ret_val))
return ret_val
if debug:
print("[rimp] No cases matched.")
if tree[0] == '-':
return (tree[0], rimp(tree[1])) # Negation
return (tree[0], rimp(tree[1]), rimp(tree[2])) # Anything else
def rneg(tree):
'''
Pushes all negations to the leaves of the tree - i.e. the
only negations left are on the literals (eg. -A).
'''
if(len(tree) == 1): # Literal
if debug:
print("[rneg] Literal: " + parser.tostring(tree))
return tree
root = tree[0]
left = tree[1]
if root == '-': # Negation
op = left[0]
if(len(left) == 1):
return tree
left = left[1]
if op == '-': # Double Negation
ret_val = rneg(left)
if debug:
print("[rneg] Double Negation: " + parser.tostring(tree) + " => " + parser.tostring(ret_val))
return ret_val
right = tree[1][2]
if op == '&' or op == '|': # DeMorgan's
op = '|' if op == '&' else '|' # Swap the op over
ret_val = (op, rneg(('-', left)), rneg(('-', right)))
if debug:
print("[rneg] DeMorgan's on: " + parser.tostring(tree) + " => " + parser.tostring(ret_val))
return ret_val
if debug:
print("[rneg] " + parser.tostring(tree) + " : No cases matched.")
return (root, rneg(tree[1]), rneg(tree[2]))
def dist(tree):
'''
Distributes over Vs until we are in CNF.
'''
if len(tree) == 1: # Literal
if debug:
print("[dist] Literal : " + parser.tostring(tree))
return tree
root = tree[0]
if root == '|':
left = tree[1]
right = tree[2]
leftop = left[0]
rightop = right[0]
# P V (Q & R) => (P V Q) & (P V R)
if rightop == '&':
ret_val = ('&', ('|', left, right[1]), ('|', left, right[2]))
if debug:
print("[dist] V-Distributivity on right : " + parser.tostring(tree) + " => " + parser.tostring(ret_val))
return ret_val
# (P & Q) V R => (P V R) & (Q V R)
if leftop == '&':
ret_val = ('&', ('|', left[1], right), ('|', left[2], right))
if debug:
print("[dist] V-Distributivity on left : " + parser.tostring(tree) + " => " + parser.tostring(ret_val))
return ret_val
if debug:
print("[dist] " + parser.tostring(tree) + " : No cases matched.")
if root == '-':
return (root, dist(tree[1]))
return (root, dist(tree[1]), dist(tree[2]))
def cnf_tree(tree):
'''
Returns the Conjunctive Normal Form tree of the given
initial tree. (combination of dist, rneg and rimp).
'''
return dist(rneg(rimp(tree)))
def cnf_pretty(tree):
'''
Pretty-prints the Clause NF of the given tree
'''
cnf_t = cnf_tree(tree)
cs = parser.tostring(cnf_t).split("&")
ret = ""
for c in cs:
ret += "("
ret += c
ret += ")"
ret += " & "
return ret[:-3] # :-3 so &s are interspersed properly...
def clause_nf(tree):
'''
Takes a PL tree and converts it to clause NF. Returns a
set of sets (i.e. {{...}, {...}, {...}, {...}}) where each
inner set represents a clause (i.e. a disjunction of
literals.
Converts to a non-'paren-ned' version and splits on & then
| to give lists to convert to sets.
'''
cnf = rationalise(set([frozenset(x) for x in [l.split('|') for l in parser.noptostring(cnf_tree(tree)).split('&')]]))
if debug:
print("[clause_nf] Clause Normal Form : " + str(cnf))
return cnf
def rationalise(s):
'''
Takes a set in CNF (see clause_nf function docstring), and removes any
sets with rationalisale elements - eg. {A, -A} => T
'''
ret_set = set([])
for c in s:
for l in c:
if(not neg(l) in c):
ret_set.add(c)
return ret_set
def neg(l):
'''
Returns the negated form of a literal:
L => -L
-L => L
'''
return (l[1] if len(l) > 1 else "-" + l)
# Tests
# rimp tests
# rneg tests
# dist tests