From 75c6dff551e17f7a91ca20043831d7941f021b8f Mon Sep 17 00:00:00 2001 From: Fedor Isakov Date: Sat, 4 Aug 2018 14:24:37 +0200 Subject: [PATCH] Optimizing new matching algorithm. Keep an index of occurrence slots for each rule. --- .../mps/logic/reactor/core/Dispatcher.kt | 6 +- .../mps/logic/reactor/core/RuleIndex.kt | 235 +++++++++--------- .../mps/logic/reactor/core/RuleMatcher.kt | 24 +- .../mps/logic/reactor/util/BitSet.kt | 3 + .../mps/logic/reactor/util/DexxCollections.kt | 16 +- 5 files changed, 153 insertions(+), 131 deletions(-) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt index 049bdc6a..18ac0ff3 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt @@ -62,10 +62,8 @@ class Dispatcher (val ruleIndex: RuleIndex) { fun activated(active: ConstraintOccurrence): DispatchFringe { return DispatchFringe(this, - ruleIndex.forOccurrence(active).mapNotNull { rule -> - rule2fringe[rule] - }.map { fringe -> - fringe.expand(active) + ruleIndex.forOccurrenceWithMask(active).mapNotNull { (rule, mask) -> + rule2fringe[rule]?.expand(active, mask) }) } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt index 97195e76..5ade1367 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt @@ -19,12 +19,12 @@ package jetbrains.mps.logic.reactor.core import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence import jetbrains.mps.logic.reactor.logical.Logical import jetbrains.mps.logic.reactor.logical.MetaLogical -import jetbrains.mps.logic.reactor.program.Constraint -import jetbrains.mps.logic.reactor.program.ConstraintSymbol -import jetbrains.mps.logic.reactor.program.Handler -import jetbrains.mps.logic.reactor.program.Rule +import jetbrains.mps.logic.reactor.program.* +import jetbrains.mps.logic.reactor.util.allSetBits import jetbrains.mps.unification.Term import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap /** * @author Fedor Isakov @@ -32,159 +32,162 @@ import java.util.* class RuleIndex(handlers: Iterable) : Iterable { - private val primarySymbol2valueIndex = HashMap() - - private val auxSymbol2valueIndex = HashMap() + private val symbol2index = HashMap() private val tag2rule = LinkedHashMap() + val rules = ArrayList() + + private val slotIndices = ArrayList() + init { buildIndex(handlers) } fun byTag(tag: String): Rule? = tag2rule[tag] - fun forOccurrence(occ: ConstraintOccurrence): Iterable = - primarySymbol2valueIndex.get(occ.constraint().symbol())?.select(occ) ?: - (auxSymbol2valueIndex.get(occ.constraint().symbol())?.select(occ) ?: emptyList()) + fun forOccurrence(occ: ConstraintOccurrence): Iterable { + val ruleIndices = symbol2index[occ.constraint().symbol()]?.select(occ) ?: return emptyList() + return ruleIndices.allSetBits().map { idx -> rules[idx] } + } + + /** + * Returns a pair of rule and bit mask with 1's marking matching slots. + */ + fun forOccurrenceWithMask(occ: ConstraintOccurrence): Iterable> { + val ruleIndices = symbol2index[occ.constraint().symbol()]?.select(occ) ?: return emptyList() + return ruleIndices.allSetBits().mapNotNull { idx -> slotIndices[idx][occ]?.let { mask -> rules[idx] to mask }} + } override fun iterator(): Iterator = tag2rule.values.iterator() private fun buildIndex(handlers: Iterable) { - // first, init the primary symbols value index - handlers - .flatMap { h -> h.primarySymbols() } - .forEach { symbol -> primarySymbol2valueIndex.getOrPut(symbol) { ValueIndex(symbol) } } - + var pos = 0 for (h in handlers) { - val primSymbols = h.primarySymbols().toSet() - for (r in h.rules()) { - if (tag2rule.containsKey(r.tag())) { - throw IllegalStateException("duplicate rule tag ${r.tag()}") - } - tag2rule[r.tag()] = r - for (c in (r.headKept() + r.headReplaced())) { - val symbol = c.symbol() - if (symbol in primSymbols) { - primarySymbol2valueIndex[symbol]?.update(r, c) + for (rule in h.rules()) { + if (tag2rule.containsKey(rule.tag())) throw IllegalStateException("duplicate rule tag ${rule.tag()}") - } else { - auxSymbol2valueIndex.getOrPut(symbol) { ValueIndex(symbol) }.update(r, c) - } + tag2rule[rule.tag()] = rule + rules.add(rule) + + val head = rule.headKept() + rule.headReplaced() + val symbol2mask = SlotIndex() + for ((bit, cst) in head.withIndex()) { + symbol2index.getOrPut(cst.symbol()) { ArgumentRuleIndex(cst.symbol()) }.update(pos, cst) + symbol2mask.update(cst, bit) } + + slotIndices.add(symbol2mask) + + pos += 1 } } } -} + class SlotIndex() { -/** - * Represents a list of rules with at least one constraint matching the 'symbol'. - * The list is indexed by all values extracted from the constraint arguments. - * The list can be selected based on the argument(s) of a constraint occurrence. - */ -private class ValueIndex(val symbol: ConstraintSymbol) { + val symbol2mask = HashMap() - val rules = ArrayList() - - val anySelectors = ArrayList>() - - val termSelectors = ArrayList>() - - val wildcardSelectors = ArrayList() - - init { - for (idx in 1..symbol.arity()) { - anySelectors.add(HashMap()) - termSelectors.add(TermTrie()) - wildcardSelectors.add(BitSet()) + fun update(cst: Constraint, pos: Int) { + symbol2mask.getOrPut(cst.symbol()) { BitSet() }.set(pos) } + + operator fun get(occ: ConstraintOccurrence): BitSet? = + symbol2mask[occ.constraint().symbol()] // guaranteed to != null + } - fun update(rule: Rule, cst: Constraint) { - var bit = rules.indexOf(rule) - if (bit < 0) { - bit = rules.size - rules.add(rule) + /** + * Represents a list of rules with at least one constraint matching the 'symbol'. + * The list is indexed by all values extracted from the constraint arguments. + * The list can be selected based on the argument(s) of a constraint occurrence. + */ + inner class ArgumentRuleIndex(val symbol: ConstraintSymbol) { + + val anySelectors = ArrayList>() + + val termSelectors = ArrayList>() + + val wildcardSelectors = ArrayList() + + init { + for (idx in 1..symbol.arity()) { + anySelectors.add(HashMap()) + termSelectors.add(TermTrie()) + wildcardSelectors.add(BitSet()) + } } - for ((idx, arg) in cst.arguments().withIndex()) { - val value2indices = anySelectors[idx] - when (arg) { - is MetaLogical<*> -> { - // all values should be accepted by a meta logical - wildcardSelectors[idx].set(bit) - } - is Term -> { - termSelectors.set(idx, termSelectors[idx].put(arg, bit)) - } - is Any -> { - val bitset = value2indices.getOrPut(arg) { BitSet() } - bitset.set(bit) - } - else -> { - /* never happens */ - throw NullPointerException() + fun update(bit: Int, cst: Constraint) { + for ((idx, arg) in cst.arguments().withIndex()) { + val value2indices = anySelectors[idx] + when (arg) { + is MetaLogical<*> -> + // all values should be accepted by a meta logical + wildcardSelectors[idx].set(bit) + is Term -> + termSelectors.set(idx, termSelectors[idx].put(arg, bit)) + is Any -> + value2indices.getOrPut(arg) { BitSet() }.also { it.set(bit) } + else -> + throw NullPointerException() // never happens + } } } - } - fun select(occ: ConstraintOccurrence): Iterable { - if (occ.constraint().symbol() != symbol) throw IllegalArgumentException() + /** + * Returns bit set where 1's indicate the indices of matching rules. + */ + fun select(occ: ConstraintOccurrence): BitSet { + if (occ.constraint().symbol() != symbol) throw IllegalArgumentException() - // initially select all rules - val upToBit = rules.size - val ruleIndices = BitSet(rules.size) - ruleIndices.set(0, upToBit) + // initially select all rules + val upToBit = rules.size + val ruleIndices = BitSet(rules.size) + ruleIndices.set(0, upToBit) - for ((idx, arg) in occ.arguments().withIndex()) { - val value2indices = anySelectors[idx] - val termIndices = termSelectors[idx] - val wildcardIndices = wildcardSelectors[idx] - if (arg is Logical<*> && !arg.isBound) { - // ALL values must be selected for a free logical - continue - } - - val argVal = if (arg is Logical<*>) arg.findRoot().value() else arg - when (argVal) { - is Term -> { - val bits = wildcardIndices.clone() as BitSet - for (i in termIndices.lookupValues(argVal)) { - bits.set(i) - } - ruleIndices.and(bits) + for ((idx, arg) in occ.arguments().withIndex()) { + val value2indices = anySelectors[idx] + val termIndices = termSelectors[idx] + val wildcardIndices = wildcardSelectors[idx] + if (arg is Logical<*> && !arg.isBound) { + // ALL values must be selected for a free logical + continue } - is Any -> { - if (value2indices.containsKey(argVal)) { - // ensure only rules with either matching values or wildcard arguments get selected + + val argVal = if (arg is Logical<*>) arg.findRoot().value() else arg + when (argVal) { + is Term -> { val bits = wildcardIndices.clone() as BitSet - bits.or(value2indices[argVal]) + for (i in termIndices.lookupValues(argVal)) { + bits.set(i) + } ruleIndices.and(bits) + } + is Any -> { + if (value2indices.containsKey(argVal)) { + // ensure only rules with either matching values or wildcard arguments get selected + val bits = wildcardIndices.clone() as BitSet + bits.or(value2indices[argVal]) + ruleIndices.and(bits) - } else { - ruleIndices.and(wildcardIndices) + } else { + ruleIndices.and(wildcardIndices) + } + } + else -> { + /* never happens */ + throw NullPointerException() } } - else -> { - /* never happens */ - throw NullPointerException() + + if (ruleIndices.isEmpty) { + break } } - if (ruleIndices.isEmpty) { - break - } + return ruleIndices } - - val result = ArrayList(ruleIndices.cardinality()) - var next = ruleIndices.nextSetBit(0) - while (next >= 0) { - result.add(rules.get(next)) - next = ruleIndices.nextSetBit(next+1) - } - return result } - -} \ No newline at end of file +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatcher.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatcher.kt index e71b8917..21816b52 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatcher.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatcher.kt @@ -61,7 +61,11 @@ class RuleMatcher(val rule: Rule) { rn is ActiveFringeNode && rn.complete && rn.genId == genId }.map { rn -> (rn as ActiveFringeNode).toMatchRule() } - fun expand(occ: ConstraintOccurrence): MatchFringe { + /** + * Expands the fringe by creating new leaf nodes that match the occurrence. + * Mask specifies possible slots for the occurrence. + */ + fun expand(occ: ConstraintOccurrence, mask: BitSet? = null): MatchFringe { if (seen.contains(occ)) { // constraint occurrence is reactivated // there are nodes having occ in their paths @@ -74,12 +78,15 @@ class RuleMatcher(val rule: Rule) { fn.unrelatedOrCopy(occ, genId + 1) else fn - }.appendAllTo(emptyConsList()) + }.toConsList() return MatchFringe(newNodes, seen, genId + 1) } else { - val newNodes = nodes.asSequence().flatMap { it.expand(occ, genId + 1) } - return MatchFringe(newNodes.appendAllTo(nodes), seen.add(occ), genId + 1) + val newNodes = nodes.asSequence().filter { fn -> + mask == null || fn.matchesVacant(mask) + }.flatMap { fn -> + fn.expand(occ, genId + 1) } + return MatchFringe(newNodes.prependTo(nodes), seen.add(occ), genId + 1) } } @@ -87,6 +94,7 @@ class RuleMatcher(val rule: Rule) { val newNodes = nodes.asSequence().mapNotNull { it.unrelatedOrNull(occ) } return MatchFringe(newNodes.toConsList(), seen.remove(occ), genId + 1) } + } open inner class FringeNode(val subst: Subst, @@ -97,10 +105,8 @@ class RuleMatcher(val rule: Rule) { * If the occurrence is already in the path, return empty sequence. */ fun expand(occ: ConstraintOccurrence, genId: Int): Sequence { - val fringeNode = unrelatedOrNull(occ) - if (fringeNode == null) return emptySequence() - - return fringeNode.vacant.allSetBits().map { idx -> + val unrelated = unrelatedOrNull(occ) ?: return emptySequence() + return unrelated.vacant.allSetBits().map { idx -> idx to match(head[idx] !!, occ, subst) }.asSequence().mapNotNull { (idx, newSubst) -> newSubst?.let { ActiveFringeNode(this, occ, idx, genId, it) } } @@ -111,6 +117,8 @@ class RuleMatcher(val rule: Rule) { */ open fun unrelatedOrNull(occ: ConstraintOccurrence): FringeNode? = this + fun matchesVacant(mask: BitSet) = !mask.copyApply { and(vacant) }.isEmpty + /** * Matches constraint and occurrence. * Recursively processes all arguments, including terms. diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/util/BitSet.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/util/BitSet.kt index c3c351e7..4fe56d37 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/util/BitSet.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/util/BitSet.kt @@ -22,6 +22,9 @@ import java.util.* * @author Fedor Isakov */ +inline fun BitSet.copyApply (f: BitSet.() -> Unit): BitSet = + (clone() as BitSet).apply(f) + fun bitSetOfOnes(size: Int): BitSet = BitSet(size).also { it.set(0, size) } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/util/DexxCollections.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/util/DexxCollections.kt index bdbfd677..2853ac0c 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/util/DexxCollections.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/util/DexxCollections.kt @@ -35,10 +35,20 @@ fun consListOf(vararg args: E): ConsList { return builder.build() } -fun Sequence.appendAllTo(toList: ConsList): ConsList = - fold(toList) { list, e -> list.append(e) } +fun Sequence.prependTo(toList: ConsList): ConsList = + fold(toList) { list, e -> list.prepend(e) } -fun Sequence.toConsList(): ConsList = appendAllTo(emptyConsList()) +fun Sequence.toConsList(): ConsList { + val builder = ConsList.factory().newBuilder() + val var2 = iterator() + + while (var2.hasNext()) { + val e = var2.next() + builder.add(e) + } + + return builder.build() +} fun ConsList.removeAt(idx: Int): ConsList { if (idx < 0) throw IllegalArgumentException("index < 0")