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:
parent
8b4d98f6e2
commit
ddb67690f4
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue