Add partial sketch of removal phase of incremental algorithm.

For that: Extend DispatchingFront and RuleMatcherImpl interface with
'forget' methods which completely drops related matches from matchers' state;
Extend MatchJournal & Chunk interfaces with helper methods.
This commit is contained in:
Grigorii Kirgizov 2019-06-03 19:04:04 +03:00
parent 8b4d98f6e2
commit ddb67690f4
7 changed files with 140 additions and 19 deletions

View File

@ -17,7 +17,6 @@
package jetbrains.mps.logic.reactor.core
import com.github.andrewoma.dexx.collection.Maps
import javaslang.collection.Map
import jetbrains.mps.logic.reactor.core.internal.RuleMatchImpl
import com.github.andrewoma.dexx.collection.Map as PersMap
@ -77,10 +76,13 @@ class Dispatcher (val ruleIndex: RuleIndex) {
}
}
private constructor(pred: DispatchingFront, consumedMatch: RuleMatchEx) {
private constructor(pred: DispatchingFront, consumedMatch: RuleMatchEx, isForgetting: Boolean) {
this.ruletag2probe = pred.ruletag2probe
pred.ruletag2probe[consumedMatch.rule().uniqueTag()]?.let {
this.ruletag2probe = ruletag2probe.put(consumedMatch.rule().uniqueTag(), it.consume(consumedMatch))
this.ruletag2probe = ruletag2probe.put(
consumedMatch.rule().uniqueTag(),
if (!isForgetting) it.consume(consumedMatch) else it.forget(consumedMatch)
)
}
}
@ -112,11 +114,29 @@ class Dispatcher (val ruleIndex: RuleIndex) {
probe.contract(discarded)
})
/**
* Returns a new [DispatchingFront] instance that completely "forgot" occurrence:
* forgot all consumed matches involving it & is contracted on it accordingly.
*/
fun forget(dropped: Occurrence) = DispatchingFront(
// note contraction, it's needed to avoid adding dropped matches back again to allMatches
this.contract(dropped),
ruleIndex.forOccurrence(dropped).mapNotNull { rule ->
ruletag2probe[rule.uniqueTag()]
}.map { probe ->
probe.forget(dropped)
})
/**
* Serves to indicate that the specified [RuleMatchEx] has been processed (consumed) and has to
* be excluded from any further "match" set returned by [matches].
*/
internal fun consume(ruleMatch: RuleMatchEx) = DispatchingFront(this, ruleMatch)
internal fun consume(ruleMatch: RuleMatchEx) = DispatchingFront(this, ruleMatch, false)
/**
* Forgets that the specified [RuleMatchEx] has been consumed.
*/
internal fun forget(ruleMatch: RuleMatchEx) = DispatchingFront(this, ruleMatch, true)
}

View File

@ -33,10 +33,14 @@ interface RuleMatchingProbe {
fun consume(ruleMatch: RuleMatchEx): RuleMatchingProbe
fun forget(ruleMatch: RuleMatchEx): RuleMatchingProbe
fun expand(occ: Occurrence): RuleMatchingProbe
fun expand(occ: Occurrence, mask: BitSet): RuleMatchingProbe
fun contract(occ: Occurrence): RuleMatchingProbe
fun forget(occ: Occurrence): RuleMatchingProbe
}

View File

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

View File

