Prune the whole related state in RuleMatchers on removal-phase. Add test case.
This commit is contained in:
parent
7acbbc3988
commit
65f1198a60
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -43,4 +43,6 @@ interface RuleMatchingProbe {
|
|||
|
||||
fun forgetSeen(occ: Occurrence): RuleMatchingProbe
|
||||
|
||||
fun forgetConsumed(occ: Occurrence): RuleMatchingProbe
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
Loading…
Reference in New Issue