From c51a984648497f69454fd31c309acbcff013ad85 Mon Sep 17 00:00:00 2001 From: Fedor Isakov Date: Wed, 31 Mar 2021 18:38:28 +0200 Subject: [PATCH] Alternative solution for invalidating feedback Introduce feedback basis to track dependencies on rules. Index journal chunks by evidence for quick lookup. The feedback basis is the collection of principal rules tags from the justifications of the current match. --- .../logic/reactor/core/CompositeFeedback.kt | 4 +-- .../mps/logic/reactor/core/Feedback.kt | 4 +-- .../mps/logic/reactor/core/Justified.kt | 1 - .../reactor/core/internal/ControllerImpl.kt | 10 +++++-- .../reactor/core/internal/MatchJournal.kt | 18 +++++++------ .../reactor/core/internal/MatchJournalImpl.kt | 27 ++++++++++++++++--- .../logic/reactor/evaluation/Supervisor.java | 5 +--- reactor/Test/src/program/MockProgram.kt | 4 ++- reactor/Test/test/TestController.kt | 2 +- 9 files changed, 51 insertions(+), 24 deletions(-) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/CompositeFeedback.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/CompositeFeedback.kt index 6b541d5d..537157c4 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/CompositeFeedback.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/CompositeFeedback.kt @@ -33,12 +33,12 @@ class CompositeFeedback private constructor(private val elements: List this.severity = maxSeverity(elements) } - override fun handle(currentRuleMatch: RuleMatch, feedbackKey: Any, supervisor: Supervisor): Boolean { + override fun handle(currentRuleMatch: RuleMatch, feedbackKey: Any, feedbackBasis: List, supervisor: Supervisor): Boolean { var unhandled = 0 for (feedback in elements) { if (!feedback.alreadyHandled()) { unhandled += 1 - if (supervisor.handleFeedback(currentRuleMatch, feedbackKey, feedback)) { + if (supervisor.handleFeedback(currentRuleMatch, feedbackKey, feedbackBasis, feedback)) { feedback.setHandled() unhandled -= 1 } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Feedback.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Feedback.kt index 27bb7008..1c8ae69c 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Feedback.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Feedback.kt @@ -40,8 +40,8 @@ abstract class Feedback : EvaluationFeedback() { /** * Returns true if the feedback has been handled. */ - open fun handle(currentRuleMatch: RuleMatch, feedbackKey: Any, supervisor: Supervisor): Boolean { - if (!alreadyHandled() && supervisor.handleFeedback(currentRuleMatch, feedbackKey, this)) { + open fun handle(currentRuleMatch: RuleMatch, feedbackKey: Any, feedbackBasis: List, supervisor: Supervisor): Boolean { + if (!alreadyHandled() && supervisor.handleFeedback(currentRuleMatch, feedbackKey, feedbackBasis, this)) { setHandled() } return alreadyHandled() diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Justified.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Justified.kt index 962ea0a6..4fcb8a04 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Justified.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Justified.kt @@ -30,7 +30,6 @@ typealias Evidence = Int */ typealias Justifications = TIntSet - fun emptyJustifications(): Justifications = TIntHashSet(1) fun justsOf(vararg elements: Evidence): Justifications = TIntHashSet(elements) fun justsFromCollection(collection: Collection): Justifications = TIntHashSet(collection) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt index 0d7c130c..ef2d08ee 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt @@ -167,7 +167,10 @@ internal class ControllerImpl ( if (itemOk) { context.withStatus { status -> if (status.feedback?.alreadyHandled() == false) { - status.feedback.handle(match, newParent.match.feedbackKey, supervisor) + status.feedback.handle(match, + newParent.match.feedbackKey, + processing.principalRuleTags(newParent), + supervisor) } } @@ -188,7 +191,10 @@ internal class ControllerImpl ( // if failure can be handled here then recover } else if (status.feedback?.alreadyHandled() == false - && status.failure.handle(match, newParent.match.feedbackKey, supervisor)) { + && status.failure.handle(match, + newParent.match.feedbackKey, + processing.principalRuleTags(newParent), + supervisor)) { status.recover() diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt index f09b4969..471290c4 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt @@ -16,6 +16,7 @@ package jetbrains.mps.logic.reactor.core.internal +import gnu.trove.TIntObjectHashMap import jetbrains.mps.logic.reactor.core.* import jetbrains.mps.logic.reactor.evaluation.* import jetbrains.mps.logic.reactor.program.* @@ -23,8 +24,10 @@ import jetbrains.mps.logic.reactor.util.Id import java.util.* import kotlin.Comparator +typealias ChunkIndex = TIntObjectHashMap -interface MatchJournal : Iterable, EvidenceSource { + +interface MatchJournal : EvidenceSource { /** * Add new [Chunk] for matches of principal rules. @@ -68,7 +71,7 @@ interface MatchJournal : Iterable, EvidenceSource { /** * Returns read-only [JournalIterator] that allows to remove future [Chunk]s. */ - override fun iterator(): JournalIterator + fun iterator(): JournalIterator /** * Returns internal [JournalIterator] that allows to remove future [Chunk]s. @@ -118,6 +121,7 @@ interface MatchJournal : Iterable, EvidenceSource { */ fun index(): Index + fun principalRuleTags(chunk: Chunk): List /** * Simplifies some search operations on [MatchJournal]. @@ -165,9 +169,9 @@ interface MatchJournal : Iterable, EvidenceSource { * Length of the indexed [MatchJournal] */ val size: Int - } + /** * Immutable snapshot of [MatchJournal]. */ @@ -178,7 +182,6 @@ interface MatchJournal : Iterable, EvidenceSource { override fun getStoreView(): StoreView = StoreViewImpl( chunks.flatMap { it.entries() }.allOccurrences().asSequence() ) - override fun getPreamble(info: PreambleInfo): View { val cornerChunk = chunks.first() // corner chunk at beginning val initialChunk = chunks[1] @@ -209,6 +212,7 @@ interface MatchJournal : Iterable, EvidenceSource { * the next [Chunk] was logged. */ interface Chunk : Justified { + /** * Corresponds to event of activated or discarded [Occurrence] */ @@ -235,7 +239,6 @@ interface MatchJournal : Iterable, EvidenceSource { * Same as [entries], but returns only discarded [Occurrence]s. */ fun discardedLog(): List = entries().filter { it.discarded }.map { it.occ } - fun toPos(): Pos = Pos(this, entries().size) } @@ -243,6 +246,7 @@ interface MatchJournal : Iterable, EvidenceSource { * [Chunk] corresponding to a [RuleMatch] of a principal [Rule]. */ interface MatchChunk : Chunk { + /** * Returns true if this [Chunk] depends on changes to specified rule. * Relevant for rules with origin. Doesn't include rule from [match]. @@ -253,7 +257,6 @@ interface MatchJournal : Iterable, EvidenceSource { * [RuleMatch] which defines this [Chunk] */ val match: RuleMatch - val ruleUniqueTag: Any get() = match.rule().uniqueTag() } @@ -265,16 +268,15 @@ interface MatchJournal : Iterable, EvidenceSource { } open class Pos(val chunk: Chunk, val entriesCount: Int) { + override fun equals(other: Any?) = other is Pos && other.chunk === chunk && other.entriesCount == entriesCount override fun hashCode(): Int = 31 * chunk.hashCode() + entriesCount - override fun toString(): String = "($chunk, $entriesCount)" } - } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt index fb8bb066..a1f83ba5 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt @@ -64,6 +64,8 @@ internal open class MatchJournalImpl( // invariant: never empty private val hist: IteratorMutableList + private val chunkIndex = ChunkIndex() + // pointer to current position where logging (chunk additions) and log erasing (chunk removals) happen // initially points at initial chunk private val __cursor: Cursor @@ -133,6 +135,13 @@ internal open class MatchJournalImpl( return added } + private fun indexChunk(chunk: Chunk) { + chunkIndex.put(chunk.evidence, chunk) + } + + private fun lookupChunkByEvidence(evidence: Evidence) = + chunkIndex.get(evidence) + private fun trackAncestor(logEvent: Justified) { while (!logEvent.justifiedBy(parentChunk())) { ancestorChunksStack.pop() @@ -234,6 +243,20 @@ internal open class MatchJournalImpl( override fun index(): MatchJournal.Index = IndexImpl(hist) + override fun principalRuleTags(chunk: Chunk): List { + val ptags = mutableListOf() + chunk.justifications().forEach { jn -> + // hist is sequential, random access can be expensive + (lookupChunkByEvidence(jn) as? MatchChunk)?.let { + if (it.match.isPrincipal) { + ptags.add(it.ruleUniqueTag) + } + } + true + } + return ptags + } + private fun allOccurrences(): Sequence { // the following loop doesn't handle this case of starting posPtr, when 'current' isn't valid (e.g. just right after resetPos()) if (__cursor.atStart()) return emptySequence() @@ -320,6 +343,7 @@ internal open class MatchJournalImpl( override fun add(chunk: Chunk) { it.add(chunk as ChunkImpl) __current = chunk + indexChunk(chunk) } override fun removeNext() { @@ -328,7 +352,6 @@ internal open class MatchJournalImpl( updateNext() } - /** * Walk in journal in direct order ([from] -> [current]) * while applying [action] to each visited [Chunk] @@ -380,7 +403,6 @@ internal open class MatchJournalImpl( } } - /** * Immutable view for MutableList that provides mutability only through its iterators */ @@ -453,7 +475,6 @@ internal open class MatchJournalImpl( } } - /** * Mock RuleMatch for use only in initial Chunk */ diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/Supervisor.java b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/Supervisor.java index aca896bd..80ce85fa 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/Supervisor.java +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/Supervisor.java @@ -34,8 +34,5 @@ public interface Supervisor { * Override this method in order to "handle" the feedback. * Returns true if the method has handled (consumed) the feedback. */ - default boolean handleFeedback(RuleMatch ruleMatch, Object feedbackKey, EvaluationFeedback feedback) { - return false; - } - + boolean handleFeedback(RuleMatch ruleMatch, Object feedbackKey, List feedbackBasis, EvaluationFeedback feedback); } diff --git a/reactor/Test/src/program/MockProgram.kt b/reactor/Test/src/program/MockProgram.kt index d9afbebb..7fc8fdd3 100644 --- a/reactor/Test/src/program/MockProgram.kt +++ b/reactor/Test/src/program/MockProgram.kt @@ -3,7 +3,9 @@ */ import jetbrains.mps.logic.reactor.core.RulesDiff +import jetbrains.mps.logic.reactor.evaluation.EvaluationFeedback import jetbrains.mps.logic.reactor.evaluation.InvocationContext +import jetbrains.mps.logic.reactor.evaluation.RuleMatch import jetbrains.mps.logic.reactor.evaluation.Supervisor import jetbrains.mps.logic.reactor.logical.LogicalContext import jetbrains.mps.logic.reactor.logical.MetaLogical @@ -128,7 +130,7 @@ open class MockSupervisor : Supervisor { else a } -// override fun handleFeedback(rule: Rule?, feedback: EvaluationFeedback?): Boolean = false + override fun handleFeedback(ruleMatch: RuleMatch, feedbackKey: Any, feedbackBasis: MutableList, feedback: EvaluationFeedback): Boolean = false } class MockConstraintRegistry() { diff --git a/reactor/Test/test/TestController.kt b/reactor/Test/test/TestController.kt index 6cb366a4..71db6e2b 100644 --- a/reactor/Test/test/TestController.kt +++ b/reactor/Test/test/TestController.kt @@ -68,7 +68,7 @@ class TestController { vararg occurrences: ConstraintOccurrence): Controller { val program = MockProgram("test", rulesLists, registry = MockConstraintRegistry()) val supervisor = object : MockSupervisor() { - override fun handleFeedback(ruleMatch: RuleMatch, feedbackKey: Any, feedback: EvaluationFeedback): Boolean = + override fun handleFeedback(ruleMatch: RuleMatch, feedbackKey: Any, feedbackBasis: MutableList, feedback: EvaluationFeedback): Boolean = feedbackHandler(ruleMatch, feedback) } MockSession.init(program, supervisor)