Reactor: drop most of incrementality- and preamble-related stuff

Neither incremental evaluation of program or "preamble" features
are useful, but they complicate things and cause major bugs.
This commit is contained in:
Fedor Isakov 2021-04-13 17:04:26 +02:00
parent 49357ab87a
commit 4d1511d794
21 changed files with 22 additions and 1562 deletions

View File

@ -109,30 +109,7 @@ class Dispatcher (val ruleIndex: RuleIndex, prevState: DispatchingFrontState = e
occId2tags.remove(discarded.identity)
?.mapNotNull { ruleTag -> ruletag2probe[ruleTag] }
?.map { probe -> probe.contract(discarded) })
/**
* Returns a [DispatchingFront] instance which "forgot" that it already expanded the occurrence.
* After that the occurrence can be expanded again without triggering observer reactivation logic.
* Needed to discern incremental reactivation from observers reactivation.
* In other words, the state of [DispatchingFront] connected with this occurrence
* transitions from "fully-expanded" to "partially-expanded".
*/
internal fun forgetExpanded(dropped: Occurrence): DispatchingFront = DispatchingFront(this,
ruleIndex.forOccurrence(dropped)
.mapNotNull { rule -> ruletag2probe[rule.uniqueTag()] }
.map { probe -> probe.forgetExpanded(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): DispatchingFront = DispatchingFront(this,
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].
@ -147,19 +124,6 @@ class Dispatcher (val ruleIndex: RuleIndex, prevState: DispatchingFrontState = e
return DispatchingFront(this)
}
/**
* Forgets that the specified [RuleMatchEx] has been consumed.
*/
internal fun forget(consumedMatch: RuleMatchEx): DispatchingFront {
ruletag2probe[consumedMatch.rule().uniqueTag()]?.let {
val probe = it.forget(consumedMatch)
if (RULE_MATCHER_PROBE_PERSISTENT) {
ruletag2probe[consumedMatch.rule().uniqueTag()] = probe
}
}
return DispatchingFront(this)
}
}
}

View File

