Mercurial > code > home > repos > homeauto
comparison service/mqtt_to_rdf/inference.py @ 1593:b0df43d5494c
big rewrite- more classes, smaller methods, more typesafe, all current tests passing
author | drewp@bigasterisk.com |
---|---|
date | Sat, 04 Sep 2021 23:23:55 -0700 |
parents | d7b66234064b |
children | e58bcfa66093 |
comparison
equal
deleted
inserted
replaced
1592:d7b66234064b | 1593:b0df43d5494c |
---|---|
15 from rdflib.collection import Collection | 15 from rdflib.collection import Collection |
16 from rdflib.graph import ConjunctiveGraph, ReadOnlyGraphAggregate | 16 from rdflib.graph import ConjunctiveGraph, ReadOnlyGraphAggregate |
17 from rdflib.term import Node, Variable | 17 from rdflib.term import Node, Variable |
18 | 18 |
19 log = logging.getLogger('infer') | 19 log = logging.getLogger('infer') |
20 INDENT = ' ' | |
20 | 21 |
21 Triple = Tuple[Node, Node, Node] | 22 Triple = Tuple[Node, Node, Node] |
22 Rule = Tuple[Graph, Node, Graph] | 23 Rule = Tuple[Graph, Node, Graph] |
23 BindableTerm = Union[Variable, BNode] | 24 BindableTerm = Union[Variable, BNode] |
24 | 25 |
33 class _RuleMatch: | 34 class _RuleMatch: |
34 """one way that a rule can match the working set""" | 35 """one way that a rule can match the working set""" |
35 vars: Dict[Variable, Node] | 36 vars: Dict[Variable, Node] |
36 | 37 |
37 | 38 |
38 inferredFuncs = { | 39 ReadOnlyWorkingSet = ReadOnlyGraphAggregate |
39 ROOM['asFarenheit'], | 40 |
40 MATH['sum'], | |
41 } | |
42 filterFuncs = { | 41 filterFuncs = { |
43 MATH['greaterThan'], | 42 MATH['greaterThan'], |
44 } | 43 } |
45 | 44 |
46 | 45 |
47 def withBinding(toBind: Graph, bindings: Dict[BindableTerm, Node], includeStaticStmts=True) -> Iterator[Triple]: | 46 class CandidateBinding: |
48 for stmt in toBind: | 47 |
49 stmt = list(stmt) | 48 def __init__(self, binding: Dict[BindableTerm, Node]): |
50 static = True | 49 self.binding = binding # mutable! |
51 for i, term in enumerate(stmt): | 50 |
52 if isinstance(term, (Variable, BNode)): | 51 def __repr__(self): |
53 stmt[i] = bindings[term] | 52 b = " ".join("%s=%s" % (k, v) for k, v in sorted(self.binding.items())) |
54 static = False | 53 return f'CandidateBinding({b})' |
54 | |
55 def apply(self, g: Graph) -> Iterator[Triple]: | |
56 for stmt in g: | |
57 stmt = list(stmt) | |
58 for i, term in enumerate(stmt): | |
59 if isinstance(term, (Variable, BNode)): | |
60 if term in self.binding: | |
61 stmt[i] = self.binding[term] | |
62 else: | |
63 yield cast(Triple, stmt) | |
64 | |
65 def applyFunctions(self, lhs): | |
66 """may grow the binding with some results""" | |
67 usedByFuncs = Graph() | |
68 while True: | |
69 before = len(self.binding) | |
70 delta = 0 | |
71 for ev in Evaluation.findEvals(lhs): | |
72 log.debug(f'{INDENT*3} found Evaluation') | |
73 | |
74 newBindings, usedGraph = ev.resultBindings(self.binding) | |
75 usedByFuncs += usedGraph | |
76 for k, v in newBindings.items(): | |
77 if k in self.binding and self.binding[k] != v: | |
78 raise ValueError( | |
79 f'conflict- thought {k} would be {self.binding[k]} but another Evaluation said it should be {v}') | |
80 self.binding[k] = v | |
81 delta = len(self.binding) - before | |
82 log.debug(f'{INDENT*4} rule {graphDump(usedGraph)} made {delta} new bindings') | |
83 if delta == 0: | |
84 break | |
85 return usedByFuncs | |
86 | |
87 def verify(self, lhs: 'Lhs', workingSet: ReadOnlyWorkingSet, usedByFuncs: Graph) -> bool: | |
88 """Can this lhs be true all at once in workingSet? Does it match with these bindings?""" | |
89 boundLhs = list(self.apply(lhs._g)) | |
90 boundUsedByFuncs = list(self.apply(usedByFuncs)) | |
91 | |
92 self.logVerifyBanner(boundLhs, workingSet, boundUsedByFuncs) | |
93 | |
94 for stmt in boundLhs: | |
95 log.debug(f'{INDENT*4} check for {stmt}') | |
96 | |
97 if stmt[1] in filterFuncs: | |
98 if not mathTest(*stmt): | |
99 log.debug(f'{INDENT*5} binding was invalid because {stmt}) is not true') | |
100 return False | |
101 elif stmt in boundUsedByFuncs: | |
102 pass | |
103 elif stmt in workingSet: | |
104 pass | |
105 else: | |
106 log.debug(f'{INDENT*5} binding was invalid because {stmt}) is not known to be true') | |
107 return False | |
108 log.debug(f"{INDENT*5} this rule's lhs can work under this binding") | |
109 return True | |
110 | |
111 def logVerifyBanner(self, boundLhs, workingSet: ReadOnlyWorkingSet, boundUsedByFuncs): | |
112 if not log.isEnabledFor(logging.DEBUG): | |
113 return | |
114 log.debug(f'{INDENT*4}/ verify all bindings against this lhs:') | |
115 for stmt in sorted(boundLhs): | |
116 log.debug(f'{INDENT*4}|{INDENT} {stmt}') | |
117 | |
118 log.debug(f'{INDENT*4}| and against this workingSet:') | |
119 for stmt in sorted(workingSet): | |
120 log.debug(f'{INDENT*4}|{INDENT} {stmt}') | |
121 | |
122 log.debug(f'{INDENT*4}| while ignoring these usedByFuncs:') | |
123 for stmt in sorted(boundUsedByFuncs): | |
124 log.debug(f'{INDENT*4}|{INDENT} {stmt}') | |
125 log.debug(f'{INDENT*4}\\') | |
126 | |
127 | |
128 class Lhs: | |
129 | |
130 def __init__(self, existingGraph): | |
131 self._g = existingGraph | |
132 | |
133 def findCandidateBindings(self, workingSet: ReadOnlyWorkingSet) -> Iterator[CandidateBinding]: | |
134 """bindings that fit the LHS of a rule, using statements from workingSet and functions | |
135 from LHS""" | |
136 nodesToBind = self.nodesToBind() | |
137 log.debug(f'{INDENT*2} nodesToBind: {nodesToBind}') | |
138 | |
139 if not self.allStaticStatementsMatch(workingSet): | |
140 return | |
141 | |
142 candidateTermMatches: Dict[BindableTerm, Set[Node]] = self.allCandidateTermMatches(workingSet) | |
143 | |
144 # for n in nodesToBind: | |
145 # if n not in candidateTermMatches: | |
146 # candidateTermMatches[n] = set() | |
147 | |
148 orderedVars, orderedValueSets = organize(candidateTermMatches) | |
149 | |
150 self.logCandidates(orderedVars, orderedValueSets) | |
151 | |
152 log.debug(f'{INDENT*2} trying all permutations:') | |
153 | |
154 for perm in itertools.product(*orderedValueSets): | |
155 binding = CandidateBinding(dict(zip(orderedVars, perm))) | |
156 log.debug('') | |
157 log.debug(f'{INDENT*3}*trying {binding}') | |
158 | |
159 usedByFuncs = binding.applyFunctions(self) | |
160 | |
161 if not binding.verify(self, workingSet, usedByFuncs): | |
162 log.debug(f'{INDENT*3} this binding did not verify') | |
163 continue | |
164 yield binding | |
165 | |
166 def nodesToBind(self) -> List[BindableTerm]: | |
167 nodes: Set[BindableTerm] = set() | |
168 staticRuleStmts = Graph() | |
169 for ruleStmt in self._g: | |
170 varsInStmt = [v for v in ruleStmt if isinstance(v, (Variable, BNode))] | |
171 nodes.update(varsInStmt) | |
172 if (not varsInStmt # ok | |
173 #and not any(isinstance(t, BNode) for t in ruleStmt) # approx | |
174 ): | |
175 staticRuleStmts.add(ruleStmt) | |
176 return sorted(nodes) | |
177 | |
178 def allStaticStatementsMatch(self, workingSet: ReadOnlyWorkingSet) -> bool: | |
179 staticRuleStmts = Graph() | |
180 for ruleStmt in self._g: | |
181 varsInStmt = [v for v in ruleStmt if isinstance(v, (Variable, BNode))] | |
182 if (not varsInStmt # ok | |
183 #and not any(isinstance(t, BNode) for t in ruleStmt) # approx | |
184 ): | |
185 staticRuleStmts.add(ruleStmt) | |
186 | |
187 for ruleStmt in staticRuleStmts: | |
188 if ruleStmt not in workingSet: | |
189 log.debug(f'{INDENT*3} {ruleStmt} not in working set- skip rule') | |
190 return False | |
191 return True | |
192 | |
193 def allCandidateTermMatches(self, workingSet: ReadOnlyWorkingSet) -> Dict[BindableTerm, Set[Node]]: | |
194 """the total set of terms each variable could possibly match""" | |
195 | |
196 candidateTermMatches: Dict[BindableTerm, Set[Node]] = defaultdict(set) | |
197 lhsBnodes: Set[BNode] = set() | |
198 for lhsStmt in self._g: | |
199 log.debug(f'{INDENT*3} possibles for this lhs stmt: {lhsStmt}') | |
200 for i, trueStmt in enumerate(sorted(workingSet)): | |
201 log.debug(f'{INDENT*4} consider this true stmt ({i}): {trueStmt}') | |
202 bindingsFromStatement: Dict[Variable, Set[Node]] = {} | |
203 for lhsTerm, trueTerm in zip(lhsStmt, trueStmt): | |
204 if isinstance(lhsTerm, BNode): | |
205 lhsBnodes.add(lhsTerm) | |
206 elif isinstance(lhsTerm, Variable): | |
207 bindingsFromStatement.setdefault(lhsTerm, set()).add(trueTerm) | |
208 elif lhsTerm != trueTerm: | |
209 break | |
210 else: | |
211 for v, vals in bindingsFromStatement.items(): | |
212 candidateTermMatches[v].update(vals) | |
213 | |
214 for trueStmt in itertools.chain(workingSet, self._g): | |
215 for b in lhsBnodes: | |
216 for t in [trueStmt[0], trueStmt[2]]: | |
217 if isinstance(t, (URIRef, BNode)): | |
218 candidateTermMatches[b].add(t) | |
219 return candidateTermMatches | |
220 | |
221 def graphWithoutEvals(self, binding: CandidateBinding) -> Graph: | |
222 g = Graph() | |
223 usedByFuncs = binding.applyFunctions(self) | |
224 | |
225 for stmt in self._g: | |
226 if stmt not in usedByFuncs: | |
227 g.add(stmt) | |
228 return g | |
229 | |
230 def logCandidates(self, orderedVars, orderedValueSets): | |
231 if not log.isEnabledFor(logging.DEBUG): | |
232 return | |
233 log.debug(f'{INDENT*2} resulting candidate terms:') | |
234 for v, vals in zip(orderedVars, orderedValueSets): | |
235 log.debug(f'{INDENT*3} {v} could be:') | |
236 for val in vals: | |
237 log.debug(f'{INDENT*4}{val}') | |
238 | |
239 | |
240 class Evaluation: | |
241 """some lhs statements need to be evaluated with a special function | |
242 (e.g. math) and then not considered for the rest of the rule-firing | |
243 process. It's like they already 'matched' something, so they don't need | |
244 to match a statement from the known-true working set.""" | |
245 | |
246 @staticmethod | |
247 def findEvals(lhs: Lhs) -> Iterator['Evaluation']: | |
248 for stmt in lhs._g.triples((None, MATH['sum'], None)): | |
249 # shouldn't be redoing this here | |
250 operands, operandsStmts = parseList(lhs._g, stmt[0]) | |
251 g = Graph() | |
252 g += operandsStmts | |
253 yield Evaluation(operands, g, stmt) | |
254 | |
255 for stmt in lhs._g.triples((None, ROOM['asFarenheit'], None)): | |
256 g = Graph() | |
257 g.add(stmt) | |
258 yield Evaluation([stmt[0]], g, stmt) | |
259 | |
260 # internal, use findEvals | |
261 def __init__(self, operands: List[Node], operandsStmts: Graph, stmt: Triple) -> None: | |
262 self.operands = operands | |
263 self.operandsStmts = operandsStmts | |
264 self.stmt = stmt | |
265 | |
266 def resultBindings(self, inputBindings) -> Tuple[Dict[BindableTerm, Node], Graph]: | |
267 """under the bindings so far, what would this evaluation tell us, and which stmts would be consumed from doing so?""" | |
268 pred = self.stmt[1] | |
269 objVar = self.stmt[2] | |
270 boundOperands = [] | |
271 for o in self.operands: | |
272 if isinstance(o, Variable): | |
273 try: | |
274 o = inputBindings[o] | |
275 except KeyError: | |
276 return {}, self.operandsStmts | |
277 | |
278 boundOperands.append(o) | |
279 | |
280 if not isinstance(objVar, Variable): | |
281 raise TypeError(f'expected Variable, got {objVar!r}') | |
282 | |
283 if pred == MATH['sum']: | |
284 log.debug(f'{INDENT*4} sum {list(map(self.numericNode, boundOperands))}') | |
285 obj = cast(Literal, Literal(sum(map(self.numericNode, boundOperands)))) | |
286 self.operandsStmts.add(self.stmt) | |
287 return {objVar: obj}, self.operandsStmts | |
288 elif pred == ROOM['asFarenheit']: | |
289 if len(boundOperands) != 1: | |
290 raise ValueError(":asFarenheit takes 1 subject operand") | |
291 f = Literal(Decimal(self.numericNode(boundOperands[0])) * 9 / 5 + 32) | |
292 g = Graph() | |
293 g.add(self.stmt) | |
294 | |
295 log.debug('made 1 st graph') | |
296 return {objVar: f}, g | |
55 else: | 297 else: |
56 if includeStaticStmts or not static: | 298 raise NotImplementedError() |
57 yield cast(Triple, stmt) | 299 |
58 | 300 def numericNode(self, n: Node): |
59 | 301 if not isinstance(n, Literal): |
60 def verifyBinding(lhs: Graph, binding: Dict[BindableTerm, Node], workingSet: Graph, usedByFuncs: Graph) -> bool: | 302 raise TypeError(f'expected Literal, got {n=}') |
61 """Can this lhs be true all at once in workingSet? Does it match with these bindings?""" | 303 val = n.toPython() |
62 boundLhs = list(withBinding(lhs, binding)) | 304 if not isinstance(val, (int, float, Decimal)): |
63 boundUsedByFuncs = list(withBinding(usedByFuncs, binding)) | 305 raise TypeError(f'expected number, got {val=}') |
64 if log.isEnabledFor(logging.DEBUG): | 306 return val |
65 log.debug(f' verify all bindings against this lhs:') | 307 |
66 for stmt in boundLhs: | 308 |
67 log.debug(f' {stmt}') | 309 # merge into evaluation, raising a Invalid for impossible stmts |
68 | |
69 log.debug(f' and against this workingSet:') | |
70 for stmt in workingSet: | |
71 log.debug(f' {stmt}') | |
72 | |
73 log.debug(f' ignoring these usedByFuncs:') | |
74 for stmt in boundUsedByFuncs: | |
75 log.debug(f' {stmt}') | |
76 # The static stmts in lhs are obviously going | |
77 # to match- we only need to verify the ones | |
78 # that needed bindings. | |
79 for stmt in boundLhs: #withBinding(lhs, binding, includeStaticStmts=False): | |
80 log.debug(f' check for {stmt}') | |
81 | |
82 if stmt[1] in filterFuncs: | |
83 if not mathTest(*stmt): | |
84 log.debug(f' binding was invalid because {stmt}) is not true') | |
85 return False | |
86 elif stmt in boundUsedByFuncs: | |
87 pass | |
88 elif stmt in workingSet: | |
89 pass | |
90 else: | |
91 log.debug(f' binding was invalid because {stmt}) cannot be true') | |
92 return False | |
93 return True | |
94 | |
95 | |
96 def findCandidateBindings(lhs: Graph, workingSet: Graph) -> Iterator[Dict[BindableTerm, Node]]: | |
97 """bindings that fit the LHS of a rule, using statements from workingSet and functions | |
98 from LHS""" | |
99 varsToBind: Set[BindableTerm] = set() | |
100 staticRuleStmts = Graph() | |
101 for ruleStmt in lhs: | |
102 varsInStmt = [v for v in ruleStmt if isinstance(v, (Variable, BNode))] | |
103 varsToBind.update(varsInStmt) | |
104 if (not varsInStmt # ok | |
105 #and not any(isinstance(t, BNode) for t in ruleStmt) # approx | |
106 ): | |
107 staticRuleStmts.add(ruleStmt) | |
108 | |
109 log.debug(f' varsToBind: {sorted(varsToBind)}') | |
110 | |
111 if someStaticStmtDoesntMatch(staticRuleStmts, workingSet): | |
112 log.debug(f' someStaticStmtDoesntMatch: {graphDump(staticRuleStmts)}') | |
113 return | |
114 | |
115 # the total set of terms each variable could possibly match | |
116 candidateTermMatches: Dict[BindableTerm, Set[Node]] = findCandidateTermMatches(lhs, workingSet) | |
117 | |
118 orderedVars, orderedValueSets = organize(candidateTermMatches) | |
119 | |
120 log.debug(f' candidate terms:') | |
121 log.debug(f' {orderedVars=}') | |
122 log.debug(f' {orderedValueSets=}') | |
123 | |
124 for i, perm in enumerate(itertools.product(*orderedValueSets)): | |
125 binding: Dict[BindableTerm, Node] = dict(zip(orderedVars, perm)) | |
126 log.debug('') | |
127 log.debug(f' ** trying {binding=}') | |
128 usedByFuncs = Graph() | |
129 for v, val, used in inferredFuncBindings(lhs, binding): # loop this until it's done | |
130 log.debug(f' inferredFuncBindings tells us {v}={val}') | |
131 binding[v] = val | |
132 usedByFuncs += used | |
133 if len(binding) != len(varsToBind): | |
134 log.debug(f' binding is incomplete, needs {varsToBind}') | |
135 | |
136 continue | |
137 if not verifyBinding(lhs, binding, workingSet, usedByFuncs): # fix this | |
138 log.debug(f' this binding did not verify') | |
139 continue | |
140 yield binding | |
141 | |
142 | |
143 def someStaticStmtDoesntMatch(staticRuleStmts, workingSet): | |
144 for ruleStmt in staticRuleStmts: | |
145 if ruleStmt not in workingSet: | |
146 log.debug(f' {ruleStmt} not in working set- skip rule') | |
147 | |
148 return True | |
149 return False | |
150 | |
151 | |
152 def findCandidateTermMatches(lhs: Graph, workingSet: Graph) -> Dict[BindableTerm, Set[Node]]: | |
153 candidateTermMatches: Dict[BindableTerm, Set[Node]] = defaultdict(set) | |
154 lhsBnodes: Set[BNode] = set() | |
155 for lhsStmt in lhs: | |
156 for trueStmt in workingSet: | |
157 log.debug(f' lhsStmt={graphDump([lhsStmt])} trueStmt={graphDump([trueStmt])}') | |
158 bindingsFromStatement: Dict[Variable, Set[Node]] = {} | |
159 for lhsTerm, trueTerm in zip(lhsStmt, trueStmt): | |
160 # log.debug(f' test {lhsTerm=} {trueTerm=}') | |
161 if isinstance(lhsTerm, BNode): | |
162 lhsBnodes.add(lhsTerm) | |
163 elif isinstance(lhsTerm, Variable): | |
164 bindingsFromStatement.setdefault(lhsTerm, set()).add(trueTerm) | |
165 elif lhsTerm != trueTerm: | |
166 break | |
167 else: | |
168 for v, vals in bindingsFromStatement.items(): | |
169 candidateTermMatches[v].update(vals) | |
170 | |
171 for trueStmt in itertools.chain(workingSet, lhs): | |
172 for b in lhsBnodes: | |
173 for t in [trueStmt[0], trueStmt[2]]: | |
174 if isinstance(t, (URIRef, BNode)): | |
175 candidateTermMatches[b].add(t) | |
176 return candidateTermMatches | |
177 | |
178 | |
179 def inferredFuncObject(subj, pred, graph, bindings) -> Tuple[Literal, Graph]: | |
180 """return result from like `(1 2) math:sum ?out .` plus a graph of all the | |
181 statements involved in that function rule (including the bound answer""" | |
182 used = Graph() | |
183 if pred == ROOM['asFarenheit']: | |
184 obj = Literal(Decimal(subj.toPython()) * 9 / 5 + 32) | |
185 elif pred == MATH['sum']: | |
186 operands, operandsStmts = parseList(graph, subj) | |
187 # shouldn't be redoing this here | |
188 operands = [bindings[o] if isinstance(o, Variable) else o for o in operands] | |
189 log.debug(f' sum {[op.toPython() for op in operands]}') | |
190 used += operandsStmts | |
191 obj = Literal(sum(op.toPython() for op in operands)) | |
192 else: | |
193 raise NotImplementedError(pred) | |
194 | |
195 used.add((subj, pred, obj)) | |
196 return obj, used | |
197 | |
198 | |
199 def mathTest(subj, pred, obj): | 310 def mathTest(subj, pred, obj): |
200 x = subj.toPython() | 311 x = subj.toPython() |
201 y = obj.toPython() | 312 y = obj.toPython() |
202 if pred == MATH['greaterThan']: | 313 if pred == MATH['greaterThan']: |
203 return x > y | 314 return x > y |
215 | 326 |
216 def infer(self, graph: Graph): | 327 def infer(self, graph: Graph): |
217 """ | 328 """ |
218 returns new graph of inferred statements. | 329 returns new graph of inferred statements. |
219 """ | 330 """ |
220 log.info(f'Begin inference of graph len={len(graph)} with rules len={len(self.rules)}:') | 331 log.debug(f'{INDENT*0} Begin inference of graph len={graph.__len__()} with rules len={len(self.rules)}:') |
221 | 332 |
222 # everything that is true: the input graph, plus every rule conclusion we can make | 333 # everything that is true: the input graph, plus every rule conclusion we can make |
223 workingSet = graphCopy(graph) | 334 workingSet = Graph() |
335 workingSet += graph | |
224 | 336 |
225 # just the statements that came from rule RHS's. | 337 # just the statements that came from rule RHS's. |
226 implied = ConjunctiveGraph() | 338 implied = ConjunctiveGraph() |
227 | 339 |
228 bailout_iterations = 100 | 340 bailout_iterations = 100 |
229 delta = 1 | 341 delta = 1 |
230 while delta > 0 and bailout_iterations > 0: | 342 while delta > 0 and bailout_iterations > 0: |
231 log.debug(f' * iteration ({bailout_iterations} left)') | 343 log.debug(f'{INDENT*1}*iteration ({bailout_iterations} left)') |
232 bailout_iterations -= 1 | 344 bailout_iterations -= 1 |
233 delta = -len(implied) | 345 delta = -len(implied) |
234 self._iterateAllRules(workingSet, implied) | 346 self._iterateAllRules(workingSet, implied) |
235 delta += len(implied) | 347 delta += len(implied) |
236 log.info(f' this inference round added {delta} more implied stmts') | 348 log.info(f'{INDENT*1} this inference round added {delta} more implied stmts') |
237 log.info(f' {len(implied)} stmts implied:') | 349 log.info(f'{INDENT*0} {len(implied)} stmts implied:') |
238 for st in implied: | 350 for st in implied: |
239 log.info(f' {st}') | 351 log.info(f'{INDENT*2} {st}') |
240 return implied | 352 return implied |
241 | 353 |
242 def _iterateAllRules(self, workingSet, implied): | 354 def _iterateAllRules(self, workingSet: Graph, implied: Graph): |
243 for i, r in enumerate(self.rules): | 355 for i, r in enumerate(self.rules): |
244 log.debug(f' workingSet: {graphDump(workingSet)}') | 356 log.debug('') |
245 log.debug(f' - applying rule {i}') | 357 log.debug(f'{INDENT*2} workingSet:') |
246 log.debug(f' lhs: {graphDump(r[0])}') | 358 for i, stmt in enumerate(sorted(workingSet)): |
247 log.debug(f' rhs: {graphDump(r[2])}') | 359 log.debug(f'{INDENT*3} ({i}) {stmt}') |
360 | |
361 log.debug('') | |
362 log.debug(f'{INDENT*2}-applying rule {i}') | |
363 log.debug(f'{INDENT*3} rule def lhs: {graphDump(r[0])}') | |
364 log.debug(f'{INDENT*3} rule def rhs: {graphDump(r[2])}') | |
248 if r[1] == LOG['implies']: | 365 if r[1] == LOG['implies']: |
249 applyRule(r[0], r[2], workingSet, implied) | 366 applyRule(Lhs(r[0]), r[2], workingSet, implied) |
250 else: | 367 else: |
251 log.info(f' {r} not a rule?') | 368 log.info(f'{INDENT*2} {r} not a rule?') |
252 | 369 |
253 | 370 |
254 def graphCopy(src: Graph) -> Graph: | 371 def applyRule(lhs: Lhs, rhs: Graph, workingSet: Graph, implied: Graph): |
255 if isinstance(src, ConjunctiveGraph): | 372 for binding in lhs.findCandidateBindings(ReadOnlyGraphAggregate([workingSet])): |
256 out = ConjunctiveGraph() | 373 # log.debug(f' rule gave {binding=}') |
257 out.addN(src.quads()) | 374 for lhsBoundStmt in binding.apply(lhs.graphWithoutEvals(binding)): |
258 return out | |
259 else: | |
260 out = Graph() | |
261 for triple in src: | |
262 out.add(triple) | |
263 return out | |
264 | |
265 | |
266 def applyRule(lhs: Graph, rhs: Graph, workingSet: Graph, implied: Graph): | |
267 for bindings in findCandidateBindings(lhs, workingSet): | |
268 log.debug(f' rule gave {bindings=}') | |
269 for lhsBoundStmt in withBinding(lhs, bindings): | |
270 workingSet.add(lhsBoundStmt) | 375 workingSet.add(lhsBoundStmt) |
271 for newStmt in withBinding(rhs, bindings): | 376 for newStmt in binding.apply(rhs): |
272 workingSet.add(newStmt) | 377 workingSet.add(newStmt) |
273 implied.add(newStmt) | 378 implied.add(newStmt) |
274 | 379 |
275 | 380 |
276 def parseList(graph, subj) -> Tuple[List[Node], Set[Triple]]: | 381 def parseList(graph, subj) -> Tuple[List[Node], Set[Triple]]: |
382 """"Do like Collection(g, subj) but also return all the | |
383 triples that are involved in the list""" | |
277 out = [] | 384 out = [] |
278 used = set() | 385 used = set() |
279 cur = subj | 386 cur = subj |
280 while True: | 387 while True: |
281 # bug: mishandles empty list | 388 # bug: mishandles empty list |
282 out.append(graph.value(cur, RDF.first)) | 389 out.append(graph.value(cur, RDF.first)) |
283 used.add((cur, RDF.first, out[-1])) | 390 used.add((cur, RDF.first, out[-1])) |
284 | 391 |
285 next = graph.value(cur, RDF.rest) | 392 next = graph.value(cur, RDF.rest) |
286 used.add((cur, RDF.rest, next)) | 393 used.add((cur, RDF.rest, next)) |
394 | |
287 cur = next | 395 cur = next |
288 if cur == RDF.nil: | 396 if cur == RDF.nil: |
289 break | 397 break |
290 return out, used | 398 return out, used |
291 | 399 |
315 orderedValueSets.append(orderedValues) | 423 orderedValueSets.append(orderedValues) |
316 | 424 |
317 return orderedVars, orderedValueSets | 425 return orderedVars, orderedValueSets |
318 | 426 |
319 | 427 |
320 def inferredFuncBindings(lhs: Graph, bindingsBefore) -> Iterator[Tuple[Variable, Node, Graph]]: | |
321 for stmt in lhs: | |
322 if stmt[1] not in inferredFuncs: | |
323 continue | |
324 var = stmt[2] | |
325 if not isinstance(var, Variable): | |
326 continue | |
327 | |
328 x = stmt[0] | |
329 if isinstance(x, Variable): | |
330 x = bindingsBefore[x] | |
331 | |
332 resultObject, usedByFunc = inferredFuncObject(x, stmt[1], lhs, bindingsBefore) | |
333 | |
334 yield var, resultObject, usedByFunc | |
335 | |
336 | |
337 def isStatic(spo: Triple): | 428 def isStatic(spo: Triple): |
338 for t in spo: | 429 for t in spo: |
339 if isinstance(t, (Variable, BNode)): | 430 if isinstance(t, (Variable, BNode)): |
340 return False | 431 return False |
341 return True | 432 return True |