Logical value observers. Testing reactivation of constraint occurrences.

This commit is contained in:
Fedor Isakov 2016-01-07 16:44:22 +01:00
parent b90bdc84c4
commit 057c4d7c69
9 changed files with 273 additions and 47 deletions

View File

@ -3,6 +3,7 @@ package jetbrains.mps.logic.reactor.core
import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation
import jetbrains.mps.logic.reactor.evaluation.SessionSolver
import jetbrains.mps.logic.reactor.logical.Logical
import jetbrains.mps.logic.reactor.logical.LogicalContext
import jetbrains.mps.logic.reactor.logical.LogicalPattern
import jetbrains.mps.logic.reactor.program.*
@ -14,10 +15,14 @@ import java.util.*
class Handler {
private val sessionSolver: SessionSolver
val sessionSolver: SessionSolver
private val rules : MutableList<Rule> = ArrayList<Rule>()
private val stored : MutableList<ConstraintOccurrence> = ArrayList<ConstraintOccurrence>()
private val activeQueue : Queue<ConstraintOccurrence> = LinkedList<ConstraintOccurrence>()
constructor(
sessionSolver: SessionSolver,
programRules: Iterable<Rule>,
@ -33,8 +38,17 @@ class Handler {
fun occurrences(): Set<ConstraintOccurrence> = stored.toSet()
fun process(active: ConstraintOccurrence) {
store(active)
fun queue(occurrence: ConstraintOccurrence) {
activeQueue.add(occurrence)
while (activeQueue.isNotEmpty()) {
process(activeQueue.poll())
}
}
private fun process(active: ConstraintOccurrence) {
if (!active.isStored()) {
store(active)
}
val matcher = object : Matcher(rules) {
override fun findOccurrences(constraint: Constraint, acceptable: (ConstraintOccurrence) -> Boolean):
@ -43,8 +57,8 @@ class Handler {
}
for (match in matcher.lookupMatches(active).filter { pm -> pm.rule.checkGuard(pm.logicalContext()) }) {
if (!active.isAlive()) return
if (match.occurrences().any{ co -> !co.isAlive() }) continue
if (!active.isStored()) return
if (match.occurrences().any{ co -> !co.isStored() }) continue
for ((cst, occ) in match.discarded) {
discard(occ)
@ -62,11 +76,12 @@ class Handler {
private fun discard(occ: ConstraintOccurrence) {
stored.remove(occ)
occ.terminate()
}
private fun activate(item: AndItem, logicalContext: LogicalContext) {
when(item) {
is Constraint -> process(item.occurrence(logicalContext))
is Constraint -> process(item.occurrence(this@Handler, logicalContext))
is Predicate -> tellPredicate(item.invocation(logicalContext))
else -> throw IllegalArgumentException("unknown item ${item}")
}
@ -82,7 +97,7 @@ class Handler {
sessionSolver.tell(invocation.predicate().symbol(), * invocation.arguments().toTypedArray())
}
private fun ConstraintOccurrence.isAlive(): Boolean =
private fun ConstraintOccurrence.isStored(): Boolean =
stored.contains(this)
}
@ -91,8 +106,8 @@ private fun AndItem.argumentValues(context: LogicalContext): List<Any> =
arguments().map { arg -> if (arg is LogicalPattern<*>) context.valueFor(arg) else arg!! }.toList()
private fun Constraint.occurrence(context: LogicalContext): ConstraintOccurrence =
ReactorConstraintOccurrence(this, argumentValues(context))
private fun Constraint.occurrence(handler: Handler, context: LogicalContext): ConstraintOccurrence =
MemConstraintOccurrence(handler, this, argumentValues(context))
private fun Predicate.invocation(logicalContext: LogicalContext): PredicateInvocation {
@ -104,20 +119,52 @@ private fun Predicate.invocation(logicalContext: LogicalContext): PredicateInvoc
}
}
fun ConstraintOccurrence.terminate() {
if (this is MemConstraintOccurrence) {
_terminate()
}
}
private data class ReactorConstraintOccurrence(val constraint: Constraint, val arguments: List<Any>, val id: Int) : ConstraintOccurrence {
private data class MemConstraintOccurrence(val handler: Handler, val constraint: Constraint, val arguments: List<Any>, val id: Int) :
ConstraintOccurrence,
LogicalValueObserver
{
var alive = true
companion object {
val random = Random()
}
constructor(constraint: Constraint, arguments: List<Any>) :
this(constraint, arguments, random.nextInt()) {}
constructor(handler: Handler, constraint: Constraint, arguments: List<Any>) :
this(handler, constraint, arguments, random.nextInt())
{
for (a in arguments) {
if (a is Logical<*>) {
a.addObserver(this)
}
}
}
override fun constraint(): Constraint = constraint
override fun arguments(): Collection<Any> = arguments
override fun valueUpdated(logical: Logical<*>) {
handler.queue(this)
}
fun _terminate() {
for (a in arguments) {
if (a is Logical<*>) {
a.removeObserver(this)
}
}
alive = false
}
override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})"
}

View File

@ -4,11 +4,26 @@ package jetbrains.mps.logic.reactor.core
import jetbrains.mps.logic.reactor.logical.Logical
import jetbrains.mps.logic.reactor.logical.LogicalPattern
import jetbrains.mps.logic.reactor.logical.SolverLogical
import java.util.*
/**
* @author Fedor Isakov
*/
internal interface LogicalValueObserver {
fun valueUpdated(logical: Logical<*>)
}
internal fun Logical<*>.addObserver(observer: LogicalValueObserver) {
(this as MemLogical<*>).observers.add(observer)
}
internal fun Logical<*>.removeObserver(observer: LogicalValueObserver) {
(this as MemLogical<*>).observers.remove(observer)
}
fun <V> LogicalPattern<V>.logical(): Logical<V> = MemLogical<V>(name())
class MemLogical<T> : SolverLogical<T> {
@ -27,6 +42,8 @@ class MemLogical<T> : SolverLogical<T> {
var rank = 0
internal val observers = ArrayList<LogicalValueObserver>()
constructor(value: T) {
this.name = "$${anonIdx++}"
this._value = value
@ -55,15 +72,33 @@ class MemLogical<T> : SolverLogical<T> {
override fun setParent(parent: SolverLogical<T>) {
this._parent = parent as MemLogical<T>
if (find().isBound) {
notifyObservers()
}
}
override fun setValue(newValue: T) { this._value = newValue }
override fun setValue(newValue: T) {
this._value = newValue
notifyObservers()
}
override fun rank(): Int = rank
override fun incRank() { rank++ }
override fun mergeObservers(mergeFrom: SolverLogical<T>) = TODO()
override fun mergeObservers(mergeFrom: SolverLogical<T>) {
val other = mergeFrom as MemLogical<T>
observers.addAll(other.observers)
other.observers.clear()
}
private fun notifyObservers() {
val obs = ArrayList(observers)
this.observers.clear()
for (o in obs) {
o.valueUpdated(this)
}
}
private fun find(): MemLogical<T> {
val tmp = _parent

View File

@ -72,7 +72,7 @@ class MemEvaluationSession : EvaluationSession {
fun launch(main: ConstraintOccurrence) {
this.handler = Handler(sessionSolver(), program.rules())
handler.process(main)
handler.queue(main)
// FIXME: shutdown the session properly
ourBackend.ourSession.set(null)
}

View File

@ -23,22 +23,3 @@ data class MemConstraint(val symbol: ConstraintSymbol, val arguments: List<Any>)
}
data class MemConstraintOccurrence(val constraint: Constraint, val arguments: List<Any>, val id: Int) : ConstraintOccurrence {
companion object {
val random = Random()
}
constructor(constraint: Constraint, arguments: List<Any>) :
this(constraint, arguments, random.nextInt()) {}
constructor(id: String, vararg args: Any) :
this(MemConstraint(ConstraintSymbol.symbol(id, args.size)), listOf(* args), random.nextInt()) {}
override fun constraint(): Constraint = constraint
override fun arguments(): Collection<Any> = arguments
override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})"
}

View File

@ -1,3 +1,5 @@
package solver
import jetbrains.mps.logic.reactor.evaluation.EvaluationSession
import jetbrains.mps.logic.reactor.evaluation.Queryable
import jetbrains.mps.logic.reactor.logical.Logical
@ -74,6 +76,8 @@ class EqualsSolver : Queryable {
}
else {
left.setParent(right)
// the representative has all the observers
right.mergeObservers(left);
}
}
@ -94,6 +98,7 @@ class EqualsSolver : Queryable {
right.findRoot().setValue(left)
}
}
fun tell_value_value(left: Any?, right: Any?) {
check(left == right)
}
@ -108,3 +113,7 @@ class EqualsSolver : Queryable {
infix fun <T : Any> Logical<T>.eq(value: T) {
EvaluationSession.current().sessionSolver().tell(PredicateSymbol("equals", 2), this, value)
}
infix fun <T : Any> Logical<T>.eq(other: Logical<T>) {
EvaluationSession.current().sessionSolver().tell(PredicateSymbol("equals", 2), this, other)
}

View File

@ -33,6 +33,8 @@ inline fun <reified T: Any> logicalPattern(name1: String, name2: String, name3:
fun <T: Any> Logical<T>.get(): T = findRoot().value()
fun <T: Any> Logical<T>.getNullable(): T? = findRoot().value()
fun <T: Any> Logical<T>.set(t: T) {
if (this is SolverLogical<T>)
findRoot().setValue(t)

View File

@ -3,7 +3,8 @@ import jetbrains.mps.logic.reactor.logical.LogicalContext
import jetbrains.mps.logic.reactor.logical.LogicalPattern
import jetbrains.mps.logic.reactor.program.*
import program.MemConstraint
import program.MemConstraintOccurrence
import TestConstraintOccurrence
import solver.EqualsSolver
import java.util.*
/**
@ -73,7 +74,7 @@ fun equals(left: Any, right: Any): ConjBuilder.() -> Unit = {
add(TestEqPredicate(left, right))
}
fun occurrence(id: String, vararg args: Any) : ConstraintOccurrence = MemConstraintOccurrence(id, * args)
fun occurrence(id: String, vararg args: Any) : ConstraintOccurrence = TestConstraintOccurrence(id, * args)
class RB(tag: String, val env: Environment) : RuleBuilder(tag) {
@ -128,3 +129,23 @@ private fun buildConjunction(type: Class<out AndItem>,
}
return conjBuilder
}
data class TestConstraintOccurrence(val constraint: Constraint, val arguments: List<Any>, val id: Int) : ConstraintOccurrence {
companion object {
val random = Random()
}
constructor(constraint: Constraint, arguments: List<Any>) :
this(constraint, arguments, random.nextInt()) {}
constructor(id: String, vararg args: Any) :
this(MemConstraint(ConstraintSymbol.symbol(id, args.size)), listOf(* args), random.nextInt()) {}
override fun constraint(): Constraint = constraint
override fun arguments(): Collection<Any> = arguments
override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})"
}

View File

@ -20,13 +20,17 @@ import org.junit.Test
class TestHandler {
fun sessionSolver(exprSolver: Queryable, equalsSolver: Queryable) : SessionSolver =
private fun sessionSolver(exprSolver: Queryable, equalsSolver: Queryable) : SessionSolver =
MemSessionSolver(exprSolver, equalsSolver).apply {
init(PredicateSymbol("equals", 2), JavaPredicateSymbol.EXPRESSION0, JavaPredicateSymbol.EXPRESSION1, JavaPredicateSymbol.EXPRESSION2, JavaPredicateSymbol.EXPRESSION3) }
private fun Builder.handler(vararg occurrences: ConstraintOccurrence): Handler =
Handler(sessionSolver(env.expressionSolver, env.equalsSolver), rules, listOf(* occurrences))
private fun <T : Any> Handler.eq(left: Logical<T>, right: Logical<T>) {
sessionSolver.tell(PredicateSymbol("equals", 2), left, right)
}
companion object {
@BeforeClass @JvmStatic fun setup() {
@ -48,7 +52,7 @@ class TestHandler {
constraint("foo")
))
).run {
handler().apply { process(occurrence("main")) }.let { rh ->
handler().apply { queue(occurrence("main")) }.let { rh ->
assertEquals(
setOf(ConstraintSymbol("main", 0), ConstraintSymbol("foo", 0)),
rh.occurrences().map { it.constraint().symbol() }.toSet())
@ -77,7 +81,7 @@ class TestHandler {
constraint("bar")
))
).run {
handler().apply { process(occurrence("main")) }.let { rh ->
handler().apply { queue(occurrence("main")) }.let { rh ->
assertEquals(
setOf(ConstraintSymbol("bar", 0), ConstraintSymbol("foo", 0)),
rh.occurrences().map { it.constraint().symbol() }.toSet())
@ -97,7 +101,7 @@ class TestHandler {
statement { test = "value" }
))
).run {
handler().process(occurrence("main"))
handler().queue(occurrence("main"))
assertEquals("value", test)
}
}
@ -115,7 +119,7 @@ class TestHandler {
statement ({ test.set("value") })
))
).run {
handler().process(occurrence("main"))
handler().queue(occurrence("main"))
assertEquals("value", test.get())
}
}
@ -134,7 +138,7 @@ class TestHandler {
statement ({ test = x.get() } )
))
).run {
handler().process(occurrence("main"))
handler().queue(occurrence("main"))
assertEquals("expected", test)
}
}
@ -161,7 +165,7 @@ class TestHandler {
statement ({ test = y.get() })
))
).run {
handler().apply { process(occurrence("main")) }.let { rh ->
handler().apply { queue(occurrence("main")) }.let { rh ->
assertEquals(
setOf(ConstraintSymbol("main", 0), ConstraintSymbol("next", 0)),
rh.occurrences().map { it.constraint().symbol() }.toSet())
@ -196,7 +200,7 @@ class TestHandler {
statement { test2 = "expected" }
))
).run {
handler().process(occurrence("main"))
handler().queue(occurrence("main"))
assertEquals("not initialized 1", test1)
assertEquals("expected", test2)
}
@ -218,7 +222,7 @@ class TestHandler {
).handler().run {
val a = logical<String>("a")
a.set("value")
process(occurrence("foo", a))
queue(occurrence("foo", a))
assertEquals(1, occurrences().size)
val co = occurrences().first()
assertEquals(ConstraintSymbol("bar",1), co.constraint().symbol())
@ -247,7 +251,7 @@ class TestHandler {
body( constraint("expected2") )
)
).handler().run {
process(occurrence("foo"))
queue(occurrence("foo"))
assertEquals(
setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0)),
occurrences().map { co -> co.constraint().symbol() }.toSet())
@ -272,13 +276,139 @@ class TestHandler {
body( constraint("expected2") )
)
).handler().run {
process(occurrence("foo"))
queue(occurrence("foo"))
assertEquals(
setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0)),
occurrences().map { co -> co.constraint().symbol() }.toSet())
}
}
@Test
fun occurrenceReactivated() {
val X = logicalPattern<Int>("X")
program(
rule("zeroth",
headKept( constraint("foo") ), body( statement({ x -> x.set(999) }, X),
constraint("bar", X))
),
rule("first",
headKept( constraint("foo") ),
body( constraint("bar", X),
constraint("qux", X))
),
rule("second",
headReplaced( constraint("qux", X) ),
body( constraint("expected1"),
statement({ x -> x.set(123) }, X))
),
rule("third",
headReplaced( constraint("foo") ),
body( constraint("unexpected"))
),
rule("fourth",
headReplaced( constraint("foo") ),
headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)),
body( constraint("expected2") )
),
rule("fifth",
headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 999 }, X)),
body( constraint("expected3", X))
)
).handler().run {
queue(occurrence("foo"))
assertEquals(3, occurrences().count())
assertEquals(
setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0), ConstraintSymbol("expected3", 1)),
occurrences().map { co -> co.constraint().symbol() }.toSet())
val ex3 = occurrences().filter { co -> co.constraint().symbol() == ConstraintSymbol("expected3", 1) }.first()
assertEquals(999, (ex3.arguments().first() as Logical<Int>).value())
}
}
@Test
fun occurrenceReactivatedAfterUnion() {
val (X, Y) = logicalPattern<Int>("X", "Y")
var handler : Handler? = null
program(
rule("first",
headKept( constraint("foo") ),
body( constraint("bar", X),
constraint("qux", Y),
statement({ x, y -> handler!!.eq(x, y) }, X, Y))
),
rule("second",
headReplaced( constraint("qux", Y) ),
body( constraint("expected1"),
statement({ y -> y.set(123) }, Y))
),
rule("third",
headReplaced( constraint("foo") ),
body( constraint("unexpected"))
),
rule("fourth",
headReplaced( constraint("foo") ),
headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)),
body( constraint("expected2") )
),
rule("fifth",
headKept( constraint("bar", X) ),
body( constraint("expected3", X))
)
).handler().run {
handler = this
queue(occurrence("foo"))
assertEquals(3, occurrences().count())
assertEquals(
setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0), ConstraintSymbol("expected3", 1)),
occurrences().map { co -> co.constraint().symbol() }.toSet())
val ex3 = occurrences().filter { co -> co.constraint().symbol() == ConstraintSymbol("expected3", 1) }.first()
assertEquals(null, (ex3.arguments().first() as Logical<Int>).value())
}
}
@Test
fun occurrenceReactivatedAfterUnionUnbound() {
val (X, Y) = logicalPattern<Int>("X", "Y")
var handler : Handler? = null
program(
rule("first",
headKept( constraint("foo") ),
body( constraint("bar", X),
statement({ x, y -> handler!!.eq(x, y) }, X, Y),
constraint("qux", Y))
),
rule("second",
headReplaced( constraint("qux", Y) ),
body( constraint("expected1"),
statement({ y -> y.set(123) }, Y))
),
rule("third",
headReplaced( constraint("foo") ),
body( constraint("unexpected"))
),
rule("fourth",
headReplaced( constraint("foo") ),
headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)),
body( constraint("expected2") )
),
rule("fifth",
headKept( constraint("bar", X) ),
body( constraint("expected3", X))
)
).handler().run {
handler = this
queue(occurrence("foo"))
assertEquals(3, occurrences().count())
assertEquals(
setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0), ConstraintSymbol("expected3", 1)),
occurrences().map { co -> co.constraint().symbol() }.toSet())
val ex3 = occurrences().filter { co -> co.constraint().symbol() == ConstraintSymbol("expected3", 1) }.first()
assertEquals(null, (ex3.arguments().first() as Logical<Int>).value())
}
}
}

View File

@ -7,6 +7,7 @@ import jetbrains.mps.logic.reactor.program.JavaPredicateSymbol
import jetbrains.mps.logic.reactor.program.PredicateSymbol
import org.junit.*
import org.junit.Assert.*
import solver.eq
/**
* @author Fedor Isakov