@ -27,6 +27,7 @@ import jetbrains.mps.logic.reactor.program.*
import jetbrains.mps.logic.reactor.util.Id
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
@ -34,6 +35,8 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
fun logMatch(match: RuleMatch)
fun logActivation(occ: Occurrence)
fun activatingChunkOf(occ: Occurrence): Chunk?
fun currentPos(): Pos
fun resetPos()
fun reset(pastPos: Pos)
@ -51,17 +54,23 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
override fun toString() = (if (isDiscarded) '-' else '+') + occ.toString()
}
abstract fun entries(): List<Entry>
fun activated(): List<Occurrence> = entries().filter { !it.isDiscarded }.map { it.occ }
fun discarded(): List<Occurrence> = entries().filter { it.isDiscarded }.map { it.occ }
abstract fun entriesLog(): List<Entry>
fun activatedLog(): List<Occurrence> = entriesLog().filter { !it.isDiscarded }.map { it.occ }
fun discardedLog(): List<Occurrence> = entriesLog().filter { it.isDiscarded }.map { it.occ }
// Get the resulting set of activated occurrences
fun activated(): List<Occurrence> = HashSet<Id<Occurrence>>().apply {
entriesLog().forEach {
if (it.isDiscarded) remove(Id(it.occ)) else add(Id(it.occ))
}
}.map { it.wrapped }
abstract fun findOccurrence(ctr: Constraint): Occurrence?
abstract fun principalConstraint(): Constraint?
override fun toString() = "(id=$id, $justifications, ${match.rule().uniqueTag()}, ${entries()})"
override fun toString() = "(id=$id, $justifications, ${match.rule().uniqueTag()}, ${entriesLog()})"
override fun chunk(): Chunk = this
override fun entriesInChunk(): Int = entries().size
override fun entriesInChunk(): Int = entriesLog().size
}
abstract class Pos {
@ -77,7 +86,7 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
internal open class MatchJournalImpl(
private val ispec: IncrementalProgramSpec,
protected val ispec: IncrementalProgramSpec,
view: MatchJournal.View? = null
) : MatchJournal
{
@ -103,6 +112,8 @@ internal open class MatchJournalImpl(
private var pos: MutableListIterator<ChunkImpl> = hist.listIterator()
private var current: ChunkImpl = pos.next() // take the initial chunk, move pos
private val parentChunksIndex = HashMap<Occurrence, MatchJournal.Chunk>()
override fun iterator(): MutableIterator<MatchJournal.Chunk> = hist.iterator()
@ -133,6 +144,18 @@ internal open class MatchJournalImpl(
}
override fun activatingChunkOf(occ: Occurrence): MatchJournal.Chunk? {
if (ispec.isPrincipal(occ.constraint)) {
//todo: maintain index and find from index
// maintain, build from view, anything else?
return parentChunksIndex[occ]
}
// todo: for non-principal find manually... need it?
return null
}
override fun currentPos(): MatchJournal.Pos = current
// fixme: unclear, whether this makes sense here, in "pure" journal without store? along with reset and replay?
@ -178,7 +201,7 @@ internal open class MatchJournalImpl(
val set = HashSet<Id<Occurrence>>()
for (chunk in hist) { // initial chunk is counted too
chunk.entries().forEach {
chunk.entriesLog().forEach {
if (it.isDiscarded) set.remove(Id(it.occ)) else set.add(Id(it.occ))
}
if (chunk === current) {
@ -193,7 +216,7 @@ internal open class MatchJournalImpl(
{
var occurrences: MutableList<MatchJournal.Chunk.Entry> = mutableListOf()
override fun entries(): List<MatchJournal.Chunk.Entry> = occurrences
override fun entriesLog(): List<MatchJournal.Chunk.Entry> = occurrences
override fun findOccurrence(ctr: Constraint): Occurrence? =
occurrences.find { !it.isDiscarded && it.occ.constraint.symbol() == ctr.symbol() }?.occ

View File

@ -103,7 +103,7 @@ internal open class StateFrameStack() : ProcessingState, LogicalObserver
internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.DispatchingFront,
journal: MatchJournal,
journal: MatchJournalImpl,
ruleIndex: RuleIndex,
val trace: EvaluationTrace = EvaluationTrace.NULL,
val profiler: Profiler? = null)
@ -128,14 +128,68 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
}
fun invalidateByRules(ruleIds: Set<Any>) {
// for (ruleTag in rulesDiff.removed) {
//
// }
val justificationRoots = mutableListOf<Int>()
val it = this.iterator()
var prevChunk = it.next() // skip initial chunk
while (it.hasNext()) {
val chunk = it.next()
val toRemove = ruleIds.contains(chunk.match.rule().uniqueTag())
if (toRemove) {
justificationRoots.add(chunk.id)
// Remove the chunk from the journal
it.remove()
// 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
// todo: check/assert it
val matchedOccs = chunk.match.allHeads() as Iterable<Occurrence>
val (invalidatedOccs, validOccs) = matchedOccs.partition { occ ->
occ.justifications().intersects(justificationRoots)
}
assert(invalidatedOccs.isEmpty())
// todo: do need to reactivate only the main, matching~activating match?
// (i.e. don't reactivate additional, inactive heads that only completed the match?)
execQueue.addAll(validOccs.map { occ ->
val chunkPos = activatingChunkOf(occ)
if (chunkPos != null) ExecPos(chunkPos, occ) else null
}.filterNotNull())
continue
}
val toInvalidate = chunk.justifications.intersects(justificationRoots)
if (toInvalidate) {
// Remove the chunk from the journal
it.remove()
// 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)
// 'Undo' all activated in this chunk occurrences and related to them matches.
chunk.activated().forEach {
dispatchingFront = dispatchingFront.forget(it)
}
}
prevChunk = chunk
}
}
private fun Justs.intersects(other: Iterable<Int>): Boolean = other.any { this.contains(it) }
private data class MatchCandidate(val rule: Rule, val occ: Occurrence, val occParentId: Int)
fun addRuleApplications(rules: Iterable<Rule>) {
// 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>()
val it = this.iterator()
@ -298,5 +352,6 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
}
private fun RuleMatch.allStored() = (matchHeadKept() + matchHeadReplaced()).all { co -> (co as Occurrence).stored }
private fun RuleMatch.allHeads() = matchHeadKept() + matchHeadReplaced()
private fun RuleMatch.allStored() = allHeads().all { co -> (co as Occurrence).stored }
}

View File

@ -347,12 +347,20 @@ internal class ReteRuleMatcherImpl(val rule: Rule) : RuleMatcher {
return this
}
override fun forget(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 {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun forget(ruleMatch: RuleMatchEx): RuleMatchingProbe {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
fun indexOf(occurrence: ConstraintOccurrence): Int =
seenOcc2Idx[occurrence] ?: (nextOccIdx++).also { idx -> seenOcc2Idx[occurrence] = idx }

View File

@ -66,6 +66,12 @@ internal class RuleMatcherImpl(private val ruleLookup: RuleLookup,
consumedSignatures.add(ruleMatch.signature()),
genId)
override fun forget(ruleMatch: RuleMatchEx): RuleMatchingProbe =
RuleMatchFront(nodes,
seenOccurrences,
consumedSignatures.remove(ruleMatch.signature()),
genId)
override fun expand(occ: Occurrence): RuleMatchingProbe =
expand(occ, bitSetOfOnes(head.size))
@ -94,6 +100,11 @@ internal class RuleMatcherImpl(private val ruleLookup: RuleLookup,
return RuleMatchFront(newNodes, seenOccurrences, consumedSignatures, genId + 1)
}
override fun forget(occ: Occurrence): RuleMatchFront {
val newConsumed = Sets.copyOf(consumedSignatures.filter{ !it.contains(Id(occ)) })
return RuleMatchFront(nodes, seenOccurrences, newConsumed, genId)
}
}
open inner class MatchNode(val subst: Subst, val vacant: BitSet = bitSetOfOnes(head.size)) {