-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkb_parser.py
More file actions
230 lines (200 loc) · 6.52 KB
/
kb_parser.py
File metadata and controls
230 lines (200 loc) · 6.52 KB
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import re
from utils import expr, Expr, first
OPERATOR_REPLACEMENT_SYMBOLS = {
'=>':'==>',
'<=':'<==',
'||':'|'
}
OP_IDENTITY = {'&': True, '|': False, '+': 0, '*': 1}
def expr_parse_infix_ops(x):
"""
Converts logical operators' symbols from user-defined format to internal format.
"""
for old, new in OPERATOR_REPLACEMENT_SYMBOLS.items():
regex_pattern = r'(?<!\S)' + re.escape(old) + r'(?!\S)' # Only match operators surrounded by non-word characters
x = re.sub(regex_pattern, new, x)
return x
def parse_sentences(sentences):
"""
Converts a list of sentences into a single complex sentence.
"""
complex_sentence = None
for s in sentences:
p_sentence = expr_parse_infix_ops(s)
if complex_sentence:
complex_sentence += '&' + '(' + p_sentence + ')'
else:
complex_sentence = '(' + p_sentence + ')'
return expr(complex_sentence)
def is_horn_form(kb, query):
"""
Checks if the given KB and query is in Horn form.
"""
kb_valid_horn = True
# Checks KB sentences
for sentence in kb:
clause = expr(expr_parse_infix_ops(sentence))
if not is_definite_clause(clause):
kb_valid_horn = False
break
# Check query sentence/symbol
query_valid_horn = is_prop_symbol(query)
return (kb_valid_horn and query_valid_horn)
def is_definite_clause(s):
"""Returns True for exprs s of the form A & B & ... & C ==> D,
where all literals are positive. In clause form, this is
~A | ~B | ... | ~C | D, where exactly one clause is positive.
>>> is_definite_clause(expr('Farmer(Mac)'))
True
"""
if is_symbol(s.op):
return True
elif s.op == '==>':
antecedent, consequent = s.args
return is_symbol(consequent.op) and all(is_symbol(arg.op) for arg in conjuncts(antecedent))
else:
return False
def is_symbol(s):
"""A string s is a symbol if it starts with an alphabetic char.
>>> is_symbol('R2D2')
True
"""
return isinstance(s, str) and s[:1].isalpha()
def is_prop_symbol(s):
"""A proposition logic symbol is an initial-uppercase string.
>>> is_prop_symbol('exe')
False
"""
return is_symbol(s)
def to_cnf(s):
"""
[Page 253]
Convert a propositional logical sentence to conjunctive normal form.
That is, to the form ((A | ~B | ...) & (B | C | ...) & ...)
>>> to_cnf('~(B | C)')
(~B & ~C)
"""
s = expr(s)
if isinstance(s, str):
s = expr(s)
s = eliminate_implications(s)
s = move_not_inwards(s)
return distribute_and_over_or(s)
def eliminate_implications(s):
"""Change implications into equivalent form with only &, |, and ~ as logical operators."""
s = expr(s)
if not s.args or is_symbol(s.op):
return s # Atoms are unchanged.
args = list(map(eliminate_implications, s.args))
a, b = args[0], args[-1]
if s.op == '==>':
return b | ~a
elif s.op == '<==':
return a | ~b
elif s.op == '<=>':
return (a | ~b) & (b | ~a)
else:
assert s.op in ('&', '|', '~')
return Expr(s.op, *args)
def move_not_inwards(s):
"""Rewrite sentence s by moving negation sign inward.
>>> move_not_inwards(~(A | B))
(~A & ~B)
"""
s = expr(s)
if s.op == '~':
def NOT(b):
return move_not_inwards(~b)
a = s.args[0]
if a.op == '~':
return move_not_inwards(a.args[0]) # ~~A ==> A
if a.op == '&':
return associate('|', list(map(NOT, a.args)))
if a.op == '|':
return associate('&', list(map(NOT, a.args)))
return s
elif is_symbol(s.op) or not s.args:
return s
else:
return Expr(s.op, *list(map(move_not_inwards, s.args)))
def distribute_and_over_or(s):
"""Given a sentence s consisting of conjunctions and disjunctions
of literals, return an equivalent sentence in CNF.
>>> distribute_and_over_or((A & B) | C)
((A | C) & (B | C))
"""
s = expr(s)
if s.op == '|':
s = associate('|', s.args)
if s.op != '|':
return distribute_and_over_or(s)
if len(s.args) == 0:
return False
if len(s.args) == 1:
return distribute_and_over_or(s.args[0])
conj = first(arg for arg in s.args if arg.op == '&')
if not conj:
return s
others = [a for a in s.args if a is not conj]
rest = associate('|', others)
return associate('&', [distribute_and_over_or(c | rest)
for c in conj.args])
elif s.op == '&':
return associate('&', list(map(distribute_and_over_or, s.args)))
else:
return s
def associate(op, args):
"""Given an associative op, return an expression with the same
meaning as Expr(op, *args), but flattened -- that is, with nested
instances of the same op promoted to the top level.
>>> associate('&', [(A&B),(B|C),(B&C)])
(A & B & (B | C) & B & C)
>>> associate('|', [A|(B|(C|(A&B)))])
(A | B | C | (A & B))
"""
args = dissociate(op, args)
if len(args) == 0:
return OP_IDENTITY[op]
elif len(args) == 1:
return args[0]
else:
return Expr(op, *args)
def dissociate(op, args):
"""Given an associative op, return a flattened list result such
that Expr(op, *result) means the same as Expr(op, *args).
>>> dissociate('&', [A & B])
[A, B]
"""
result = []
def collect(subargs):
for arg in subargs:
if arg.op == op:
collect(arg.args)
else:
result.append(arg)
collect(args)
return result
def conjuncts(s):
"""Return a list of the conjuncts in the sentence s.
>>> conjuncts(A & B)
[A, B]
>>> conjuncts(A | B)
[(A | B)]
"""
return dissociate('&', [s])
def disjuncts(s):
"""Return a list of the disjuncts in the sentence s.
>>> disjuncts(A | B)
[A, B]
>>> disjuncts(A & B)
[(A & B)]
"""
return dissociate('|', [s])
def prop_symbols(x):
"""Return the set of all propositional symbols in x."""
if not isinstance(x, Expr):
return set()
elif is_prop_symbol(x.op):
return {x}
else:
return {symbol for arg in x.args for symbol in prop_symbols(arg)}