1
1
# -*- coding: utf-8 -*-
2
2
3
3
"""NORMALIZATION MODULE
4
+
4
5
Transforms HT propositional formulas into logic programs with the form:
5
6
p1 & p2 & ... & pN -> q1 | q2 | ... | qM
7
+
8
+ Also transforms HT first order formulas into Prenex Normal Form.
6
9
"""
7
10
8
11
import string
@@ -14,6 +17,8 @@ class OP:
14
17
IMPLIES = '>'
15
18
AND = '&'
16
19
OR = '|'
20
+ EXISTS = '/E'
21
+ FORALL = '/F'
17
22
18
23
class LIT :
19
24
"""Enum class for true & false values"""
@@ -26,6 +31,15 @@ def __init__(self, val, left=None, right=None):
26
31
self .l = left
27
32
self .r = right
28
33
34
+ def __repr__ (self ):
35
+ s = ''
36
+ if self .l is not None :
37
+ s += self .l .__repr__ () + ' '
38
+ if self .r is not None :
39
+ s += self .r .__repr__ () + ' '
40
+ s += self .val
41
+ return s
42
+
29
43
def __eq__ (self , node ):
30
44
v = False
31
45
l = False
@@ -48,9 +62,16 @@ def __eq__(self, node):
48
62
v = True
49
63
return (v and l and r )
50
64
65
+ def is_quantifier (self ):
66
+ if (self .val == OP .EXISTS ) or (self .val == OP .FORALL ):
67
+ return True
68
+ else :
69
+ return False
70
+
51
71
def is_literal (self ):
52
72
if ((self .val == OP .NOT ) or (self .val == OP .IMPLIES )
53
- or (self .val == OP .AND ) or (self .val == OP .OR )):
73
+ or (self .val == OP .AND ) or (self .val == OP .OR )
74
+ or (self .val == OP .EXISTS ) or (self .val == OP .FORALL )):
54
75
return False
55
76
else :
56
77
return True
@@ -71,6 +92,9 @@ def get_string(self):
71
92
string += self .r .get_string ()
72
93
return string
73
94
95
+ class MalformedFormulaError (Exception ):
96
+ pass
97
+
74
98
class Formula :
75
99
76
100
separator = ' '
@@ -90,12 +114,82 @@ def build_tree(self, string):
90
114
op2 = stack .pop ()
91
115
n .r = op1
92
116
n .l = op2
117
+ if (s == OP .EXISTS ) or (s == OP .FORALL ):
118
+ op1 = stack .pop ()
119
+ op2 = stack .pop ()
120
+ n .r = op1
121
+ n .l = op2
122
+ if not n .l .is_literal ():
123
+ # Quantifiers always have their bound variable in self.l
124
+ raise MalformedFormulaError (string )
93
125
stack .append (n )
94
126
return stack .pop ()
95
127
96
128
def show (self ):
97
129
self .root .print_tree (0 )
98
130
131
+
132
+ #### First-order functions
133
+
134
+ def prenex (node ):
135
+ """Converts a formula into Prenex Normal Form
136
+
137
+ Arguments:
138
+ node: The root node of the formula tree
139
+ Returns:
140
+ The root node of the formula in PNF
141
+ """
142
+ newnode = node
143
+ if node .val == OP .NOT :
144
+ # Rule 0.0
145
+ if node .r .val == OP .EXISTS :
146
+ newnode = Node (OP .FORALL , left = node .r .l )
147
+ newnode .r = Node (OP .NOT , right = node .r .r )
148
+ # Rule 0.1
149
+ elif node .r .val == OP .FORALL :
150
+ newnode = Node (OP .EXISTS , left = node .r .l )
151
+ newnode .r = Node (OP .NOT , right = node .r .r )
152
+
153
+ # Rules 1 & 2
154
+ elif (node .val == OP .AND ) or (node .val == OP .OR ):
155
+ if node .l .is_quantifier ():
156
+ newnode = Node (node .l .val , left = node .l .l , right = Node (node .val ))
157
+ newnode .r .l = node .l .r
158
+ newnode .r .r = node .r
159
+ if node .r .is_quantifier ():
160
+ newnode = Node (node .r .val , left = node .r .l , right = Node (node .val ))
161
+ newnode .r .l = node .l
162
+ newnode .r .r = node .r .r
163
+
164
+ elif node .val == OP .IMPLIES :
165
+ # Rule 3
166
+ if node .r .is_quantifier ():
167
+ newnode = Node (node .r .val , left = node .r .l , right = Node (OP .IMPLIES ))
168
+ newnode .r .l = node .l
169
+ newnode .r .r = node .r .r
170
+ # Rule 4.0
171
+ if node .l .val == OP .EXISTS :
172
+ newnode = Node (OP .FORALL , left = node .l .l , right = Node (OP .IMPLIES ))
173
+ newnode .r .l = node .l .r
174
+ newnode .r .r = node .r
175
+ # Rule 4.1
176
+ elif node .l .val == OP .FORALL :
177
+ newnode = Node (OP .EXISTS , left = node .l .l , right = Node (OP .IMPLIES ))
178
+ newnode .r .l = node .l .r
179
+ newnode .r .r = node .r
180
+
181
+ # Recursive call
182
+ if not newnode .is_literal ():
183
+ if newnode .val == OP .NOT :
184
+ newnode .r = prenex (newnode .r )
185
+ else :
186
+ newnode .l = prenex (newnode .l )
187
+ newnode .r = prenex (newnode .r )
188
+ return newnode
189
+
190
+
191
+ #### Propositional-only functions
192
+
99
193
def nnf (node ):
100
194
"""Converts a formula into Negation Normal Form
101
195
@@ -104,41 +198,47 @@ def nnf(node):
104
198
Returns:
105
199
The root node of the formula in NNF
106
200
"""
107
-
108
201
newnode = node
109
202
if node .val == OP .NOT :
110
203
if node .r .is_literal ():
204
+ # Rule 1
111
205
if node .r .val == LIT .TRUE :
112
206
newnode = Node (LIT .FALSE )
207
+ # Rule 2
113
208
elif node .r .val == LIT .FALSE :
114
209
newnode = Node (LIT .TRUE )
115
210
return newnode
116
211
else :
117
212
if node .r .val == OP .NOT :
213
+ # Rule 3
118
214
if node .r .r .val == OP .NOT :
119
215
newnode = node .r .r
120
216
else :
121
217
newnode .r = nnf (node .r )
122
218
return newnode
219
+ # Rule 4
123
220
elif node .r .val == OP .AND :
124
221
newnode = Node (OP .OR )
125
222
newnode .l = Node (OP .NOT )
126
223
newnode .l .r = node .r .l
127
224
newnode .r = Node (OP .NOT )
128
225
newnode .r .r = node .r .r
226
+ # Rule 5
129
227
elif node .r .val == OP .OR :
130
228
newnode = Node (OP .AND )
131
229
newnode .l = Node (OP .NOT )
132
230
newnode .l .r = node .r .l
133
231
newnode .r = Node (OP .NOT )
134
232
newnode .r .r = node .r .r
233
+ # Rule 6
135
234
elif node .r .val == OP .IMPLIES :
136
235
newnode = Node (OP .AND )
137
236
newnode .l = Node (OP .NOT )
138
237
newnode .l .r = Node (OP .NOT )
139
238
newnode .l .r .r = node .r .l
140
239
newnode .r = Node (OP .NOT )
141
240
newnode .r .r = node .r .r
241
+ # Recursive call
142
242
if not newnode .is_literal ():
143
243
if newnode .val == OP .NOT :
144
244
newnode = nnf (newnode )
@@ -298,10 +398,10 @@ def apply_substitution(f, side):
298
398
for rule in substitution_rules [side ]:
299
399
applicable , result = rule (f )
300
400
if applicable :
301
- for i in result :
302
- for j in i :
303
- for h in j :
304
- print h .get_string ()
401
+ # for i in result:
402
+ # for j in i:
403
+ # for h in j:
404
+ # print h.get_string()
305
405
return result
306
406
return []
307
407
@@ -598,10 +698,72 @@ def test_paper_example(self):
598
698
'-p & q > ' }
599
699
self .assertEqual (normalization (f ), s )
600
700
701
+ class PrenexTest (unittest .TestCase ):
702
+
703
+ r0_1 = Formula ('x p(x) q(x) & /E -' )
704
+ r0_2 = Formula ('x p(x) /F -' )
705
+
706
+ r1_1 = Formula ('x s(x) r(x) & /E p &' )
707
+ r1_2 = Formula ('p x s(x) r(x) & /F &' )
708
+
709
+ r2_1 = Formula ('p x s(x) r(x) & /E |' )
710
+ r2_2 = Formula ('x s(x) r(x) & /F p |' )
711
+
712
+ r3_1 = Formula ('p x q(x) /E >' )
713
+ r3_2 = Formula ('p x q(x) r(x) | /F >' )
714
+
715
+ r4_1 = Formula ('x p(x) /E q >' )
716
+ r4_2 = Formula ('x q(x) r(x) & /F p >' )
717
+
718
+ nested1 = Formula ('p x y q(x) /E /F >' )
719
+
720
+ def test_r0 (self ):
721
+ s1 = 'x p(x) q(x) & - /F'
722
+ s2 = 'x p(x) - /E'
723
+ self .assertEqual (str (prenex (self .r0_1 .root )), s1 )
724
+ self .assertEqual (str (prenex (self .r0_2 .root )), s2 )
725
+
726
+ def test_r1 (self ):
727
+ s1 = 'x s(x) r(x) & p & /E'
728
+ s2 = 'x p s(x) r(x) & & /F'
729
+ self .assertEqual (str (prenex (self .r1_1 .root )), s1 )
730
+ self .assertEqual (str (prenex (self .r1_2 .root )), s2 )
731
+
732
+ def test_r2 (self ):
733
+ s1 = 'x p s(x) r(x) & | /E'
734
+ s2 = 'x s(x) r(x) & p | /F'
735
+ self .assertEqual (str (prenex (self .r2_1 .root )), s1 )
736
+ self .assertEqual (str (prenex (self .r2_2 .root )), s2 )
737
+
738
+ def test_r3 (self ):
739
+ s1 = 'x p q(x) > /E'
740
+ s2 = 'x p q(x) r(x) | > /F'
741
+ self .assertEqual (str (prenex (self .r3_1 .root )), s1 )
742
+ self .assertEqual (str (prenex (self .r3_2 .root )), s2 )
743
+
744
+ def test_r4 (self ):
745
+ s1 = 'x p(x) q > /F'
746
+ s2 = 'x q(x) r(x) & p > /E'
747
+ self .assertEqual (str (prenex (self .r4_1 .root )), s1 )
748
+ self .assertEqual (str (prenex (self .r4_2 .root )), s2 )
749
+
750
+ def test_mixed (self ):
751
+ pass
752
+
753
+ def test_nested (self ):
754
+ s1 = 'x y p q(x) > /E /F'
755
+ self .assertEqual (str (prenex (self .nested1 .root )), s1 )
756
+
757
+ def test_malformed_formula (self ):
758
+ def build_malformed ():
759
+ Formula ('p x y & s(x) r(x) & /F &' )
760
+ self .assertRaises (MalformedFormulaError , build_malformed )
761
+
762
+
601
763
if __name__ == '__main__' :
602
764
603
765
#TODO: adapt for subsumed+taut checking
604
- # unittest.main()
766
+ unittest .main ()
605
767
606
768
f = NormTest .constraint
607
769
f .show ()
0 commit comments