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