MatchJournal now adds chunks for principal occurrences: search is simplified, case of several principal occs in rule is handled.

This commit is contained in:
Grigorii Kirgizov 2019-07-04 14:51:46 +03:00 committed by Fedor Isakov
parent 43a8838c5a
commit ef2627cab4
5 changed files with 118 additions and 129 deletions

View File

@ -112,14 +112,19 @@ class Dispatcher (val ruleIndex: RuleIndex) {
}
/**
* Returns a new [DispatchingFront] instance which "forgot" that it seen occurrence.
* Need it for incremental reactivations to discern them from reactivations due to logicals.
* Returns a new [DispatchingFront] instance which "forgot" that it has seen occurrence.
* Needed for incremental reactivations to discern them from reactivations due to logicals.
*/
internal fun forgetSeen(dropped: Occurrence) =
forRelatedProbe(dropped) { probe ->
probe.forgetSeen(dropped)
}
/**
* Returns a new [DispatchingFront] instance which contracts state with this occurrence
* and "forgets" that has seen it or that consumed any matches involving it.
* Needed for pruning outdated unrelevant state on incremental reactivations.
*/
internal fun forget(dropped: Occurrence) =
forRelatedProbe(dropped) { probe ->
probe.contract(dropped).forgetSeen(dropped).forgetConsumed(dropped)

View File

@ -91,26 +91,11 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
* Also serves as a comparator for positions: later in journal means greater.
*/
interface Index : Comparator<Pos> {
/**
* Returns [Chunk] where provided occurrence was activated.
* Works not for all occurrences: may return null even if there exists activating chunk.
* Must work for principal occurrences.
* Returns [Chunk] where provided principal occurrence was activated.
* Returns null for non-principal occurrences.
*/
fun activatingChunkOf(occId: Id<Occurrence>): Chunk?
/**
* Returns position of the first principal occurrence activated in provided chunk.
* Returns null if the chunk activates no principal occurrences.
*/
fun principalOccurrenceOf(chunk: Chunk): Pos?
/**
* Find [Pos] by occurrence.
*/
fun principalPos(occ: Id<Occurrence>): Pos? =
// just a composition of two operations
activatingChunkOf(occ)?.let { principalOccurrenceOf(it) }
fun activatingChunkOf(occId: Id<Occurrence>): OccChunk?
/**
* Find [Pos] at which provided match was triggered.
@ -121,7 +106,7 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
// The latest matched occurrence from match's head is(by definition)
// the occurrence which activated this match.
match.signature().mapNotNull { occSig ->
occSig?.let { principalPos(it) }
occSig?.let { activatingChunkOf(it)?.toPos() }
}.maxWith(this) // compare positions: find latest
}
@ -136,11 +121,13 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
)
}
abstract class Chunk(val match: RuleMatch, val id: Int, val justifications: Justs) : MatchJournalChunk
{
override fun match(): RuleMatch = match
override fun id(): Int = id
override fun justifications(): TIntSet = justifications
interface Chunk : MatchJournalChunk {
val id: Int
val justifications: Justs
// fixme: hide rm-mutability
var entries: MutableList<Entry>
override fun entriesLog(): List<Entry> = entries
data class Entry(val occ: Occurrence, val discarded: Boolean = false) : MatchJournalChunk.Entry {
override fun occ(): Occurrence = occ
@ -148,10 +135,9 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
override fun toString() = (if (discarded) '-' else '+') + occ.toString()
}
fun isDescendantOf(chunkId: Int): Boolean = justifications().contains(chunkId)
fun isTopLevel(): Boolean = justifications().size() <= 1 // this condition implies that there're no ancestor chunks
fun isDescendantOf(chunkId: Int): Boolean = justifications.contains(chunkId)
fun isTopLevel(): Boolean = justifications.size() <= 1 // this condition implies that there're no ancestor chunks
abstract override fun entriesLog(): List<Entry>
fun activatedLog(): List<Occurrence> = entriesLog().filter { !it.discarded() }.map { it.occ() as Occurrence }
fun discardedLog(): List<Occurrence> = entriesLog().filter { it.discarded() }.map { it.occ() as Occurrence }
@ -161,11 +147,31 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
*/
fun activated(): List<Occurrence> = entriesLog().allOccurrences()
override fun toString() = "(id=${id()}, ${justifications()}, ${match().rule().uniqueTag()}, ${entriesLog()})"
fun toPos(): Pos = Pos(this, entriesLog().size)
}
class MatchChunk(override val id: Int, val match: RuleMatch) : Chunk {
override val justifications: Justs = match.headJustifications().apply { add(id) }
override var entries: MutableList<Chunk.Entry> = mutableListOf()
override fun entriesLog(): List<Chunk.Entry> = entries
override fun toString() = "(id=$id, $justifications, ${match.rule().tag()}, $entries)"
}
class OccChunk(override val id: Int, val occ: Occurrence) : Chunk {
init { occ.justifications.add(id) }
override val justifications: Justs
get() = occ.justifications
override var entries: MutableList<Chunk.Entry> = mutableListOf()
override fun toString() = "(id=$id, $justifications, activation of $occ)"
}
open class Pos(val chunk: Chunk, val entriesCount: Int) {
val occ: Occurrence
get() = chunk.entriesLog()[entriesCount - 1].occ()
@ -176,6 +182,8 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
&& other.entriesCount == entriesCount
override fun hashCode(): Int = 31 * chunk.hashCode() + entriesCount
override fun toString(): String = "($chunk, $entriesCount)"
}
}