@ -54,19 +54,6 @@ interface RuleMatchingProbe {
*/
fun consume(ruleMatch: RuleMatchEx): RuleMatchingProbe
/**
* Clears all internal state related to [ruleMatch].
* Effect is as if [ruleMatch] was never seen.
*/
fun forget(ruleMatch: RuleMatchEx): RuleMatchingProbe
/**
* Clears all state related to [Occurrence] [occ].
* Same as [contract], but also clears internal state.
* Effect is as if [occ] was never seen.
*/
fun forget(occ: Occurrence): RuleMatchingProbe
fun expand(occ: Occurrence): RuleMatchingProbe
fun expand(occ: Occurrence, mask: BitSet, profiler: Profiler? = null): RuleMatchingProbe
@ -75,22 +62,4 @@ interface RuleMatchingProbe {
* Tells the probe that [occ] can't be used for finding matches.
*/
fun contract(occ: Occurrence): RuleMatchingProbe
/**
* Turns the Probe's processing-related state linked with
* [occ] from "fully-expanded" to "partially-expanded".
* Doesn't modify internal state related to [matches], instead
* modifies how the next [expand] of [occ] will be processed.
*
* Notion of "partially-expanded" state is important
* only for incremental execution.
*/
fun forgetExpanded(occ: Occurrence): RuleMatchingProbe
/**
* Clears internal state related to [consume]
* as if [occ] were never seen by this probe.
*/
fun forgetConsumed(occ: Occurrence): RuleMatchingProbe
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2014-2019 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.logic.reactor.core
import jetbrains.mps.logic.reactor.program.Rule
@Deprecated("superfluous")
class RulesDiff(
preserved: Iterable<Rule>,
val added: Iterable<Rule>,
val removed: Set<Any>
) {
private val preserved: Map<Any, Rule> = HashMap<Any, Rule>().apply {
preserved.forEach { put(it.uniqueTag(), it) }
}
fun getPreservedRule(utag: Any): Rule? = preserved[utag]
fun getPreserved(): Set<Any> = preserved.keys
companion object {
@JvmStatic
fun emptyDiff() = RulesDiff(emptyList(), emptyList(), emptySet())
@JvmStatic
fun findDiff(old: Iterable<Rule>, new: Iterable<Rule>): RulesDiff {
val oldTagsSet = old.map { it.uniqueTag() }.toHashSet()
val newTagsSet = new.map { it.uniqueTag() }.toHashSet()
val added = new.filter { !oldTagsSet.contains(it.uniqueTag()) }
val (preserved, removed) = old.partition { newTagsSet.contains(it.uniqueTag()) }
val removedTags: Set<Any> = removed.map { it.uniqueTag() }.toSet()
return RulesDiff(preserved, added, removedTags)
}
}
}

View File

@ -51,8 +51,6 @@ internal class ConstraintsProcessing(
fun setStrategy(strategy: ProcessingStrategy) { this.incrementalProcessing = strategy }
fun getStateCleaner(): ProgramStateCleaner = ProgramStateCleaner()
fun getFrontState(): DispatchingFrontState = dispatchingFront.state()
fun engage(controller: Controller) {
@ -63,18 +61,6 @@ internal class ConstraintsProcessing(
logicalState.clearController(controller)
}
fun activateContinue(controller: Controller, activeOcc: Occurrence, parent: MatchJournal.MatchChunk): FeedbackStatus {
assert(activeOcc.stored)
// Forget that occurrence was seen. Otherwise it will be
// processed as with reactivation through observers.
this.dispatchingFront = dispatchingFront.forgetExpanded(activeOcc)
trace.activateContinue(activeOcc)
return processActivated(controller, activeOcc, parent, FeedbackStatus.NORMAL())
}
fun evaluate(controller: Controller, prototype: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus =
profiler.profile<FeedbackStatus>("activate_${prototype.constraint().symbol()}") {
// fixme: ensure justifications are tracked (incremented) correctly in processing & creator
@ -185,17 +171,11 @@ internal class ConstraintsProcessing(
profiler.profile("discardOccurrence") {
match.forEachReplaced { occ ->
// Principal occurrences must be preserved for future incremental evaluation sessions
if (!occ.isPrincipal) {
this.dispatchingFront = dispatchingFront.contract(occ)
}
this.dispatchingFront = dispatchingFront.contract(occ)
occ.stored = false
profiler.profile("terminateOccurrence") {
occ.terminate(logicalState)
}
trace.discard(occ)
@ -203,19 +183,7 @@ internal class ConstraintsProcessing(
}
}
inner class ProgramStateCleaner{
fun erase(occurrence: Occurrence) {
dispatchingFront = dispatchingFront.forget(occurrence)
occurrence.terminate(logicalState)
incrementalProcessing.processInvalidated(occurrence, logicalState)
}
fun erase(match: RuleMatchEx) {
dispatchingFront = dispatchingFront.forget(match)
}
}
/**
* Encapsulates logic for deriving [Evidence] and [Justifications] for a new [Occurrence].
*/

View File

@ -1,26 +0,0 @@
/*
* Copyright 2014-2019 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.logic.reactor.core.internal
import jetbrains.mps.logic.reactor.core.Occurrence
internal interface ContinuedActivationSink {
fun offerAll(continueFromPos: MatchJournal.Pos, occs: Iterable<Occurrence>)
fun offer(continueFromPos: MatchJournal.Pos, ancestor: MatchJournal.OccChunk): Boolean
}

View File

@ -65,7 +65,6 @@ internal interface SessionManager {
* Bundle of all entities involved in a session.
*/
internal data class SessionParts(
val preambleInfo: PreambleInfo,
val ruleIndex: RuleIndex,
val journal: MatchJournal,
val logicalState: LogicalState,
@ -89,45 +88,10 @@ internal class EvaluationSessionImpl private constructor (
@Suppress("UNCHECKED_CAST")
override fun <T : Any> parameter(key: ParameterKey<T>): T? = params ?.get(key) as T
private fun SessionToken?.hasBindingObservers() =
(this as? SessionTokenImpl)?.principalObservers?.isNotEmpty() ?: false
private fun launch(token: SessionToken?, store: OccurrenceStore, main: Constraint): EvaluationResult {
val sessionProcessing: SessionManager =
with(incrementality) {
when {
ability().allowed() -> when {
// it's a too restrictive place for fallback, happens almost always
//token.hasBindingObservers() -> FallbackProcessingSession()
incrLevel() == IncrementalSpec.IncrLevel.Full -> IncrementalProcessingSession()
incrLevel() == IncrementalSpec.IncrLevel.Preamble -> PreambleProcessingSession()
else -> DefaultProcessingSession()
}
else -> DefaultProcessingSession()
}
}
val session =
with(sessionProcessing) {
if (token != null) {
(token as? SessionTokenImpl)?.setStore(store)
nextSession(token)
} else {
firstSession()
}
}
try {
return sessionProcessing.runSession(session, main)
// fixme: exception is a dirty way to abort incremental processing.
} catch (e: IncrementalContractViolationException) {
with(FallbackProcessingSession(session)) {
val fallbackSession =
if (token != null) nextSession(token) else firstSession()
return runSession(fallbackSession, main)
}
}
private fun launch(token: SessionToken?, main: Constraint): EvaluationResult {
val sessionProcessing: SessionManager = DefaultProcessingSession()
val session = sessionProcessing.firstSession()
return sessionProcessing.runSession(session, main)
}
open inner class DefaultProcessingSession: SessionManager {
@ -152,11 +116,11 @@ internal class EvaluationSessionImpl private constructor (
val controller = ControllerImpl(supervisor, processing, incrementality, trace, profiler)
return SessionParts(program.preambleInfo(), ruleIndex, journal, logicalState, controller, processing, processingStrategy, PrincipalObserverDispatcher.EMPTY)
return SessionParts(ruleIndex, journal, logicalState, controller, processing, processingStrategy, PrincipalObserverDispatcher.EMPTY)
}
override fun endSession(session: SessionParts): SessionToken = with(session) {
SessionTokenImpl(journal.view(), emptyList(), ruleIndex.toRules(), emptyFrontState(), ruleIndex, logicalState, principalObservers.apply { clearTriggerReceiver() })
SessionTokenImpl(journal.view(), ruleIndex.toRules(), emptyFrontState(), ruleIndex, logicalState, principalObservers.apply { clearTriggerReceiver() })
}
override fun runSession(session: SessionParts, main: Constraint): EvaluationResult = with(session) {
@ -169,190 +133,7 @@ internal class EvaluationSessionImpl private constructor (
protected fun SessionParts.run(main: Constraint): FeedbackStatus = strategy.run(processing, controller, main)
}
/**
* Same as [DefaultProcessingSession], but also returns
* [EvaluationResult.invalidFeedbackKeys] & [EvaluationResult.invalidRules]
* to signify that results of a previous session must be cleared.
*/
inner class FallbackProcessingSession(private val abortedSession: SessionParts?): DefaultProcessingSession() {
constructor(): this(null)
val invalidatedFeedback: MutableFeedbackKeySet = mutableSetOf()
val invalidatedRules: MutableList<Any> = mutableListOf()
override fun nextSession(token: SessionToken): SessionParts {
invalidateToken(token.journalView as MatchJournal.View)
invalidateAborted()
return super.nextSession(token)
}
override fun runSession(session: SessionParts, main: Constraint): EvaluationResult = with(session) {
val status = run(main)
controller.shutDown()
val newToken = endSession(session)
invalidatedFeedback.addAll(strategy.invalidatedFeedback())
invalidatedRules.addAll(strategy.invalidatedRules())
return EvaluationResultImpl(newToken, status, invalidatedFeedback, invalidatedRules)
}
private fun invalidateAborted() {
if(abortedSession != null) {
with (abortedSession) {
invalidatedFeedback.addAll(strategy.invalidatedFeedback())
invalidatedRules.addAll(strategy.invalidatedRules())
}
}
}
private fun invalidateToken(view: MatchJournal.View) {
for(chunk in view.chunks) {
if (chunk is MatchJournal.MatchChunk) {
invalidatedFeedback.add(chunk.match.feedbackKey)
invalidatedRules.add(chunk.ruleUniqueTag)
}
}
}
}
open inner class IncrementalProcessingSession(): DefaultProcessingSession() {
/**
* Same as [DefaultProcessingSession.firstSession],
* but uses [GroundProcessing] instead of [EmptyProcessing]
*/
override fun firstSession(): SessionParts {
val ruleIndex = RuleIndex(program.rules())
val journal = MatchJournalImpl(incrementality)
val logicalState = LogicalState()
val dispatchingFront = Dispatcher(ruleIndex).front()
val principalObservers = LogicalBindObserverDispatcher()
val processingStrategy = GroundProcessing(incrementality, principalObservers)
val processing = ConstraintsProcessing(dispatchingFront, journal, logicalState, incrementality, trace, profiler)
processing.setStrategy(processingStrategy)
val controller = ControllerImpl(supervisor, processing, incrementality, trace, profiler)
return SessionParts(program.preambleInfo(), ruleIndex, journal, logicalState, controller, processing, processingStrategy, principalObservers)
}
override fun nextSession(token: SessionToken): SessionParts {
val tkn = token as SessionTokenImpl
val logicalState = tkn.logicalState
val ruleIndex = tkn.ruleIndex.apply { updateIndexFromRules(program.rules()) }
val journal = MatchJournalImpl(incrementality, tkn.journalView as MatchJournal.View)
val front = Dispatcher(ruleIndex, tkn.getFrontState()).front()
val processing = ConstraintsProcessing(front, journal, logicalState, incrementality, trace, profiler)
val principalObservers = tkn.principalObservers
val processingStrategy = IncrementalProcessing(
incrementality, journal, program.newRules(), program.droppedRules(),
processing.getStateCleaner(), ruleIndex, principalObservers, trace
)
processing.setStrategy(processingStrategy)
val controller = ControllerImpl(supervisor, processing, incrementality, trace, profiler)
return SessionParts(program.preambleInfo(), ruleIndex, journal, logicalState, controller, processing, processingStrategy, principalObservers)
}
override fun endSession(session: SessionParts): SessionToken = with(session) {
val histView = journal.view()
processing.resetStore() // clear observers
val principalState = sessionState(frontState).resetLookup()
principalObservers.clearTriggerReceiver()
return SessionTokenImpl(histView, emptyList(), ruleIndex.toRules(), principalState, ruleIndex, logicalState, principalObservers)
}
/**
* Preserve data needed between sessions:
* preserve only relevant and non-empty RuleMatchers
*/
protected fun sessionState(frontState: DispatchingFrontState) =
frontState.filterValues { ruleMatcher ->
incrementality.isPrincipal(ruleMatcher.rule()) || ruleMatcher.probe().hasOccurrences()
}
}
inner class PreambleProcessingSession(): IncrementalProcessingSession() {
private var inputStore: OccurrenceStore = emptyStore()
override fun nextSession(token: SessionToken): SessionParts {
val tkn = token as SessionTokenImpl
val logicalState = LogicalState()
// fixme: ensure why ruleIndex.update here leads to errors
val ruleIndex = tkn.ruleIndex.apply{ updateIndexFromRules(program.rules()) }
// val ruleIndex = RuleIndex(program.rules())
val front = Dispatcher(ruleIndex).front() // new dispatcher front
val journal = MatchJournalImpl(incrementality) // new journal
val processing = ConstraintsProcessing(front, journal, logicalState, incrementality, trace, profiler)
this.inputStore = tkn.principalStore
val processingStrategy = CachedOccurrencesProcessing(incrementality, inputStore)
processing.setStrategy(processingStrategy)
val controller = ControllerImpl(supervisor, processing, incrementality, trace, profiler)
return SessionParts(program.preambleInfo(), ruleIndex, journal, logicalState, controller, processing, processingStrategy, tkn.principalObservers)
}
override fun endSession(session: SessionParts): SessionToken = with(session) {
val histView = journal.view()
val outputOccurrences = histView.filterOccurrences(inputStore)
outputOccurrences.forEach{ it.terminate(logicalState) }
processing.resetStore() // clear observers
logicalState.reset()
principalObservers.clearTriggerReceiver()
val rules = ruleIndex.toRules().filter(preambleInfo::inPreamble)
SessionTokenImpl(histView, outputOccurrences, rules, emptyFrontState(), ruleIndex, LogicalState())
}
private fun MatchJournal.View.filterOccurrences(without: OccurrenceStore = emptyStore()): OccurrenceStore {
val withoutSources = without.asSequence().map { it.cacheKey() }.toHashSet()
return chunks.asSequence()
.mapNotNull { (it as? MatchJournal.OccChunk)?.occ }
.filter { it.stored }
.filter { !withoutSources.contains(it.cacheKey()) }
.toList()
}
private fun ConstraintOccurrence.cacheKey() = sourceRule() to constraint().symbol()
private fun getPreamble(session: SessionParts): MatchJournal.View = with(session) {
val journalView = journal.view()
val preambleView = journalView.getPreamble(preambleInfo)
// unnecessary work if dispatcher state isn't passed in SessionToken
// cleanNonPreamble(processing, journalView.chunks, preambleView)
preambleView
}
private fun cleanNonPreamble(processing: ConstraintsProcessing, chunks: Iterable<MatchJournal.Chunk>, preambleView: MatchJournal.View) {
val preambleSet = preambleView.chunks.toSet()
val cleaner = processing.getStateCleaner()
chunks.asSequence().filterNot(preambleSet::contains).forEach {
it.activatedLog().forEach(cleaner::erase)
if (it is MatchJournal.MatchChunk) {
cleaner.erase(it.match as RuleMatchEx)
}
}
}
}
private class Config(val program: Program) : EvaluationSession.Config() {
val parameters = HashMap<ParameterKey<*>, Any>()
@ -363,22 +144,11 @@ internal class EvaluationSessionImpl private constructor (
var token: SessionToken? = null
var principalStore: Collection<Occurrence> = emptyList()
override fun withTrace(computingTracer: EvaluationTrace): EvaluationSession.Config {
this.evaluationTrace = computingTracer
return this
}
override fun withStoreView(storeView: StoreView): EvaluationSession.Config {
return this
}
override fun withStore(store: Collection<ConstraintOccurrence>): EvaluationSession.Config {
this.principalStore = store as Collection<Occurrence>
return this
}
override fun withSessionToken(token: SessionToken?): EvaluationSession.Config {
this.token = token
return this
@ -407,7 +177,7 @@ internal class EvaluationSessionImpl private constructor (
Backend.ourBackend.ourSession.set(session)
try {
val main = parameters[ParameterKey.of("main", Constraint::class.java)] as Constraint
return session.launch(token, principalStore, main)
return session.launch(token, main)
}
finally {
try {

View File

@ -22,6 +22,7 @@ import jetbrains.mps.logic.reactor.program.IncrementalContractViolationException
import jetbrains.mps.logic.reactor.program.IncrementalSpec
import jetbrains.mps.logic.reactor.program.Rule
@Deprecated("obsolete class")
interface IncrSpecHolder {
val ispec: IncrementalSpec
@ -34,18 +35,3 @@ interface IncrSpecHolder {
val Rule.isWeakPrincipal get() = ispec.isWeakPrincipal(this)
}
inline fun checkContract(value: Boolean) {
checkContract(value) { "Contract assertion failed" }
}
inline fun checkContract(value: Boolean, lazyMsg: () -> String) {
if (!value) throw IncrementalContractViolationException(lazyMsg())
}
inline fun IncrSpecHolder.assertContract(lazyValue: () -> Boolean) {
assertContract(lazyValue) { "Contract assertion failed" }
}
inline fun IncrSpecHolder.assertContract(lazyValue: () -> Boolean, noinline lazyMsg: () -> String) {
if (ispec.assertLevel().assertContracts()) checkContract(lazyValue(), lazyMsg)
}

View File

@ -1,489 +0,0 @@
/*
* Copyright 2014-2020 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.logic.reactor.core.internal
import jetbrains.mps.logic.reactor.core.*
import jetbrains.mps.logic.reactor.evaluation.EvaluationTrace
import jetbrains.mps.logic.reactor.program.IncrementalSpec
import jetbrains.mps.logic.reactor.program.Rule
import java.util.PriorityQueue
/**
* Incremental stages handle different parts of incremental processing.
*
* Each incremental stage is applied at each journal cursor position
* with read-only rights (see [ChunkReader]) while traversing [MatchJournal].
*/
internal interface IncrementalStage: IncrSpecHolder {
// fun receive(data: Iterable<R>): Boolean
// fun onNext(reader: MatchJournal.ChunkReader): Collection<T>
}
/**
* Invalidation stage includes several activities:
* - removing from chunks (i.e. principal matches) that correspond
* to rules removed from program, their dependent chunks, and those
* dependent on additional invalidated [Justified] entities from [receive];
* - reactivating occurrences that led to invalidated matches (through [activationSink]);
* - pruning invalidated occurrences and matches from [Dispatcher.DispatchingFront]'s state.
*/
internal class InvalidationStage(
override val ispec: IncrementalSpec,
private val posTracker: PosTracking,
private val invalidRuleIds: Set<Any>,
private val activationSink: ContinuedActivationSink,
private val stateCleaner: ConstraintsProcessing.ProgramStateCleaner,
private val trace: EvaluationTrace
): IncrementalStage {
private val invalidJustifications = mutableListOf<Justified>()
private val invalidFeedbackKeys: MutableSet<Any> = mutableSetOf<Any>()
private val invalidRuleIdsAll: MutableList<Any> = mutableListOf<Any>()
fun invalidatedFeedback(): FeedbackKeySet = invalidFeedbackKeys.toHashSet()
fun invalidatedRules(): List<Any> = invalidRuleIdsAll.toList()
fun receive(invalid: Iterable<Justified>) { invalidJustifications.addAll(invalid) }
fun receive(invalid: Justified) { invalidJustifications.add(invalid) }
/**
* Invalidates next chunk, if needed.
* Doesn't remove the [Chunk] from the [Journal].
* Returns [true] if chunk is invalidated and must be removed.
*/
fun onNext(reader: ChunkReader): Boolean {
val chunk = reader.next
if (chunk is MatchJournal.MatchChunk && chunk.dependsOnAny(invalidRuleIds)) {
invalidJustifications.add(chunk)
}
// Invalidating dependent chunks
if (chunk.justifiedByAny(invalidJustifications)) {
val validOccs = invalidateChunk(chunk)
activationSink.offerAll(reader.current.toPos(), validOccs)
// Remove chunk from the journal
return true
}
return false
}
private fun invalidateChunk(chunk: MatchJournal.Chunk): Iterable<Occurrence> {
// 'Undo' all activated in this chunk occurrences: clear Dispatcher & LogicalState
chunk.activatedLog().forEach(stateCleaner::erase)
val validOccs: Sequence<Occurrence>
if (chunk is MatchJournal.MatchChunk) {
with (chunk.match) {
trace.invalidate(this)
// Don't accidentaly invalidate new rules.
// New rules themselves remain valid, only their effects must be cleared.
if (posTracker.isOld(chunk)) {
invalidRuleIdsAll.add(rule().uniqueTag())
}
// So invalidate feedback they produced
invalidFeedbackKeys.add(feedbackKey)
stateCleaner.erase(this as RuleMatchEx)
// Valid head occurrences could match more rules
// without this match, so need to reactivate them.
// E.g. occurrences discarded in this match on
// previous run but revived here can match more rules.
validOccs = allHeads().filter { occ ->
!occ.justifiedByAny(invalidJustifications)
}
// By definition of Chunk and principal rule,
// all occurrences from the head are principal.
assert(allHeads().all { it.isPrincipal })
}
} else validOccs = emptySequence()
return validOccs.asIterable()
}
private fun MatchJournal.MatchChunk.dependsOnAny(utags: Iterable<Any>): Boolean =
utags.contains(ruleUniqueTag) || utags.any(::dependsOnRule)
}
/**
* Addition stage includes:
* - searching for potential matches for new [addedRules] (see [addRuleCandidates])
* - receiving additional [MatchCandidates] (e.g. postponed matches)
* - offering both to [ContinuedActivationSink] at right positions in [MatchJournal]
* and in correct order according to rule priorities (see [offerCandidates])
*/
internal class AdditionStage(
override val ispec: IncrementalSpec,
private val posTracker: PosTracking,
private val addedRules: Iterable<Rule>,
private val activationSink: ContinuedActivationSink,
private val ruleOrdering: RuleOrdering,
private val ruleIndex: RuleIndex,
private val trace: EvaluationTrace
): IncrementalStage {
interface MatchCandidate {
val rule: Rule
val occChunk: MatchJournal.OccChunk
}
private data class PotentialMatch(
override val rule: Rule,
override val occChunk: MatchJournal.OccChunk
): MatchCandidate
private val activationCandidates = PriorityQueue<MatchCandidate>{
lhs, rhs -> ruleOrdering.compare(lhs.rule, rhs.rule)
}
private val hasRules = addedRules.iterator().hasNext() // it's constant
fun onNext(reader: ChunkReader) {
val chunk = reader.current
if (hasRules && chunk is MatchJournal.OccChunk) {
addRuleCandidates(chunk)
}
offerCandidates(reader)
}
fun onRewind(reader: ChunkReader) = with(posTracker) {
activationCandidates.removeIf {
isNew(it.occChunk) || isFuture(it.occChunk)
}
}
fun receive(candidates: Iterable<MatchCandidate>) =
activationCandidates.addAll(candidates)
private fun addRuleCandidates(chunk: MatchJournal.OccChunk) {
// filters out rules using occurrence's arguments
val allRuleCandidates = ruleIndex.forOccurrence(chunk.occ).map { it.uniqueTag() }.toHashSet()
for (rule in addedRules) {
if (allRuleCandidates.contains(rule.uniqueTag()) && rule.canMatch(chunk.occ.constraint)) {
// 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(PotentialMatch(rule, chunk))
// todo: also use the rule to help Dispatcher in future?
// i.e. try matching only on the candidate rule
}
}
}
private fun offerCandidates(reader: ChunkReader) {
val aIt = activationCandidates.iterator()
while (aIt.hasNext()) {
val candidate = aIt.next()
val candidateRule = candidate.rule
val occChunk = candidate.occChunk
val pos =
if (ruleOrdering.canBeInserted(candidateRule, occChunk, reader.next) || reader.atEnd())
reader.current.toPos()
else
continue
activationSink.offer(pos, occChunk)
trace.potentialMatch(occChunk.occ, candidateRule)
// Drop the candidate if appropriate activation place is found.
aIt.remove()
}
}
}
// todo; extract more restricted interface (w/o rewind) for use in stages
internal class PosTracking(
val index: MatchJournal.Index,
private val journal: MatchJournal,
initPos: MatchJournal.Pos = journal.initialChunk().toPos()
) {
/**
* Serves as a reference point for determining [isFuture] and [isPast].
* Contract: [MatchJournal.Index.isKnown] is always `true` for [lastVisited].
* Updated on each [onNext].
*/
private var lastVisited: MatchJournal.Pos = initPos
/**
* Contract: [MatchJournal.Index.isKnown] is always `true` for [front].
* Updated only on [rewind], not on each [onNext].
*/
private var front: MatchJournal.Pos = initPos
private fun updateReferencePos(current: MatchJournal.Chunk) {
if (index.isKnown(current)) {
lastVisited = current.toPos()
}
}
fun rewind(pos: MatchJournal.Pos) = with(index) {
assert(isKnown(pos.chunk))
assert(pos before lastVisited)
journal.resetCursor(pos)
if (lastVisited after front) { // rewind can happen inside another rewind and appear inferior
front = lastVisited
}
lastVisited = pos
}
/**
* Tracks [MatchJournal] position as a reference point for [isFuture].
*/
fun onNext(reader: ChunkReader): Unit =
updateReferencePos(reader.current)
fun isOld(chunk: MatchJournal.Chunk): Boolean =
index.isKnown(chunk)
fun isOld(occ: Occurrence): Boolean =
index.isKnown(occ)
fun isNew(chunk: MatchJournal.Chunk): Boolean =
!index.isKnown(chunk)
fun isNew(occ: Occurrence): Boolean =
!index.isKnown(occ)
fun isFront(): Boolean =
with(index) { lastVisited afterOrEq front }
fun isFuture(pos: MatchJournal.Pos): Boolean =
with(index) { pos after lastVisited }
fun isFuture(chunk: MatchJournal.Chunk) = isFuture(chunk.toPos())
fun isPast(pos: MatchJournal.Pos): Boolean =
with(index) { pos before lastVisited }
fun isPast(chunk: MatchJournal.Chunk) = isPast(chunk.toPos())
}
internal class PostponeMatchesStage(
override val ispec: IncrementalSpec,
private val posTracker: PosTracking,
private val ruleOrdering: RuleOrdering
): IncrementalStage {
private data class PostponedMatch(
val match: RuleMatchEx,
override val occChunk: MatchJournal.OccChunk
): AdditionStage.MatchCandidate {
override val rule: Rule = match.rule()
init { assert(match.allHeads().contains(occChunk.occ)) }
}
private val postponedMatches: MutableMap<Int, MutableCollection<RuleMatchEx>> = hashMapOf()
fun onNext(reader: ChunkReader): Collection<AdditionStage.MatchCandidate> {
return (reader.current as? MatchJournal.OccChunk)?.let { occChunk ->
postponedMatches.remove(occChunk.identity)?.map { PostponedMatch(it, occChunk) }
} ?: emptyList()
}
/**
* Postpones future matches from [matches] on [active]
* and adds postponed matches to the resulting list (if there're any).
*
* @return matches ready for processing, sorted according to rule priorities.
*/
fun process(active: Occurrence, matches: List<RuleMatchEx>): List<RuleMatchEx> =
postponeFutureMatches(matches).withPostponedMatches(active)
/**
* Determines, filters out, and postpones future matches.
* Returns only current matches.
*
* Future match is a match, which has heads that are not yet
* activated according to current [MatchJournal] position.
*/
fun postponeFutureMatches(matches: List<RuleMatchEx>): List<RuleMatchEx> {
val currentMatches = mutableListOf<RuleMatchEx>()
for (m in matches) {
// Returns null for matches with occurrences only from this session
// because journalIndex indexes only previous session.
val occChunk = posTracker.index.activationPos(m)
if (occChunk != null && posTracker.isFuture(occChunk.toPos())) {
postponedMatches.getOrPut(occChunk.identity, ::mutableListOf).add(m)
} else {
currentMatches.add(m)
}
}
return currentMatches
}
/**
* Adds previously postponed matches on [active] [Occurrence], if any, or returns original list.
*
* @return matches sorted according to rule priorities or original list.
*/
private fun List<RuleMatchEx>.withPostponedMatches(active: Occurrence): List<RuleMatchEx> =
postponedMatches.remove(active.identity)?.let { postponed ->
(this + postponed).sortedWith(ruleOrdering.matchComparator)
} ?: this
private val MatchJournal.OccChunk.identity get() = this.occ.identity
}
internal class ContinueOccurrencesStage(
override val ispec: IncrementalSpec,
private val journalIndex: MatchJournal.Index
): IncrementalStage, ContinuedActivationSink {
/**
* Specifies position in [MatchJournal] for continuing program evaluation.
* Position of [reactivated] in journal must precede execution position [continueFrom].
* (see contract in [assertValid]).
*/
private data class ExecPos(val continueFrom: MatchJournal.Pos, val reactivated: MatchJournal.OccChunk) {
val reactivatedOcc: Occurrence get() = reactivated.occ
}
private infix fun ChunkReader.at(execPos: ExecPos) =
this at execPos.continueFrom.chunk
private fun ExecPos.assertValid() {
assert(journalIndex.isKnown(reactivated)) {
"only occurrences from previous session can be incrementally continued"
}
assert({ // lazily
if (!isNew(continueFrom.chunk)) { // can't assert it for new chunks (i.e. those from this session)
val isAncestor = continueFrom.chunk.justifiedBy(reactivated)
val isPredecessor = journalIndex.compare(continueFrom, reactivated.toPos()) >= 0
isAncestor || isPredecessor
} else true
}())
}
private val queue: MutableList<ExecPos> = ArrayList<ExecPos>(1 + journalIndex.size / 8) // rough estimate
private val seen: MutableSet<ExecPos> = HashSet<ExecPos>()
fun onNext(reader: ChunkReader) = mutableListOf<Occurrence>().apply {
while (queue.isNotEmpty() && reader at queue.top()) {
add(queue.pop().reactivatedOcc)
}
}
fun onRewind(reader: ChunkReader): Unit = with(journalIndex) {
val rewindPos = reader.next.toPos()
// clear queue
queue.removeIf {
it.reactivated.toPos() afterOrEq rewindPos
}
// clear seen
seen.removeIf {
isNew(it.continueFrom.chunk) || it.continueFrom afterOrEq rewindPos
}
}
override fun offerAll(continueFromPos: MatchJournal.Pos, occs: Iterable<Occurrence>) =
occs.mapNotNull(journalIndex::activatingChunkOf)
.sortedWith(journalIndex.chunkComparator)
.forEach { offer(continueFromPos, it) }
override fun offer(continueFromPos: MatchJournal.Pos, ancestor: MatchJournal.OccChunk): Boolean =
ExecPos(continueFromPos, ancestor).let {
when {
seen.add(it) -> {
it.assertValid()
queue.push(it)
}
else -> false
}
}
private fun isNew(chunk: MatchJournal.Chunk) = !journalIndex.isKnown(chunk)
private fun <E> MutableList<E>.top(): E = this.last()
private fun <E> MutableList<E>.pop(): E = this.removeAt(this.size - 1)
private fun <E> MutableList<E>.push(element: E): Boolean = this.add(element)
}
internal class RewindStage(
override val ispec: IncrementalSpec,
private val posTracker: PosTracking,
private val principalObserver: PrincipalObserverDispatcher
): IncrementalStage {
private val toRewind: PriorityQueue<MatchJournal.MatchChunk> = PriorityQueue(posTracker.index.chunkComparator)
private val seen = hashSetOf<MatchJournal.Chunk>()
// precondition: all received occurrences are principal and valid (i.e. not invalidated)
fun receive(occs: Sequence<Occurrence>): Boolean =
occs.filter(::takeVolatile)
.mapNotNull(posTracker.index::activatingChunkOf) // get chunk corresponding to occurrence activation
.mapNotNull(posTracker.index::matchChunkOf) // get chunk that activated it
.filter(seen::add) // filter already seen
.toList().let(toRewind::addAll)
/**
* Returns collection of past chunks for rewind
* in sorted order from earliest to latest.
*/
fun onNext(reader: ChunkReader): Collection<MatchJournal.MatchChunk> =
if (needRewind()) {
toRewind.toList().also { toRewind.clear() }
} else emptyList()
fun needRewind(): Boolean = toRewind.peek()?.let {
posTracker.isPast(it)
} ?: false
private fun takeVolatile(occ: Occurrence) =
// NB: only occurrences from previous session are considered
posTracker.isOld(occ) &&
with(principalObserver) { removeTriggered(occ) }
}

View File

@ -182,27 +182,6 @@ interface MatchJournal : EvidenceSource {
override fun getStoreView(): StoreView = StoreViewImpl(
chunks.flatMap { it.entries() }.allOccurrences().asSequence()
)
override fun getPreamble(info: PreambleInfo): View {
val cornerChunk = chunks.first() // corner chunk at beginning
val initialChunk = chunks[1]
val preambleChunks = arrayListOf<Chunk>(cornerChunk, initialChunk)
for (chunk in chunks.asSequence().drop(preambleChunks.size)) {
// chunks which depend only on preamble are added to preamble
if (chunk.justifiedOnlyBy(preambleChunks)) {
if (chunk is MatchChunk && info.inPreamble(chunk.match.rule())
|| chunk is OccChunk)
{
preambleChunks.add(chunk)
}
}
}
val evidenceSeed = preambleChunks.last().evidence;
preambleChunks.add(chunks.last()) // corner chunk at end
assert(evidenceSeed == preambleChunks.maxBy { it.evidence }?.evidence)
return View(preambleChunks, evidenceSeed + 1)
}
}
/**

View File

@ -51,130 +51,3 @@ interface PrincipalObserverDispatcher {
override fun removeTriggered(occ: Occurrence): Boolean = false
}
}
/**
* Ensures important contract of incremental algorithm.
*
* Contract states that arguments of principal [Occurrence]s
* must be immutable, that is, logicals can't be unified
* with either ground or free other logicals.
*/
internal class LogicalBindObserverDispatcher : PrincipalObserverDispatcher {
private var receiver: ObserverTriggeredHandler = { false }
private val observing: HashMap<Int, LogicalBindObserver> = hashMapOf()
private val triggered: HashSet<Int> = hashSetOf()
override fun toString(): String =
"${javaClass.name}(observed: ${observing.size}, triggered: ${triggered.size})"
override fun setTriggerReceiver(receiver: ObserverTriggeredHandler) { this.receiver = receiver }
override fun clearTriggerReceiver() { receiver = { false } }
override fun onActivated(occ: Occurrence, observable: LogicalStateObservable) {
if (observing.containsKey(occ.identity)) return
LogicalBindObserver(this, occ, observable).let {
if (it.observes) {
observing[occ.identity] = it
}
}
}
override fun onInvalidated(occ: Occurrence, observable: LogicalStateObservable) {
// NB: works between incremental sessions only if this instance is preserved between them
observing.remove(occ.identity)?.removeObservers(observable)
}
override fun isEmpty(): Boolean = triggered.isEmpty() //&& observing.isEmpty()
override fun isObserving(occ: Occurrence): Boolean = observing.containsKey(occ.identity)
override fun isTriggered(occ: Occurrence): Boolean = triggered.contains(occ.identity)
override fun removeTriggered(occ: Occurrence): Boolean = triggered.remove(occ.identity)
fun onTrigger(source: Occurrence, logical: Logical<*>) {
observing.remove(source.identity) // logical observers are already removed
if (!receiver(source)) {
// if receiver didn't accept occurrence,
// then remember it for later requests
triggered.add(source.identity)
}
}
private inner class LogicalBindObserver(
private val logicalDispatcher: LogicalBindObserverDispatcher,
val source: Occurrence,
observable: LogicalStateObservable
): ForwardingLogicalObserver {
private val observed: MutableSet<Logical<*>> = hashSetOf()
init {
for (unboundLogical in source.usedUnboundLogicals()) {
observe(unboundLogical, observable)
}
}
private fun observe(arg: Logical<*>, observable: LogicalStateObservable) {
if (!observed.contains(arg)) {
observable.addForwardingObserver(arg, this)
observed.add(arg)
}
}
fun removeObservers(observable: LogicalStateObservable) {
for (logical in observed) {
observable.removeForwardingObserver(logical, this)
}
observed.clear()
}
val observes: Boolean = observed.isNotEmpty()
override fun valueUpdated(logical: Logical<*>, controller: Controller) = onUpdated(logical, controller)
override fun parentUpdated(logical: Logical<*>, controller: Controller) = onUpdated(logical, controller)
private fun onUpdated(logical: Logical<*>, controller: Controller) {
removeObservers(controller.logicalStateObservable())
logicalDispatcher.onTrigger(source, logical)
}
}
private fun Occurrence.removeContractObservers(observable: LogicalStateObservable) {
for (observedLogical in this.usedUnboundLogicals()) {
observable.removeForwardingObserversWhere(observedLogical) { observer ->
observer is LogicalBindObserver && observer.source.identity == this.identity
}
}
}
}
internal fun Occurrence.usedUnboundLogicals(): Set<Logical<*>> {
val unique = hashSetOf<Logical<*>>()
for (arg in arguments) {
val argLogicals = when (arg) {
is Logical<*> ->
if (!arg.isBound) listOf(arg)
else when(val value = arg.findRoot().value()) {
is Term -> value.unboundLogicals()
else -> emptyList()
}
is Term -> arg.unboundLogicals()
else -> emptyList()
}
unique.addAll(argLogicals)
}
return unique
}

View File

@ -148,269 +148,3 @@ internal open class EmptyProcessing: ProcessingStrategy {
override fun processInvalidated(occ: Occurrence, observable: LogicalStateObservable) {}
}
/**
* Strategy that observes logical vars used in principal constraints
* with a help of [PrincipalObserverDispatcher].
*
* Required for [IncrementalProcessing] for an initial session run.
*/
internal open class GroundProcessing(
override val ispec: IncrementalSpec,
private val principalObservers: PrincipalObserverDispatcher = PrincipalObserverDispatcher.EMPTY
): EmptyProcessing(), IncrSpecHolder {
override fun processActivated(active: Occurrence, observable: LogicalStateObservable) {
if (active.isPrincipal) {
principalObservers.onActivated(active, observable)
}
}
override fun processInvalidated(occ: Occurrence, observable: LogicalStateObservable) {
if (occ.isPrincipal) {
principalObservers.onInvalidated(occ, observable)
}
}
}
/**
* Facade implementation for incremental processing algorithm.
*
* It includes 5 stages that operate on [MatchJournalImpl.Cursor].
* Stages are:
* - [RewindStage]
* - [InvalidationStage]
* - [AdditionStage]
* - [PostponeMatchesStage]
* - [ContinueOccurrencesStage]
*
* Main loop [run] defines relations between stages.
* After invalidation and addition control flow is passed to
* general processing in [Controller] & [ConstraintsProcessing].
*
* Methods [processMatch] & [processOccurrenceMatches] serve as
* a bridge back from [ConstraintsProcessing] to specific stages.
*/
internal class IncrementalProcessing(
ispec: IncrementalSpec,
val journal: MatchJournal,
newRules: Iterable<Rule>,
droppedRules: Iterable<Any>,
stateCleaner: ConstraintsProcessing.ProgramStateCleaner,
ruleIndex: RuleIndex,
principalObservers: PrincipalObserverDispatcher,
trace: EvaluationTrace
): GroundProcessing(ispec, principalObservers) {
private val journalIndex = journal.index()
private val ruleOrdering = RuleOrdering(ruleIndex)
private val posTracker = PosTracking(journalIndex, journal)
private val continuator = ContinueOccurrencesStage(ispec, journalIndex)
private val invalidator = InvalidationStage(ispec, posTracker, droppedRules.toSet(), continuator, stateCleaner, trace)
private val adder = AdditionStage(ispec, posTracker, newRules, continuator, ruleOrdering, ruleIndex, trace)
private val postponer = PostponeMatchesStage(ispec, posTracker, ruleOrdering)
private val rewinder = RewindStage(ispec, posTracker, principalObservers)
init {
principalObservers.setTriggerReceiver(this::receiveBindTriggered)
}
override fun invalidatedFeedback(): FeedbackKeySet =
invalidator.invalidatedFeedback()
override fun invalidatedRules(): List<Any> =
invalidator.invalidatedRules()
override fun offerMatch(match: RuleMatchEx) =
!match.allHeads().filter { it.isPrincipal }.toList().let {
if (it.isNotEmpty()) {
rewinder.receiveStrict(it.asSequence(), match)
} else false
}
override fun processMatch(match: RuleMatchEx) =
continueReplacedHeadsImpl(match)
override fun processOccurrenceMatches(active: Occurrence, matches: List<RuleMatchEx>) =
postponeFutureMatchesImpl(active, matches)
override fun run(processing: ConstraintsProcessing, controller: Controller, main: Constraint): FeedbackStatus {
var status: FeedbackStatus = FeedbackStatus.NORMAL()
val cursor = journal.cursor
while (true) {
posTracker.onNext(cursor)
rewind(cursor)
invalidate(cursor)
val postponedMatches = postponer.onNext(cursor)
adder.receive(postponedMatches)
// Adder step must work on the incremental front.
// If rewind happened, then must skip this.
if (posTracker.isFront()) { adder.onNext(cursor) }
// fixme: pass inStatus?
status = continuator.runContinued(processing, controller, cursor)
// continuator may request invalidating more chunks
val haveChanges = invalidate(cursor)
if (!status.operational) break
if (!rewinder.needRewind() && cursor.atEnd()) break
// These changes (if present) must operate on
// current cursor position, so don't advance it.
if (!rewinder.needRewind() && !haveChanges) cursor.next()
}
return status
}
private fun receiveBindTriggered(occ: Occurrence): Boolean =
if (journalIndex.isKnown(occ)) {
invalidator.receive(occ)
true
} else false
private fun continueReplacedHeadsImpl(match: RuleMatchEx) {
if (requiresIncrementalProcessing(match)) {
val invalidJustifications = match.matchHeadReplaced().filter { it.isPrincipal }
invalidator.receive(invalidJustifications)
}
}
private fun postponeFutureMatchesImpl(active: Occurrence, matches: List<RuleMatchEx>) =
if (requiresIncrementalProcessing(active)) {
postponer.postponeFutureMatches(matches)
} else matches
private fun RewindStage.receiveStrict(occs: Sequence<Occurrence>, match: RuleMatchEx) =
rewinder.receive(occs).also {
if (ispec.assertLevel() == IncrementalSpec.AssertLevel.AssertContracts) {
throw IncrementalContractViolationException(
"Incremental processing can't guarantee correctness for match ${match.rule().uniqueTag()}"
)
}
}
private fun rewind(cursor: ChunkReader) {
val toRewind = rewinder.onNext(cursor)
toRewind.firstOrNull()?.let { earliest ->
// This is the only place where journal can be reset to past
// fixme: modifying not through cursor; not the cleanest way
posTracker.rewind(earliest.toPos())
// Will invalidate these chunks and reevaluate them
invalidator.receive(toRewind)
// Some stages on rewind need to clear internal state
adder.onRewind(cursor)
continuator.onRewind(cursor)
}
}
private fun invalidate(cursor: RemovingJournalIterator): Boolean {
var haveChanges = false
do {
// handle all new chunks that were invalidated by rewind
if (posTracker.isNew(cursor.next)) {
invalidator.receive(cursor.next)
}
if (invalidator.onNext(cursor)) {
cursor.removeNext()
haveChanges = true
} else break
} while (true)
return haveChanges
}
private fun requiresIncrementalProcessing(match: RuleMatchEx) = !journal.isFront() && ispec.ability().allowed() && match.isPrincipal
private fun requiresIncrementalProcessing(occ: Occurrence) = !journal.isFront() && ispec.ability().allowed() && occ.isPrincipal
}
/**
* Strategy that works with occurrence store.
* Doesn't directly work with [MatchJournal] except for default logging.
* Strategy simply activates [Occurrence]s passed as input store
*
* Caller is responsible for handling new computed principal [Occurrences]:
* it can get them from [MatchJournal] and output for putting into cache.
*
* It requires that program in question adheres to incremental contracts.
* Importantly, one ensured by [LogicalBindObserverDispatcher].
*/
internal class CachedOccurrencesProcessing(
ispec: IncrementalSpec,
private val occurrences: OccurrenceStore
): GroundProcessing(ispec) {
private var inPreamble = true
private val postponedMatches: MutableList<Pair<Occurrence, List<RuleMatchEx>>> = mutableListOf()
override fun processOccurrenceMatches(active: Occurrence, matches: List<RuleMatchEx>) =
if (inPreamble && matches.isNotEmpty()) {
postponedMatches.add(active to matches)
emptyList<RuleMatchEx>()
} else matches
override fun run(processing: ConstraintsProcessing, controller: Controller, main: Constraint): FeedbackStatus {
var status: FeedbackStatus = FeedbackStatus.NORMAL()
// NB: assume evaluation order doesn't matter for these occurrences
// first activate cached occurrences,
// but don't process their matches right away
this.inPreamble = true
for (occ in occurrences) {
if (occ.constraint().symbol() == main.symbol())
continue
status = processing.evaluate(controller, occ, status)
if (!status.operational)
return status
}
this.inPreamble = false
// then continue matches caused by cached occurrences
status = continueMatches(processing, controller, status)
// then proceed with normal execution
if (status.operational)
status = controller.activate(main)
return status
}
private fun continueMatches(processing: ConstraintsProcessing, controller: Controller, inStatus: FeedbackStatus): FeedbackStatus {
val parentChunk = processing.initialChunk() // fixme: get activation chunk of active occ?
val status = postponedMatches.fold(inStatus) { status, (active, matches) ->
processing.processMatches(controller, active, matches, parentChunk, status)
}
return status
}
}
private fun ContinueOccurrencesStage.runContinued(processing: ConstraintsProcessing, controller: Controller, chunkReader: ChunkReader): FeedbackStatus {
var status: FeedbackStatus = FeedbackStatus.NORMAL()
val parentChunk = processing.parentChunk()
for (continuedOcc in this.onNext(chunkReader)) {
if (continuedOcc.stored) {
status = processing.activateContinue(controller, continuedOcc, parentChunk)
if (!status.operational) break
}
}
return status
}

View File

@ -26,7 +26,7 @@ import kotlin.NoSuchElementException
import kotlin.collections.ArrayList
/**
* An alternative implementation of RuleMatcherImpl. Has similar asymptotic characteristics as the "naïve" implementation.
* An alternative implementation of RuleMatcher. Has similar asymptotic characteristics as the "naïve" implementation.
*
* Loosely based on "Rete network" algorithm.
*
@ -77,6 +77,7 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
var lastGeneration = Generation(Layer(InitialNode()))
/* FIXME MEMLEAK */
val consumedSignatures = IndexedSignatureSet()
abstract inner class ReteNode
@ -234,8 +235,10 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
*/
inner class Layer() {
/* FIXME MEMLEAK */
private val introTrail: Trail = trailOf()
/* FIXME MEMLEAK */
private val introNodes = HashMap<Int, MutableList<ReteNode>>()
private val nodeList = UnionFindLinkedList<ReteNode>()
@ -255,12 +258,7 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
fun containsOccurrence(occ: Occurrence): Boolean {
return introTrail.contains(occ.identity)
}
fun forgetContains(occ: Occurrence) {
// FIXME this breaks the internal invariant
introTrail.remove(occ.identity)
}
fun iterate(): MutableIterator<ReteNode> = nodeList.iterator()
fun nextNode(it: MutableIterator<ReteNode>): ReteNode? {
@ -449,23 +447,7 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
consumedSignatures.removeAllWith(occurrence.identity)
return nextGeneration().reset()
}
/*
* Allows to avoid triggering reactivation logic in the next call of "introduce" for this occurrence.
*/
fun forgetIntroduced(occurrence: Occurrence): Generation {
for (layer in layers) {
layer.forgetContains(occurrence)
}
if (lastIntroduced === occurrence) lastIntroduced = null
return nextGeneration()
}
fun erase(occurrence: Occurrence): Generation {
if (lastIntroduced === occurrence) lastIntroduced = null
return drop(occurrence).clearInvalidNodes()
}
private fun clearInvalidNodes(): Generation {
while (nodesIt.hasNext()) {
val n = nodesIt.next()
@ -528,21 +510,6 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
return this
}
override fun forgetExpanded(occ: Occurrence): ReteNetwork {
this.lastGeneration.forgetIntroduced(occ)
return this
}
override fun forgetConsumed(occ: Occurrence): ReteNetwork {
consumedSignatures.removeAllWith(occ.identity)
return this
}
override fun forget(occ: Occurrence): RuleMatchingProbe {
this.lastGeneration = lastGeneration.erase(occ)
return this
}
override fun hasOccurrences(): Boolean {
return this.lastGeneration.hasOccurrences()
}
@ -556,11 +523,6 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
return this
}
override fun forget(ruleMatch: RuleMatchEx): RuleMatchingProbe {
consumedSignatures.remove(ruleMatch.signatureArray().toSignature())
return this
}
}
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2014-2019 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.logic.reactor.core.internal
import jetbrains.mps.logic.reactor.core.Occurrence
import jetbrains.mps.logic.reactor.core.canMatch
import jetbrains.mps.logic.reactor.evaluation.RuleMatch
import jetbrains.mps.logic.reactor.program.Rule
internal class RuleOrdering(order: Iterable<Rule>): ComparatorExt<Rule> {
private val ruleOrder: Map<Any, Int> = HashMap<Any, Int>().apply {
put(MatchJournalImpl.InitRuleMatch.rule().uniqueTag(), -1) // less than anything
order.forEachIndexed { index, rule -> put(rule.uniqueTag(), index) }
}
val ruleTags: Set<Any> = order.map { it.uniqueTag() }.toHashSet() // NB: without initial rule
override fun compare(lhs: Rule, rhs: Rule): Int = compareBy<Rule>(this::orderOfThrow).compare(lhs, rhs)
val matchComparator: Comparator<RuleMatch> get() = compareBy<RuleMatch>{ orderOfThrow(it.rule()) }
private fun orderOf(rule: Rule): Int? = ruleOrder[rule.uniqueTag()]
private fun orderOfThrow(rule: Rule): Int = when (val res = orderOf(rule)) {
null -> throw IllegalStateException("Compared rule (${rule.uniqueTag()}) must be present in rule index!")
else -> res
}
}
/**
* Checks whether [candidateRule] can be inserted in journal as a child
* of [parentChunk] before one of its child chunks, [beforeChunk].
* It is assumed that [candidateRule] can be matched by [Occurrence] from [parentChunk].
*/
internal fun RuleOrdering.canBeInserted(candidateRule: Rule, parentChunk: MatchJournal.OccChunk, beforeChunk: MatchJournal.Chunk): Boolean {
// Place to try activating candidate rule is:
// either according to the ordering between rules
// or as the last one, after all existing activations
assert(candidateRule.canMatch(parentChunk.occ.constraint))
val placeToInsertFound = beforeChunk is MatchJournal.MatchChunk
&& candidateRule before beforeChunk.match.rule()
val isDescendant = beforeChunk.justifiedBy(parentChunk)
val isSibling = beforeChunk.evidence == parentChunk.evidence && beforeChunk != parentChunk
val childChunksEnded = !isDescendant || isSibling
return (childChunksEnded || placeToInsertFound)
}

View File

@ -25,7 +25,6 @@ import jetbrains.mps.logic.reactor.program.Rule
data class SessionTokenImpl(
private val journalView: MatchJournal.View,
private var store: Collection<Occurrence>,
private val rules: Iterable<Rule>,
private val frontState: DispatchingFrontState,
val ruleIndex: RuleIndex,
@ -35,8 +34,4 @@ data class SessionTokenImpl(
{
override fun getJournalView(): MatchJournalView = journalView
override fun getRules(): Iterable<Rule> = rules
override fun getPrincipalStore(): Collection<Occurrence> = store
fun getFrontState(): DispatchingFrontState = frontState
fun setStore(newStore: Collection<Occurrence>) { this.store = newStore }
}

View File

@ -78,14 +78,6 @@ public abstract class EvaluationSession {
public abstract Config withTrace(EvaluationTrace computingTracer);
/**
* @deprecated passing store view is deprecated and doesn't have an effect
*/
@Deprecated
public abstract Config withStoreView(StoreView storeView);
public Config withStore(Collection<ConstraintOccurrence> store) { return this; }
public Config withSessionToken(SessionToken token) { return this; }
public Config withIncrSpec(IncrementalSpec ispec) { return this; }

View File

@ -16,9 +16,6 @@
package jetbrains.mps.logic.reactor.evaluation;
import jetbrains.mps.logic.reactor.program.PreambleInfo;
public interface MatchJournalView {
StoreView getStoreView();
MatchJournalView getPreamble(PreambleInfo info);
}

View File

@ -25,6 +25,4 @@ public interface SessionToken {
MatchJournalView getJournalView();
@NotNull()
Iterable<Rule> getRules();
@NotNull
Collection<ConstraintOccurrence> getPrincipalStore();
}

View File

@ -1,36 +0,0 @@
/*
* Copyright 2014-2020 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.logic.reactor.program;
/**
* Provides required but incomplete information to determine preamble.
* Aimed at filtering rules with origins which do belong to preamble.
*
* Together with justifications from journal provides complete information.
* See implementation for details.
*/
public interface PreambleInfo {
boolean inPreamble(Rule rule);
static final PreambleInfo EMPTY = new PreambleInfo() {
@Override
public boolean inPreamble(Rule rule) {
return false;
}
};
}

View File

@ -16,15 +16,8 @@
package jetbrains.mps.logic.reactor.program;
import javaslang.collection.List;
import javaslang.collection.Stream;
import jetbrains.mps.logic.reactor.core.RulesDiff;
import java.util.ArrayList;
import java.util.stream.Collectors;
/**
* A collection of rulesLists that constitute a constraint rules program.
* A collection of rules that constitute a constraint rules program.
*
* @author Fedor Isakov
*/
@ -32,39 +25,6 @@ public abstract class Program {
public abstract String name();
@Deprecated
public abstract Iterable<RulesList> rulesLists();
public abstract PreambleInfo preambleInfo();
@Deprecated
public Program withRulesDiff(RulesDiff diff) { return this; };
@Deprecated
public RulesDiff incrementalDiff() { return RulesDiff.emptyDiff(); };
public Iterable<Rule> rules() {
ArrayList<Rule> allRules = new ArrayList<Rule>();
for (RulesList rulesList : rulesLists()) {
for (Rule rule : rulesList.rules()) {
allRules.add(rule);
}
}
return allRules;
};
/**
* Returns rules that have been created since the last evaluation of this program.
*/
public Iterable<Rule> newRules () {
return incrementalDiff().getAdded();
}
/**
* Returns objects that identify rules removed from the previous invocation.
*/
public Iterable<Object> droppedRules() {
return incrementalDiff().getRemoved();
}
abstract public Iterable<Rule> rules();
}

View File

@ -2,7 +2,6 @@
* @author Fedor Isakov
*/
import jetbrains.mps.logic.reactor.core.RulesDiff
import jetbrains.mps.logic.reactor.evaluation.EvaluationFeedback
import jetbrains.mps.logic.reactor.evaluation.InvocationContext
import jetbrains.mps.logic.reactor.evaluation.RuleMatch
@ -103,21 +102,8 @@ class MockRule(
}
class MockProgram(val name: String, val rulesLists: List<RulesList>, val registry: MockConstraintRegistry) : Program() {
private var rulesDiff: RulesDiff = RulesDiff.emptyDiff()
override fun preambleInfo(): PreambleInfo = PreambleInfo.EMPTY
override fun incrementalDiff(): RulesDiff = rulesDiff
override fun withRulesDiff(diff: RulesDiff): MockProgram {
this.rulesDiff = diff
return this
}
override fun name(): String = name
override fun rulesLists(): Iterable<RulesList> = unmodifiableCollection(rulesLists)
override fun rules(): MutableIterable<Rule> = unmodifiableCollection(rulesLists.flatMap { it.rules() })
}

View File

@ -1,13 +1,11 @@
import jetbrains.mps.logic.reactor.program.IncrementalSpec
import jetbrains.mps.logic.reactor.core.Occurrence
import jetbrains.mps.logic.reactor.core.ReactorLifecycle
import jetbrains.mps.logic.reactor.core.RulesDiff
import jetbrains.mps.logic.reactor.core.internal.MatchJournal
import jetbrains.mps.logic.reactor.evaluation.*
import jetbrains.mps.logic.reactor.logical.Logical
import jetbrains.mps.logic.reactor.program.Constraint
import jetbrains.mps.logic.reactor.program.ConstraintSymbol
import jetbrains.mps.logic.reactor.program.IncrementalContractViolationException
import org.junit.*
import org.junit.Assert.*
import program.MockConstraint
@ -33,6 +31,7 @@ import solver.tellEquals
* @author Fedor Isakov
*/
@Ignore
class TestIncrementalProgram {
companion object {
@ -64,9 +63,7 @@ class TestIncrementalProgram {
private fun Builder.relaunch(name: String, incrSpec: IncrementalSpec, sessionToken: SessionToken, resultHandler: (EvaluationResult) -> Unit )
: Pair<Builder, EvaluationResult>
{
val prog = program(name).withRulesDiff(
RulesDiff.findDiff(sessionToken.rules, rules)
)
val prog = program(name)
val result = EvaluationSession.newSession(prog)
.withParameter(EvaluationSession.ParameterKey.of("main", Constraint::class.java), MockConstraint(ConstraintSymbol("main", 0)))
.withIncrSpec(incrSpec)