-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMicroPAM.py
More file actions
232 lines (177 loc) · 7.34 KB
/
Copy pathMicroPAM.py
File metadata and controls
232 lines (177 loc) · 7.34 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
# Comments
# MicroPAM (p187) ------------------------------------------------------
from cd_functions import filler_role, header_cd, instantiate, match
from extra_functions import is_predication
from lisp_functions import atom, numberp
from mcpam_functions import constraint_side, lhs, pattern_side, rhs
class MicroPAM:
init_rules: list
sub_for: list
plans_for: list
instance_of: list
inference_rules: list
known_themes: list
known_goals: list
known_plans: list
data_base: list
isa_props: dict
def __init__(self, init_rules: list, sub_for: list, plans_for: list, instance_of: list, isa_props: dict):
self.init_rules = init_rules
self.sub_for = sub_for
self.plans_for = plans_for
self.instance_of = instance_of
self.inference_rules = instance_of + plans_for + sub_for + init_rules
self.isa_props = isa_props
self.clear_globals()
def justify(self, input: list, log: list[str]):
# Tries to find an explanation for `input` (a CD predication)
log.append("Trying to explain")
log.append(input)
# a stack of recently discovered facts (cd's)
chain = []
# cd is initialized to the current sentence from the story
# it changes to infererred plans, goals, and themes
cd = input
while True:
# if cd can be related to an earlier theme, goal or plan, the cd is the explanation
if self.predicted(cd, log):
break
log.append("Does not confirm prediction")
chain.append([cd, self.inference_rules[:]])
# infer as many themes, goals, and plans from the chain of new facts as possible
# also adds these to the chain
cd = self.try_inference(chain, log)
if not cd:
break
if cd:
log.append("Adding inference chain to data base")
for cd_inf in reversed(chain):
self.update_db(cd_inf[0], log)
self.update_db(cd, log)
else:
log.append("No inference chain found - adding")
self.update_db(input, log)
def predicted(self, cd: list, log: list[str]):
# is cd part of the known plans, goals or themes?
if self.isa("goal", cd):
return self.relate(cd, self.known_themes, self.init_rules, log) or self.relate(cd, self.known_plans, self.sub_for, log)
elif self.isa("plan", cd):
return self.relate(cd, self.known_goals, self.plans_for, log)
elif self.isa("action", cd):
return self.relate(cd, self.known_plans, self.instance_of, log)
else:
return None
def relate(self, cd: list, item_list: list, rule_list: list, log: list[str]):
# item_list contains known themes, goals, or plans
# rule_list contains all rules that belong to the themes, goals or plans
# each rule has an antecedent (rhs) and a consequent (lhs)
# the function tries the match the cd (via the antecedent) with the known theme, goal, or plan (via the consequent)
bindings = {}
for item in item_list:
for rule in rule_list:
bindings = self.match_side(rhs(rule), cd, {})
if bindings is not None and self.match_side(lhs(rule), item, bindings) is not None:
log.append("Confirms prediction from")
log.append(item)
return True
return False
def try_inference(self, chain: list, log: list[str]):
# chain is a list of [cd, all_inference_rules]
# return the lhs of the first match, and extend the chain
cd_inf = []
cd = None
while len(chain) > 0:
cd_inf = chain.pop()
# try all inference rules in the cd
# if the cd matches a rule, add it to the chain
# and return the bound lhs of the rule
cd = self.try_rules(cd_inf[0], cd_inf[1][:], chain)
if cd is None:
log.append("No usable inferences from")
log.append(cd_inf[0])
else:
break
if cd:
log.append("Possible explanation assuming")
log.append(cd)
return cd
return None
def try_rules(self, cd, rules: list, chain: list):
# match cd with the rhs of each of the rules
# if a match occurs, return a binding with the lhs of the rule
rule = None
bindings = None
while len(rules) > 0:
rule = rules.pop()
bindings = self.match_side(rhs(rule), cd, {})
if bindings is not None:
break
if bindings:
# append the fact to the chain
chain.append([cd, rules])
return instantiate(lhs(rule)[0], bindings)
return None
def update_db(self, cd: list, log: list[str]):
log.append(cd)
# add cd as a fact to the `database`
self.add_cd(cd)
# add cd to the known themes, goals or plans
if self.isa("is", cd):
log.append("---theme")
self.known_themes.append(cd)
if self.isa("goal", cd):
self.known_goals.append(cd)
if self.isa("plan", cd):
self.known_plans.append(cd)
def add_cd(self, cd):
# adds fact `cd` to the `database`
# the database is not used here, but could be used by an application of which MicroPAM is a part
self.data_base.append(cd)
def match_side(self, side, item, bindings: dict) -> dict:
# try to bind side with item, by both matching the pattern
current_bindings = match(pattern_side(side), item, bindings)
# and evaluating the optional constraint
if current_bindings:
if constraint_side(side):
if not self.evaluate(constraint_side(side), current_bindings):
return None
return current_bindings
def evaluate(self, cd, bindings: dict):
# evaluate a check to return a value
if is_predication(cd):
predicate = cd[0]
if predicate == "pos-val":
value = instantiate(cd[1], bindings)
return value[0] > 0
elif predicate == "isa":
an_instance = instantiate(cd[1], bindings)
a_class = instantiate(cd[2], bindings)
return self.isa(an_instance, a_class)
else:
raise Exception(f"Unknown predicate for 'evaluate': {predicate}")
return False
def isa(self, type: str, cd: any):
# does type equal cd, or is type an instance of cd?
if numberp(cd):
return False
elif atom(cd):
return self.isa_check(type, cd)
else:
if self.isa_check(type, header_cd(cd)):
return True
x = filler_role("type", cd)
if x:
return self.isa_check(type, header_cd(x))
return False
def isa_check(self, type, cd):
# does cd equal type, or is it a property of type?
if type == cd:
return True
if cd in self.isa_props and self.isa_props[cd] == type:
return True
return False
def clear_globals(self):
self.known_themes = []
self.known_goals = []
self.known_plans = []
self.data_base = []