# HG changeset patch # User drewp@bigasterisk.com # Date 1632288559 25200 # Node ID 89e53cb8a01ce28619c75988bf7dad2bae0455ea # Parent a2347393b43e0bd2b619599513f4bac68f5acbb0 fix some harder tests. Mostly, _advanceTheStack needed to spin the odometer rings starting from the other side, to get all the right combos diff -r a2347393b43e -r 89e53cb8a01c service/mqtt_to_rdf/inference.py --- a/service/mqtt_to_rdf/inference.py Tue Sep 21 22:19:11 2021 -0700 +++ b/service/mqtt_to_rdf/inference.py Tue Sep 21 22:29:19 2021 -0700 @@ -61,7 +61,8 @@ self._shortId = next(_chunkLooperShortId) self._alignedMatches = list(self.lhsChunk.ruleMatchesFrom(self.workingSet)) - self._current = CandidateBinding({}) # only ours- do not store prev, since it could change without us + # only ours- do not store prev, since it could change without us + self._current = CandidateBinding({}) self._pastEnd = False self._seenBindings: List[CandidateBinding] = [] # combined bindings (up to our ring) that we've returned @@ -78,7 +79,9 @@ return self.prev.currentBinding() def advance(self): - """update to a new set of bindings we haven't seen (since last restart), or go into pastEnd mode""" + """update _current to a new set of valid bindings we haven't seen (since + last restart), or go into pastEnd mode. Note that _current is just our + contribution, but returned valid bindings include all prev rings.""" if self._pastEnd: raise NotImplementedError('need restart') ringlog.debug('') @@ -89,7 +92,6 @@ augmentedWorkingSet = list( applyChunky(self.prev.currentBinding(), self._alignedMatches, returnBoundStatementsOnly=False)) - if self._advanceWithPlainMatches(augmentedWorkingSet): ringlog.debug(f'{INDENT*6} <-- {self}.advance finished with plain matches') return @@ -108,19 +110,13 @@ for aligned in augmentedWorkingSet: try: - fullBinding = aligned.totalBindingIfThisStmtWereTrue(self._prevBindings()) - except Inconsistent: - ringlog.debug(f'{INDENT*7} ChunkLooper{self._shortId} - {aligned} would be inconsistent with prev bindings') + newBinding = aligned.newBindingIfMatched(self._prevBindings()) + except Inconsistent as exc: + ringlog.debug( + f'{INDENT*7} ChunkLooper{self._shortId} - {aligned} would be inconsistent with prev bindings ({exc})') continue - newBinding = fullBinding.copy() - newBinding.subtract(self._prevBindings()) - - ringlog.debug(f'{INDENT*7} {newBinding=} {self._seenBindings=}') - if fullBinding not in self._seenBindings: - self._seenBindings.append(fullBinding.copy()) - self._current = newBinding - ringlog.debug(f'{INDENT*7} new binding from {self} -> {fullBinding}') + if self._testAndKeepNewBinding(newBinding): return True return False @@ -142,16 +138,22 @@ pass else: if newBinding is not None: - fullBinding: CandidateBinding = self._prevBindings().copy() - fullBinding.addNewBindings(newBinding) - if fullBinding not in self._seenBindings: - self._seenBindings.append(fullBinding) - self._current = newBinding - ringlog.debug(f'{INDENT*7} new binding from {self} -> {fullBinding}') + if self._testAndKeepNewBinding(newBinding): return True return False + def _testAndKeepNewBinding(self, newBinding): + fullBinding: CandidateBinding = self._prevBindings().copy() + fullBinding.addNewBindings(newBinding) + isNew = fullBinding not in self._seenBindings + ringlog.debug(f'{INDENT*7} {self} considering {newBinding=} to make {fullBinding}. {isNew=}') + if isNew: + self._seenBindings.append(fullBinding.copy()) + self._current = newBinding + return True + return False + def _boundOperands(self, operands) -> List[Node]: pb: CandidateBinding = self._prevBindings() @@ -296,15 +298,15 @@ raise NoOptions() def _advanceTheStack(self, looperRings: List[ChunkLooper]) -> bool: - carry = True # 1st elem always must advance - for i, ring in enumerate(looperRings): + carry = True # last elem always must advance + for i, ring in reversed(list(enumerate(looperRings))): # unlike normal odometer, advancing any earlier ring could invalidate later ones if carry: odolog.debug(f'{INDENT*4} advanceAll [{i}] {ring} carry/advance') ring.advance() carry = False if ring.pastEnd(): - if ring is looperRings[-1]: + if ring is looperRings[0]: allRingsDone = [r.pastEnd() for r in looperRings] odolog.debug(f'{INDENT*4} advanceAll [{i}] {ring} says we done {allRingsDone=}') return True diff -r a2347393b43e -r 89e53cb8a01c service/mqtt_to_rdf/stmt_chunk.py --- a/service/mqtt_to_rdf/stmt_chunk.py Tue Sep 21 22:19:11 2021 -0700 +++ b/service/mqtt_to_rdf/stmt_chunk.py Tue Sep 21 22:29:19 2021 -0700 @@ -19,23 +19,39 @@ @dataclass class AlignedRuleChunk: - """a possible association between a rule chunk and a workingSet chunk. Use - matches() to see if the rule actually fits (and then we might cache some of - that work when computing the new bindings""" + """a possible association between a rule chunk and a workingSet chunk. You can test + whether the association would still be possible under various additional bindings.""" ruleChunk: 'Chunk' workingSetChunk: 'Chunk' - def totalBindingIfThisStmtWereTrue(self, prevBindings: CandidateBinding) -> CandidateBinding: - outBinding = prevBindings.copy() + def __post_init__(self): + if not self.matches(): + raise Inconsistent() + + def newBindingIfMatched(self, prevBindings: CandidateBinding) -> CandidateBinding: + """supposing this rule did match the statement, what new bindings would + that produce? + + raises Inconsistent if the existing bindings mean that our aligned + chunks can no longer match. + """ + outBinding = CandidateBinding({}) for rt, ct in zip(self.ruleChunk._allTerms(), self.workingSetChunk._allTerms()): if isinstance(rt, (Variable, BNode)): + if prevBindings.contains(rt) and prevBindings.applyTerm(rt) != ct: + msg = f'{rt=} {ct=} {prevBindings=}' if log.isEnabledFor(logging.DEBUG) else '' + raise Inconsistent(msg) if outBinding.contains(rt) and outBinding.applyTerm(rt) != ct: - msg = f'{rt=} {ct=} {outBinding=}' if log.isEnabledFor(logging.DEBUG) else '' - raise Inconsistent(msg) + # maybe this can happen, for stmts like ?x :a ?x . + raise Inconsistent("outBinding inconsistent with itself") outBinding.addNewBindings(CandidateBinding({rt: ct})) + else: + if rt != ct: + # getting here means prevBindings was set to something our + # rule statement disagrees with. + raise Inconsistent(f'{rt=} != {ct=}') return outBinding - # could combine this and totalBindingIf into a single ChunkMatch object def matches(self) -> bool: """could this rule, with its BindableTerm wildcards, match workingSetChunk?""" for selfTerm, otherTerm in zip(self.ruleChunk._allTerms(), self.workingSetChunk._allTerms()): @@ -95,9 +111,11 @@ if "stable failures please": allChunksIter = sorted(allChunksIter) for chunk in allChunksIter: - aligned = AlignedRuleChunk(self, chunk) - if aligned.matches(): - yield aligned + try: + aligned = AlignedRuleChunk(self, chunk) + except Inconsistent: + continue + yield aligned def __repr__(self): pre = ('+'.join('%s' % elem for elem in self.subjList) + '+' if self.subjList else '') @@ -132,15 +150,11 @@ g: Iterable[AlignedRuleChunk], returnBoundStatementsOnly=True) -> Iterator[AlignedRuleChunk]: for aligned in g: + bound = aligned.ruleChunk.apply(cb, returnBoundStatementsOnly=returnBoundStatementsOnly) try: - bound = aligned.ruleChunk.apply(cb, returnBoundStatementsOnly=returnBoundStatementsOnly) - except BindingUnknown: - log.debug(f'{INDENT*7} CB.apply cant bind {aligned} using {cb.binding}') - - continue - log.debug(f'{INDENT*7} CB.apply took {aligned} to {bound}') - - yield AlignedRuleChunk(bound, aligned.workingSetChunk) + yield AlignedRuleChunk(bound, aligned.workingSetChunk) + except Inconsistent: + pass class ChunkedGraph: