Prune the whole related state in RuleMatchers on removal-phase. Add test case.

This commit is contained in:
Grigorii Kirgizov 2019-06-13 20:10:08 +03:00
parent 7acbbc3988
commit 65f1198a60
7 changed files with 111 additions and 47 deletions

View File

@ -106,23 +106,32 @@ class Dispatcher (val ruleIndex: RuleIndex) {
* Returns a new [DispatchingFront] instance that is "contracted": all matches corresponding to the
* specified discarded constraint occurrence are eliminated.
*/
fun contract(discarded: Occurrence) = DispatchingFront(this,
ruleIndex.forOccurrence(discarded).mapNotNull { rule ->
ruletag2probe[rule.uniqueTag()]
}.map { probe ->
fun contract(discarded: Occurrence) =
forRelatedProbe(discarded) { probe ->
probe.contract(discarded)
})
}
/**
* Returns a new [DispatchingFront] instance which "forgot" that it seen occurrence.
* Need it for incremental reactivations to discern them from reactivations due to logicals.
*/
internal fun forgetSeen(dropped: Occurrence) = DispatchingFront(this,
ruleIndex.forOccurrence(dropped).mapNotNull { rule ->
ruletag2probe[rule.uniqueTag()]
}.map { probe ->
internal fun forgetSeen(dropped: Occurrence) =
forRelatedProbe(dropped) { probe ->
probe.forgetSeen(dropped)
})
}
internal fun forget(dropped: Occurrence) =
forRelatedProbe(dropped) { probe ->
probe.contract(dropped).forgetSeen(dropped).forgetConsumed(dropped)
}
private fun forRelatedProbe(occ: Occurrence, action: (RuleMatchingProbe) -> RuleMatchingProbe) =
DispatchingFront(this,
ruleIndex.forOccurrence(occ).mapNotNull { rule ->
ruletag2probe[rule.uniqueTag()]
}.map { probe ->
action(probe)
})
/**
* Serves to indicate that the specified [RuleMatchEx] has been processed (consumed) and has to

View File

@ -43,4 +43,6 @@ interface RuleMatchingProbe {
fun forgetSeen(occ: Occurrence): RuleMatchingProbe
fun forgetConsumed(occ: Occurrence): RuleMatchingProbe
}

View File

@ -52,7 +52,7 @@ internal class ControllerImpl (
fun incrLaunch(constraint: Constraint, rulesDiff: RulesDiff): FeedbackStatus {
// todo: use profiler here?
state.invalidateByRules(rulesDiff.removed)
state.invalidateRuleMatches(rulesDiff.removed)
state.addRuleMatches(rulesDiff.added)
val status = state.launchQueue(this)
return status

View File

@ -50,6 +50,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
: StoreAwareJournalImpl(journal)
{
private val ruleOrdering: RuleOrdering = RuleOrdering(ruleIndex)
private val journalIndex: MatchJournal.Index = journal.index()
// It is a position in Journal from previous session,
@ -69,14 +70,12 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
private data class MatchCandidate(val rule: Rule, val occ: Occurrence, val occParentId: Int)
// fixme: wtf, why idea's compiler complains???
override fun index(): MatchJournal.Index = journalIndex
// Invalidation includes several activities:
// - removing chunks (i.e. principal matches) corresponding to removed rules and chunks depending on them from journal
// - removing chunks (i.e. principal matches) corresponding to
// removed rules and chunks depending on them from journal
// - reactivating occurrences that led to invalidated matches
// - pruning invalidated occurrences and matches from Dispatcher's state
fun invalidateByRules(ruleIds: Set<Any>) {
fun invalidateRuleMatches(ruleIds: Set<Any>) {
val justificationRoots = mutableListOf<Int>()
val it = this.iterator()
@ -92,11 +91,10 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
// We removed the match, so need to reactivate all still valid occurrences from the head
// by definition of Chunk and principal rule, all occurrences from the head are principal
val matchedOccs = chunk.match.allHeads() as Iterable<Occurrence>
assert(matchedOccs.all { it.isPrincipal() })
val (invalidatedOccs, validOccs) = matchedOccs.partition { occ ->
occ.justifications().intersects(justificationRoots)
}
assert(matchedOccs.all { it.isPrincipal() })
// todo: do need to reactivate only the main, matching~activating match?
// (i.e. don't reactivate additional, inactive heads that only completed the match?)
@ -113,20 +111,20 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
// Remove the chunk from the journal
it.remove()
// Need to 'cancel' discarding: contract nodes with discarded occurrences.
// These nodes may become valid and will be processed due to reactivation of needed occurrences.
chunk.match.matchHeadReplaced().forEach {
dispatchingFront = dispatchingFront.contract(it as Occurrence)
}
// Seems, it's not strictly necessary, because some of its head occurrences are anyway invalidated forever
// and storing this invalid consumed match can make no harm, except some memory overhead.
// fixme: move Chunk's interface to RuleMatchEx instead of RuleMatch
dispatchingFront = dispatchingFront.forget(chunk.match as RuleMatchEx)
// Need to 'cancel' discarding.
// These nodes may become valid and will be processed due to reactivation of needed occurrences.
chunk.match.matchHeadReplaced().forEach {
dispatchingFront = dispatchingFront.forget(it as Occurrence)
}
// 'Undo' all activated in this chunk occurrences
// todo: also forget all consumed matches invo
chunk.activated().forEach {
dispatchingFront = dispatchingFront.contract(it).forgetSeen(it)
dispatchingFront = dispatchingFront.forget(it)
}
}
@ -137,9 +135,6 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
private fun canMatch(rule: Rule, occ: Occurrence): Boolean =
(rule.headKept() + rule.headReplaced()).find { it.symbol() == occ.constraint.symbol() } != null
// todo: what if non-principal rules will be passed as input there?
// only rules that can match on principal occurrences will pass through this method to the queue
// so, no non-principal rule should pass it.
fun addRuleMatches(rules: Iterable<Rule>) {
val activationCandidates = mutableListOf<MatchCandidate>()

View File

@ -351,6 +351,10 @@ internal class ReteRuleMatcherImpl(val rule: Rule) : RuleMatcher {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun forgetConsumed(occ: Occurrence): ReteNetwork {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun matches(): Collection<RuleMatchImpl> = generations.last().matches()
override fun consume(ruleMatch: RuleMatchEx): RuleMatchingProbe {

View File

@ -100,11 +100,10 @@ internal class RuleMatcherImpl(private val ruleLookup: RuleLookup,
return RuleMatchFront(newNodes, seenOccurrences, consumedSignatures, genId + 1)
}
// todo: need it for incremental removal?
// fun forgetConsumed(occ: Occurrence): RuleMatchFront {
// val newConsumed = Sets.copyOf(consumedSignatures.filter{ !it.contains(Id(occ)) })
// return RuleMatchFront(nodes, seenOccurrences, newConsumed, genId) // NB: not modifying genId
// }
override fun forgetConsumed(occ: Occurrence): RuleMatchFront {
val newConsumed = Sets.copyOf(consumedSignatures.filter{ !it.contains(Id(occ)) })
return RuleMatchFront(nodes, seenOccurrences, newConsumed, genId) // NB: not modifying genId
}
override fun forgetSeen(occ: Occurrence): RuleMatchFront {
val newSeen = seenOccurrences.remove(Id(occ))

View File

@ -457,7 +457,7 @@ class TestIncrementalProgram {
}
@Test
fun relaunchTwice() {
fun addAndRelaunchTwice() {
val progSpec = MockIncrProgSpec(
setOf("main", "foo.bar", "foo.baz", "baz.lax"),
setOf(sym0("foo"), sym0("baz"))
@ -582,7 +582,7 @@ class TestIncrementalProgram {
// Test on complex condition involving consumedSignatures and 'reactivation' flag in RuleMatchFront.expand
@Test
fun reactivatePartialMatchOfPropagationRule() {
fun completePartialMatch() {
val progSpec = MockIncrProgSpec(
setOf(".foo", ".bar", "foobar"),
setOf(sym0("start"), sym0("foo"), sym0("bar"))
@ -613,8 +613,6 @@ class TestIncrementalProgram {
))
).launch("withoutBar", progSpec) { result ->
println(result.token().journalView.chunks)
// "foobar" hasn't matched without 'bar, so 'foo' is still here
result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"))
@ -632,8 +630,6 @@ class TestIncrementalProgram {
))
).relaunch("withBar", progSpec, evalRes.token()) { result ->
println(result.token().journalView.chunks)
// if "foobar" happens too early, "1st" occ won't be produced
result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"), sym0("marker"))
result.lastChunkSymbols() shouldBe listOf(sym0("marker"))
@ -642,6 +638,71 @@ class TestIncrementalProgram {
}
}
// Same as 'completePartialMatch' test, except it discards 'foo' at the end.
@Test
@Ignore("Shouldn't pass by design. Incrementality-enabled programs are not supposed to have such rule sequences.")
fun completePartialMatchBeforeReplaced() {
val progSpec = MockIncrProgSpec(
setOf(".foo", ".bar", "foobar"),
setOf(sym0("start"), sym0("foo"), sym0("bar"))
)
programWithRules(
rule("main",
headReplaced(
constraint("main")
),
body(
princConstraint("start")
)),
rule(".foo",
headReplaced(
princConstraint("start")
),
body(
princConstraint("foo")
)),
// Propagation rule that doesn't match the first time due to the lack of 'bar'
rule("foobar",
headKept(
princConstraint("foo"),
princConstraint("bar")
),
body(
constraint("marker")
)),
rule("rmfoo",
headReplaced(
princConstraint("foo")
),
body()
)
).launch("withoutBar", progSpec) { result ->
// "foobar" hasn't matched without 'bar, so 'foo' is still here
result.storeView().constraintSymbols() shouldBe emptySet<ConstraintSymbol>()
}.also { (builder, evalRes) ->
val nPrincipalMatches = evalRes.countChunks()
// Provides 'bar' by reactivating 'foo'
builder.insertRulesAt(1,
rule(".bar",
headKept(
princConstraint("foo")
),
body(
princConstraint("bar")
))
).relaunch("withBar", progSpec, evalRes.token()) { result ->
// if "foobar" happens too early, "1st" occ won't be produced
result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("marker"))
result.lastChunkSymbols() shouldBe listOf(sym0("marker"))
result.countChunks() shouldBe (2 + nPrincipalMatches) // +[.bar, foobar]
}
}
}
@Test
fun rmSinglePropagationMatch() {
@ -739,7 +800,7 @@ class TestIncrementalProgram {
}
@Test
fun rmMatchWithDependencies() {
fun rmWithDependencies() {
val progSpec = MockIncrProgSpec(
setOf("main", "foo.baz", "foo.bar", "barbaz.qux", "baz.lax"),
setOf(sym0("foo"), sym0("baz"), sym0("bar"))
@ -851,8 +912,6 @@ class TestIncrementalProgram {
builder.removeRules(listOf("foo.bar"))
.relaunch("removed", progSpec, evalRes.token()) { result ->
println(result.token().journalView.chunks)
result.storeView().constraintSymbols() shouldBe setOf(sym0("lax"))
result.lastChunkSymbols() shouldBe listOf(sym0("lax"))
result.countChunks() shouldBe (nPrincipalMatches - 1 + 2)
@ -965,8 +1024,6 @@ class TestIncrementalProgram {
))
).relaunch("substituted", progSpec, evalRes.token()) { result ->
println(result.token().journalView.chunks)
result.countChunks() shouldBe (nPrincipalMatches - 1)
result.lastChunkSymbols() shouldBe listOf(sym0("bar"))
}
@ -1011,8 +1068,6 @@ class TestIncrementalProgram {
)
).launch("initial run", progSpec) { result ->
println(result.token().journalView.chunks)
result.storeView().constraintSymbols() shouldBe setOf(sym0("baz"))
result.lastChunkSymbols() shouldBe listOf(sym0("baz"))
@ -1038,7 +1093,7 @@ class TestIncrementalProgram {
))
).relaunch("substituted", progSpec, evalRes.token()) { result ->
println(result.token().journalView.chunks)
// println(result.token().journalView.chunks)
result.countChunks() shouldBe (nPrincipalMatches)
result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"))