View File

@ -20,6 +20,7 @@ import jetbrains.mps.logic.reactor.core.Controller
import jetbrains.mps.logic.reactor.core.Justs
import jetbrains.mps.logic.reactor.core.Occurrence
import jetbrains.mps.logic.reactor.core.justsOf
import jetbrains.mps.logic.reactor.core.internal.MatchJournal.*
import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
import jetbrains.mps.logic.reactor.evaluation.MatchJournalView
import jetbrains.mps.logic.reactor.evaluation.RuleMatch
@ -31,25 +32,28 @@ import jetbrains.mps.logic.reactor.program.*
import jetbrains.mps.logic.reactor.util.Id
import java.util.*
//typealias Chunk = MatchJournal.Chunk
internal open class MatchJournalImpl(
private val ispec: IncrementalProgramSpec,
view: MatchJournalView? = null
) : MatchJournal {
// invariant: never empty
private val hist: IteratorMutableList<ChunkImpl>
private val hist: IteratorMutableList<Chunk>
private var nextChunkId: Int
init {
if (view == null) {
nextChunkId = 0
val initChunk = ChunkImpl(InitRuleMatch, nextChunkId, justsOf(nextChunkId))
nextChunkId++
hist = IteratorMutableList(LinkedList<ChunkImpl>().apply { add(initChunk) })
val initChunk = MatchChunk(nextChunkId++, InitRuleMatch)
hist = IteratorMutableList(LinkedList<Chunk>().apply { add(initChunk) })
} else {
// assert that initial chunk is present
assert (view.chunks.first().match() is InitRuleMatch)
hist = IteratorMutableList(LinkedList(view.chunks as List<ChunkImpl>))
with (view.chunks.first()) {
assert(this is MatchChunk && match is InitRuleMatch)
}
hist = IteratorMutableList(LinkedList(view.chunks as List<Chunk>))
nextChunkId = view.nextChunkId
}
}
@ -57,37 +61,31 @@ internal open class MatchJournalImpl(
constructor(view: MatchJournal.View? = null) : this(IncrementalProgramSpec.DefaultSpec, view)
// pointer to current position in history where logging (chunk additions) and log erasing (chunk removals) happens
private var posPtr: MutableListIterator<ChunkImpl> = hist.listIterator()
private var posPtr: MutableListIterator<Chunk> = hist.listIterator()
// invariant: always contains valid chunk
private var current: ChunkImpl = posPtr.next() // take the initial chunk, move posPtr
private var current: Chunk = posPtr.next() // take the initial chunk, move posPtr
override fun iterator(): MutableIterator<MatchJournal.Chunk> = hist.iterator()
override fun iterator(): MutableIterator<Chunk> = hist.iterator()
override fun logMatch(match: RuleMatch) {
// Two cases when a new chunk is created:
// either the set of justifications isn't empty
// or we directly know that we deal with a principal rule.
val justs = match.headJustifications()
if (ispec.isPrincipal(match.rule()) || !justs.isEmpty) {
justs.add(nextChunkId)
val newChunk = ChunkImpl(match, nextChunkId, justs)
posPtr.add(newChunk)
++nextChunkId
current = newChunk
if (ispec.isPrincipal(match.rule())) {
current = MatchChunk(nextChunkId++, match)
posPtr.add(current)
}
// Log discards
// Log discarded occurrences
(match as RuleMatchImpl).forEachReplaced { occ ->
current.entries.add(MatchJournal.Chunk.Entry(occ, true))
current.entries.add(Chunk.Entry(occ, true))
}
}
override fun logActivation(occ: Occurrence) {
current.entries.add(MatchJournal.Chunk.Entry(occ))
if (ispec.isPrincipal(occ.constraint)) {
current = OccChunk(nextChunkId++, occ)
posPtr.add(current)
}
current.entries.add(Chunk.Entry(occ))
}
@ -138,7 +136,7 @@ internal open class MatchJournalImpl(
override fun storeView(): StoreView = StoreViewImpl(allOccurrences())
override fun index(): MatchJournal.Index = IndexImpl(ispec, hist)
override fun index(): MatchJournal.Index = IndexImpl(hist)
private fun allOccurrences(): Sequence<Occurrence> {
// the following loop doesn't handle this case of starting posPtr, when 'current' isn't valid (e.g. just right after resetPos())
@ -168,44 +166,22 @@ internal open class MatchJournalImpl(
override fun listIterator(index: Int): MutableListIterator<E> = l.listIterator(index)
}
private class ChunkImpl(match: RuleMatch, id: Int, justifications: Justs) : MatchJournal.Chunk(match, id, justifications) {
var entries: MutableList<Entry> = mutableListOf()
override fun entriesLog(): List<Entry> = entries
}
private class IndexImpl(ispec: IncrementalProgramSpec, chunks: Iterable<ChunkImpl>): MatchJournal.Index
private class IndexImpl(chunks: Iterable<Chunk>): MatchJournal.Index
{
private val chunkOrder: Map<Int, Int>
// only for principal constraints
private val parentChunks: Map<Id<Occurrence>, MatchJournal.Chunk>
private val principalOccurrences: Map<Int, MatchJournal.Pos>
private val chunkOrder = HashMap<Int, Int>()
private val occChunks = HashMap<Id<Occurrence>, OccChunk>()
init {
chunkOrder = HashMap<Int, Int>().apply {
chunks.forEachIndexed { index, chunk -> put(chunk.id(), index) }
}
chunks.forEachIndexed { index, chunk ->
chunkOrder[chunk.id] = index
val m = HashMap<Id<Occurrence>, MatchJournal.Chunk>()
val m2 = HashMap<Int, MatchJournal.Pos>()
chunks.forEach { chunk ->
// actually there should be only a single principal occurrence, 'find' is enough
chunk.entriesLog().forEachIndexed { index, e ->
if (ispec.isPrincipal(e.occ.constraint()) && !e.discarded()) {
m[Id(e.occ)] = chunk
m2[chunk.id] = MatchJournal.Pos(chunk, index + 1)
}
if (chunk is OccChunk) {
occChunks[Id(chunk.occ)] = chunk
}
}
parentChunks = m
principalOccurrences = m2
}
override fun activatingChunkOf(occId: Id<Occurrence>) = parentChunks[occId]
override fun principalOccurrenceOf(chunk: MatchJournal.Chunk) = principalOccurrences[chunk.id]
override fun activatingChunkOf(occId: Id<Occurrence>) = occChunks[occId]
// todo: throw for invalid positions?
override fun compare(lhs: MatchJournal.Pos, rhs: MatchJournal.Pos): Int {
@ -231,19 +207,19 @@ internal open class MatchJournalImpl(
object EmptyRule : Rule() {
override fun kind(): Kind = Kind.PROPAGATION
override fun uniqueTag(): Any = tag().hashCode()
override fun tag(): String = "initialrule${"initial_rule".hashCode()}"
override fun tag(): String = "initialrule${"initialrule".hashCode()}"
override fun headKept(): Iterable<Constraint> = emptyList()
override fun headReplaced(): Iterable<Constraint> = emptyList()
override fun guard(): Iterable<Predicate> = emptyList()
override fun bodyAlternation(): Iterable<Iterable<AndItem>> = emptyList() // fixme: maybe provide 'main' constraint prod?
override fun bodyAlternation(): Iterable<Iterable<AndItem>> = emptyList()
override fun all(): Iterable<AndItem> = emptyList()
}
}
}
fun MatchJournal.justs() = this.currentPos().chunk.justifications()
fun MatchJournal.justs() = this.currentPos().chunk.justifications
private fun RuleMatch.headJustifications(): Justs {
fun RuleMatch.headJustifications(): Justs {
val res: Justs = justsOf()
this.matchHeadKept().forEach { it.justifications().let { res.addAll(it) } }
this.matchHeadReplaced().forEach { it.justifications().let { res.addAll(it) } }

View File

@ -68,7 +68,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
private data class ExecPos(val pos: MatchJournal.Pos, val activeOcc: Occurrence)
private data class MatchCandidate(val rule: Rule, val occ: Occurrence, val occParentId: Int)
private data class MatchCandidate(val rule: Rule, val occChunk: MatchJournal.OccChunk)
/**
@ -87,13 +87,12 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
while (it.hasNext()) {
val chunk = it.next()
val toRemove = ruleIds.contains(chunk.match().rule().uniqueTag())
if (toRemove) {
if (chunk is MatchJournal.MatchChunk && ruleIds.contains(chunk.match.rule().uniqueTag())) {
justificationRoots.add(chunk.id)
// 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>
val matchedOccs = chunk.match.allHeads() as Iterable<Occurrence>
val (invalidatedOccs, validOccs) = matchedOccs.partition { occ ->
occ.justifications().intersects(justificationRoots)
}
@ -102,33 +101,36 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
// 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.mapNotNull { occ ->
journalIndex.activatingChunkOf(Id(occ))?.let { chunkPos ->
ExecPos(chunkPos.toPos(), occ)
journalIndex.activatingChunkOf(Id(occ))?.let { chunk ->
ExecPos(chunk.toPos(), occ)
}
})
}
// Invalidating dependent chunks
val toInvalidate = toRemove || chunk.justifications.intersects(justificationRoots)
if (toInvalidate) {
// Remove the chunk from the journal
if (chunk.justifications.intersects(justificationRoots)) {
// Remove chunk from the journal
it.remove()
trace.invalidate(chunk.match())
// 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
chunk.activated().forEach {
dispatchingFront = dispatchingFront.forget(it)
}
if (chunk is MatchJournal.MatchChunk) {
trace.invalidate(chunk.match)
// 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)
}
}
}
prevChunk = chunk
@ -146,21 +148,19 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
var prevChunk = chunk
while (true) {
// Does this chunk have principal occurrence and can activate anything at all?
journalIndex.principalOccurrenceOf(chunk)?.occ?.let { pOcc ->
if (chunk is MatchJournal.OccChunk) {
// filters out rules using occurrence's arguments
val allRuleCandidates = ruleIndex.forOccurrence(pOcc).map { it.uniqueTag() }.toHashSet()
val allRuleCandidates = ruleIndex.forOccurrence(chunk.occ).map { it.uniqueTag() }.toHashSet()
for (rule in rules) {
if (allRuleCandidates.contains(rule.uniqueTag()) && canMatch(rule, pOcc)) {
if (allRuleCandidates.contains(rule.uniqueTag()) && canMatch(rule, chunk.occ)) {
// Can this rule be matched by principal occurrence?
// Then we will need to find the place among existing child chunks
// (i.e. among some number of following ones)
// to activate this occurrence, to (possibly) match this rule.
// Also remember the parent justification of this rule candidate
// to drop it from monitoring when child chunks end.
activationCandidates.add(MatchCandidate(rule, pOcc, chunk.id))
activationCandidates.add(MatchCandidate(rule, chunk))
// todo: also use the rule to help Dispatcher in future?
// i.e. try matching only on the candidate rule
}
@ -169,13 +169,14 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
val aIt = activationCandidates.listIterator()
while (aIt.hasNext()) {
val (candRule, candOcc, parentId) = aIt.next()
val (candRule, occChunk) = aIt.next()
// Place to activate candidate:
// either as the last one, after all existing activations
// or according to the ordering between rules.
val placeToInsertFound = ruleOrdering.compare(chunk.match().rule(), candRule) > 0
val childChunksEnded = !chunk.isDescendantOf(parentId)
// either according to the ordering between rules.
// or as the last one, after all existing activations
val placeToInsertFound =
chunk is MatchJournal.MatchChunk && ruleOrdering.compare(chunk.match.rule(), candRule) > 0
val childChunksEnded = !chunk.isDescendantOf(occChunk.id)
val pos =
// We need the previous chunk as pos here (i.e. adding after it).
@ -184,8 +185,8 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
else if (!it.hasNext()) chunk.toPos()
else continue
execQueue.offer(ExecPos(pos, candOcc))
trace.potentialMatch(candOcc, candRule)
execQueue.offer(ExecPos(pos, occChunk.occ))
trace.potentialMatch(occChunk.occ, candRule)
// Drop the candidate if appropriate activation place is found.
aIt.remove()
}

View File

@ -16,7 +16,7 @@
package jetbrains.mps.logic.reactor.evaluation;
import gnu.trove.set.TIntSet;
//import gnu.trove.set.TIntSet;
import java.util.List;
@ -26,8 +26,7 @@ public interface MatchJournalChunk {
boolean discarded();
}
RuleMatch match();
int id();
TIntSet justifications();
// int id();
// TIntSet justifications();
List<Entry> entriesLog();
}