From c925edcadbabcdc6de5e0442105dc30a387d3088 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Wed, 8 May 2019 17:50:28 -0400 Subject: Implement interval arithmetic without exponentiation --- .../logic2viatra/tests/interval/AdditionTest.xtend | 49 +++++ .../logic2viatra/tests/interval/DivisionTest.xtend | 202 ++++++++++++++++++++ .../tests/interval/MultiplicationTest.xtend | 205 +++++++++++++++++++++ .../logic2viatra/tests/interval/NegationTest.xtend | 34 ++++ .../tests/interval/SubtractionTest.xtend | 49 +++++ 5 files changed, 539 insertions(+) create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/AdditionTest.xtend create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/DivisionTest.xtend create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/MultiplicationTest.xtend create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/NegationTest.xtend create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SubtractionTest.xtend (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/AdditionTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/AdditionTest.xtend new file mode 100644 index 00000000..de5f40e1 --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/AdditionTest.xtend @@ -0,0 +1,49 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval + +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval +import java.util.Collection +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.junit.runners.Parameterized.Parameters + +import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* + +@RunWith(Parameterized) +class AdditionTest { + @Parameters(name = "{index}: {0} + {1} = {2}") + static def Collection data() { + #[ + #[EMPTY, EMPTY, EMPTY], + #[EMPTY, between(-1, 1), EMPTY], + #[between(-1, 1), EMPTY, EMPTY], + #[UNBOUNDED, UNBOUNDED, UNBOUNDED], + #[UNBOUNDED, upTo(2), UNBOUNDED], + #[UNBOUNDED, above(-2), UNBOUNDED], + #[UNBOUNDED, between(-1, 1), UNBOUNDED], + #[upTo(2), UNBOUNDED, UNBOUNDED], + #[upTo(2), upTo(1), upTo(3)], + #[upTo(2), above(-1), UNBOUNDED], + #[upTo(2), between(-1, 2), upTo(4)], + #[above(-2), UNBOUNDED, UNBOUNDED], + #[above(-2), upTo(1), UNBOUNDED], + #[above(-2), above(-1), above(-3)], + #[above(-2), between(-1, 2), above(-3)], + #[between(-2, 3), UNBOUNDED, UNBOUNDED], + #[between(-2, 3), upTo(1), upTo(4)], + #[between(-2, 3), above(-1), above(-3)], + #[between(-2, 3), between(-1, 2.5), between(-3, 5.5)] + ] + } + + @Parameter(0) public var Interval a + @Parameter(1) public var Interval b + @Parameter(2) public var Interval result + + @Test + def void additionTest() { + Assert.assertEquals(result, a + b) + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/DivisionTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/DivisionTest.xtend new file mode 100644 index 00000000..3a8c0c5d --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/DivisionTest.xtend @@ -0,0 +1,202 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval + +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval +import java.util.Collection +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.junit.runners.Parameterized.Parameters + +import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* + +@RunWith(Parameterized) +class DivisionTest { + @Parameters(name="{index}: {0} / {1} = {2}") + static def Collection data() { + #[ + #[EMPTY, EMPTY, EMPTY], + #[EMPTY, between(-1, 1), EMPTY], + #[between(-1, 1), EMPTY, EMPTY], + #[UNBOUNDED, UNBOUNDED, UNBOUNDED], + #[UNBOUNDED, upTo(-2), UNBOUNDED], + #[UNBOUNDED, upTo(0), UNBOUNDED], + #[UNBOUNDED, upTo(3), UNBOUNDED], + #[UNBOUNDED, above(-2), UNBOUNDED], + #[UNBOUNDED, above(0), UNBOUNDED], + #[UNBOUNDED, above(3), UNBOUNDED], + #[UNBOUNDED, between(-4, -3), UNBOUNDED], + #[UNBOUNDED, between(-4, 0), UNBOUNDED], + #[UNBOUNDED, between(-3, 4), UNBOUNDED], + #[UNBOUNDED, between(0, 4), UNBOUNDED], + #[UNBOUNDED, between(3, 4), UNBOUNDED], + #[UNBOUNDED, ZERO, EMPTY], + #[upTo(-12), UNBOUNDED, UNBOUNDED], + #[upTo(-12), upTo(-2), above(0)], + #[upTo(-12), upTo(0), above(0)], + #[upTo(-12), upTo(3), UNBOUNDED], + #[upTo(-12), above(-2), UNBOUNDED], + #[upTo(-12), above(0), upTo(0)], + #[upTo(-12), above(3), upTo(0)], + #[upTo(-12), between(-4, -3), above(3)], + #[upTo(-12), between(-4, 0), above(3)], + #[upTo(-12), between(-3, 4), UNBOUNDED], + #[upTo(-12), between(0, 4), upTo(-3)], + #[upTo(-12), between(3, 4), upTo(-3)], + #[upTo(-12), ZERO, EMPTY], + #[upTo(0), UNBOUNDED, UNBOUNDED], + #[upTo(0), upTo(-2), above(0)], + #[upTo(0), upTo(0), above(0)], + #[upTo(0), upTo(3), UNBOUNDED], + #[upTo(0), above(-2), UNBOUNDED], + #[upTo(0), above(0), upTo(0)], + #[upTo(0), above(3), upTo(0)], + #[upTo(0), between(-4, -3), above(0)], + #[upTo(0), between(-4, 0), above(0)], + #[upTo(0), between(-3, 4), UNBOUNDED], + #[upTo(0), between(0, 4), upTo(0)], + #[upTo(0), between(3, 4), upTo(0)], + #[upTo(0), ZERO, EMPTY], + #[upTo(12), UNBOUNDED, UNBOUNDED], + #[upTo(12), upTo(-2), above(-6)], + #[upTo(12), upTo(0), UNBOUNDED], + #[upTo(12), upTo(3), UNBOUNDED], + #[upTo(12), above(-2), UNBOUNDED], + #[upTo(12), above(0), UNBOUNDED], + #[upTo(12), above(3), upTo(4)], + #[upTo(12), between(-4, -3), above(-4)], + #[upTo(12), between(-4, 0), UNBOUNDED], + #[upTo(12), between(-3, 4), UNBOUNDED], + #[upTo(12), between(0, 4), UNBOUNDED], + #[upTo(12), between(3, 4), upTo(4)], + #[upTo(12), ZERO, EMPTY], + #[above(-12), UNBOUNDED, UNBOUNDED], + #[above(-12), upTo(-2), upTo(6)], + #[above(-12), upTo(0), UNBOUNDED], + #[above(-12), upTo(3), UNBOUNDED], + #[above(-12), above(-2), UNBOUNDED], + #[above(-12), above(0), UNBOUNDED], + #[above(-12), above(3), above(-4)], + #[above(-12), between(-4, -3), upTo(4)], + #[above(-12), between(-4, 0), UNBOUNDED], + #[above(-12), between(-3, 4), UNBOUNDED], + #[above(-12), between(0, 4), UNBOUNDED], + #[above(-12), between(3, 4), above(-4)], + #[above(-12), ZERO, EMPTY], + #[above(0), UNBOUNDED, UNBOUNDED], + #[above(0), upTo(-2), upTo(0)], + #[above(0), upTo(0), upTo(0)], + #[above(0), upTo(3), UNBOUNDED], + #[above(0), above(-2), UNBOUNDED], + #[above(0), above(0), above(0)], + #[above(0), above(3), above(0)], + #[above(0), between(-4, -3), upTo(0)], + #[above(0), between(-4, 0), upTo(0)], + #[above(0), between(-3, 4), UNBOUNDED], + #[above(0), between(0, 4), above(0)], + #[above(0), between(3, 4), above(0)], + #[above(0), ZERO, EMPTY], + #[above(12), UNBOUNDED, UNBOUNDED], + #[above(12), upTo(-2), upTo(0)], + #[above(12), upTo(0), upTo(0)], + #[above(12), upTo(3), UNBOUNDED], + #[above(12), above(-2), UNBOUNDED], + #[above(12), above(0), above(0)], + #[above(12), above(3), above(0)], + #[above(12), between(-4, -3), upTo(-3)], + #[above(12), between(-4, 0), upTo(-3)], + #[above(12), between(-3, 4), UNBOUNDED], + #[above(12), between(0, 4), above(3)], + #[above(12), between(3, 4), above(3)], + #[above(12), ZERO, EMPTY], + #[between(-36, -12), UNBOUNDED, UNBOUNDED], + #[between(-36, -12), upTo(-2), between(0, 18)], + #[between(-36, -12), upTo(0), above(0)], + #[between(-36, -12), upTo(3), UNBOUNDED], + #[between(-36, -12), above(-2), UNBOUNDED], + #[between(-36, -12), above(0), upTo(0)], + #[between(-36, -12), above(3), between(-12, 0)], + #[between(-36, -12), between(-4, -3), between(3, 12)], + #[between(-36, -12), between(-4, 0), above(3)], + #[between(-36, -12), between(-3, 4), UNBOUNDED], + #[between(-36, -12), between(0, 4), upTo(-3)], + #[between(-36, -12), between(3, 4), between(-12, -3)], + #[between(-36, -12), ZERO, EMPTY], + #[between(-36, 0), UNBOUNDED, UNBOUNDED], + #[between(-36, 0), upTo(-2), between(0, 18)], + #[between(-36, 0), upTo(0), above(0)], + #[between(-36, 0), upTo(3), UNBOUNDED], + #[between(-36, 0), above(-2), UNBOUNDED], + #[between(-36, 0), above(0), upTo(0)], + #[between(-36, 0), above(3), between(-12, 0)], + #[between(-36, 0), between(-4, -3), between(0, 12)], + #[between(-36, 0), between(-4, 0), above(0)], + #[between(-36, 0), between(-3, 4), UNBOUNDED], + #[between(-36, 0), between(0, 4), upTo(0)], + #[between(-36, 0), between(3, 4), between(-12, 0)], + #[between(-36, 0), ZERO, EMPTY], + #[between(-12, 36), UNBOUNDED, UNBOUNDED], + #[between(-12, 36), upTo(-2), between(-18, 6)], + #[between(-12, 36), upTo(0), UNBOUNDED], + #[between(-12, 36), upTo(3), UNBOUNDED], + #[between(-12, 36), above(-2), UNBOUNDED], + #[between(-12, 36), above(0), UNBOUNDED], + #[between(-12, 36), above(3), between(-4, 12)], + #[between(-12, 36), between(-4, -3), between(-12, 4)], + #[between(-12, 36), between(-4, 0), UNBOUNDED], + #[between(-12, 36), between(-3, 4), UNBOUNDED], + #[between(-12, 36), between(0, 4), UNBOUNDED], + #[between(-12, 36), between(3, 4), between(-4, 12)], + #[between(-12, 36), ZERO, EMPTY], + #[between(0, 36), UNBOUNDED, UNBOUNDED], + #[between(0, 36), upTo(-2), between(-18, 0)], + #[between(0, 36), upTo(0), upTo(0)], + #[between(0, 36), upTo(3), UNBOUNDED], + #[between(0, 36), above(-2), UNBOUNDED], + #[between(0, 36), above(0), above(0)], + #[between(0, 36), above(3), between(0, 12)], + #[between(0, 36), between(-4, -3), between(-12, 0)], + #[between(0, 36), between(-4, 0), upTo(0)], + #[between(0, 36), between(-3, 4), UNBOUNDED], + #[between(0, 36), between(0, 4), above(0)], + #[between(0, 36), between(3, 4), between(0, 12)], + #[between(0, 36), ZERO, EMPTY], + #[between(12, 36), UNBOUNDED, UNBOUNDED], + #[between(12, 36), upTo(-2), between(-18, 0)], + #[between(12, 36), upTo(0), upTo(0)], + #[between(12, 36), upTo(3), UNBOUNDED], + #[between(12, 36), above(-2), UNBOUNDED], + #[between(12, 36), above(0), above(0)], + #[between(12, 36), above(3), between(0, 12)], + #[between(12, 36), between(-4, -3), between(-12, -3)], + #[between(12, 36), between(-4, 0), upTo(-3)], + #[between(12, 36), between(-3, 4), UNBOUNDED], + #[between(12, 36), between(0, 4), above(3)], + #[between(12, 36), between(3, 4), between(3, 12)], + #[between(12, 36), ZERO, EMPTY], + #[ZERO, UNBOUNDED, ZERO], + #[ZERO, upTo(-2), ZERO], + #[ZERO, upTo(0), ZERO], + #[ZERO, upTo(3), ZERO], + #[ZERO, above(-2), ZERO], + #[ZERO, above(0), ZERO], + #[ZERO, above(3), ZERO], + #[ZERO, between(-4, -3), ZERO], + #[ZERO, between(-4, 0), ZERO], + #[ZERO, between(-3, 4), ZERO], + #[ZERO, between(0, 4), ZERO], + #[ZERO, between(3, 4), ZERO], + #[ZERO, ZERO, EMPTY] + ] + } + + @Parameter(0) public var Interval a + @Parameter(1) public var Interval b + @Parameter(2) public var Interval result + + @Test + def void divisionTest() { + Assert.assertEquals(result, a / b) + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/MultiplicationTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/MultiplicationTest.xtend new file mode 100644 index 00000000..5f997094 --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/MultiplicationTest.xtend @@ -0,0 +1,205 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval + +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval +import java.util.Collection +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.junit.runners.Parameterized.Parameters + +import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* + +@RunWith(Parameterized) +class MultiplicationTest { + @Parameters(name="{index}: {0} * {1} = {2}") + static def Collection data() { + #[ + #[EMPTY, EMPTY, EMPTY], + #[EMPTY, between(-1, 1), EMPTY], + #[between(-1, 1), EMPTY, EMPTY], + #[UNBOUNDED, UNBOUNDED, UNBOUNDED], + #[UNBOUNDED, upTo(-2), UNBOUNDED], + #[UNBOUNDED, upTo(0), UNBOUNDED], + #[UNBOUNDED, upTo(3), UNBOUNDED], + #[UNBOUNDED, above(-2), UNBOUNDED], + #[UNBOUNDED, above(0), UNBOUNDED], + #[UNBOUNDED, above(3), UNBOUNDED], + #[UNBOUNDED, between(-4, -3), UNBOUNDED], + #[UNBOUNDED, between(-4, 0), UNBOUNDED], + #[UNBOUNDED, between(-3, 4), UNBOUNDED], + #[UNBOUNDED, between(0, 4), UNBOUNDED], + #[UNBOUNDED, between(3, 4), UNBOUNDED], + #[UNBOUNDED, ZERO, ZERO], + #[upTo(-5), UNBOUNDED, UNBOUNDED], + #[upTo(-5), upTo(-2), above(10)], + #[upTo(-5), upTo(0), above(0)], + #[upTo(-5), upTo(3), UNBOUNDED], + #[upTo(-5), above(-2), UNBOUNDED], + #[upTo(-5), above(0), upTo(0)], + #[upTo(-5), above(3), upTo(-15)], + #[upTo(-5), between(-4, -3), above(15)], + #[upTo(-5), between(-4, 0), above(0)], + #[upTo(-5), between(-3, 4), UNBOUNDED], + #[upTo(-5), between(0, 4), upTo(0)], + #[upTo(-5), between(3, 4), upTo(-15)], + #[upTo(-5), ZERO, ZERO], + #[upTo(0), UNBOUNDED, UNBOUNDED], + #[upTo(0), upTo(-2), above(0)], + #[upTo(0), upTo(0), above(0)], + #[upTo(0), upTo(3), UNBOUNDED], + #[upTo(0), above(-2), UNBOUNDED], + #[upTo(0), above(0), upTo(0)], + #[upTo(0), above(3), upTo(0)], + #[upTo(0), between(-4, -3), above(0)], + #[upTo(0), between(-4, 0), above(0)], + #[upTo(0), between(-3, 4), UNBOUNDED], + #[upTo(0), between(0, 4), upTo(0)], + #[upTo(0), between(3, 4), upTo(0)], + #[upTo(0), ZERO, ZERO], + #[upTo(5), UNBOUNDED, UNBOUNDED], + #[upTo(5), upTo(-2), UNBOUNDED], + #[upTo(5), upTo(0), UNBOUNDED], + #[upTo(5), upTo(3), UNBOUNDED], + #[upTo(5), above(-2), UNBOUNDED], + #[upTo(5), above(0), UNBOUNDED], + #[upTo(5), above(3), UNBOUNDED], + #[upTo(5), between(-4, -3), above(-20)], + #[upTo(5), between(-4, 0), above(-20)], + #[upTo(5), between(-3, 4), UNBOUNDED], + #[upTo(5), between(0, 4), upTo(20)], + #[upTo(5), between(3, 4), upTo(20)], + #[upTo(5), ZERO, ZERO], + #[above(-5), UNBOUNDED, UNBOUNDED], + #[above(-5), upTo(-2), UNBOUNDED], + #[above(-5), upTo(0), UNBOUNDED], + #[above(-5), upTo(3), UNBOUNDED], + #[above(-5), above(-2), UNBOUNDED], + #[above(-5), above(0), UNBOUNDED], + #[above(-5), above(3), UNBOUNDED], + #[above(-5), between(-4, -3), upTo(20)], + #[above(-5), between(-4, 0), upTo(20)], + #[above(-5), between(-3, 4), UNBOUNDED], + #[above(-5), between(0, 4), above(-20)], + #[above(-5), between(3, 4), above(-20)], + #[above(-5), ZERO, ZERO], + #[above(0), UNBOUNDED, UNBOUNDED], + #[above(0), upTo(-2), upTo(0)], + #[above(0), upTo(0), upTo(0)], + #[above(0), upTo(3), UNBOUNDED], + #[above(0), above(-2), UNBOUNDED], + #[above(0), above(0), above(0)], + #[above(0), above(3), above(0)], + #[above(0), between(-4, -3), upTo(0)], + #[above(0), between(-4, 0), upTo(0)], + #[above(0), between(-3, 4), UNBOUNDED], + #[above(0), between(0, 4), above(0)], + #[above(0), between(3, 4), above(0)], + #[above(0), ZERO, ZERO], + #[above(5), UNBOUNDED, UNBOUNDED], + #[above(5), upTo(-2), upTo(-10)], + #[above(5), upTo(0), upTo(0)], + #[above(5), upTo(3), UNBOUNDED], + #[above(5), above(-2), UNBOUNDED], + #[above(5), above(0), above(0)], + #[above(5), above(3), above(15)], + #[above(5), between(-4, -3), upTo(-15)], + #[above(5), between(-4, 0), upTo(0)], + #[above(5), between(-3, 4), UNBOUNDED], + #[above(5), between(0, 4), above(0)], + #[above(5), between(3, 4), above(15)], + #[above(5), ZERO, ZERO], + #[between(-6, -5), UNBOUNDED, UNBOUNDED], + #[between(-6, -5), upTo(-2), above(10)], + #[between(-6, -5), upTo(0), above(0)], + #[between(-6, -5), upTo(3), above(-18)], + #[between(-6, -5), above(-2), upTo(12)], + #[between(-6, -5), above(0), upTo(0)], + #[between(-6, -5), above(3), upTo(-15)], + #[between(-6, -5), between(-4, -3), between(15, 24)], + #[between(-6, -5), between(-4, 0), between(0, 24)], + #[between(-6, -5), between(-3, 4), between(-24, 18)], + #[between(-6, -5), between(0, 4), between(-24, 0)], + #[between(-6, -5), between(3, 4), between(-24, -15)], + #[between(-6, -5), ZERO, ZERO], + #[between(-6, 0), UNBOUNDED, UNBOUNDED], + #[between(-6, 0), upTo(-2), above(0)], + #[between(-6, 0), upTo(0), above(0)], + #[between(-6, 0), upTo(3), above(-18)], + #[between(-6, 0), above(-2), upTo(12)], + #[between(-6, 0), above(0), upTo(0)], + #[between(-6, 0), above(3), upTo(0)], + #[between(-6, 0), between(-4, -3), between(0, 24)], + #[between(-6, 0), between(-4, 0), between(0, 24)], + #[between(-6, 0), between(-3, 4), between(-24, 18)], + #[between(-6, 0), between(0, 4), between(-24, 0)], + #[between(-6, 0), between(3, 4), between(-24, 0)], + #[between(-6, 0), ZERO, ZERO], + #[between(-5, 6), UNBOUNDED, UNBOUNDED], + #[between(-5, 6), upTo(-2), UNBOUNDED], + #[between(-5, 6), upTo(0), UNBOUNDED], + #[between(-5, 6), upTo(3), UNBOUNDED], + #[between(-5, 6), above(-2), UNBOUNDED], + #[between(-5, 6), above(0), UNBOUNDED], + #[between(-5, 6), above(3), UNBOUNDED], + #[between(-5, 6), between(-4, -3), between(-24, 20)], + #[between(-5, 6), between(-4, 0), between(-24, 20)], + #[between(-5, 6), between(-3, 4), between(-20, 24)], + #[between(-5, 6), between(-3, 2), between(-18, 15)], + #[between(-5, 1), between(-3, 4), between(-20, 15)], + #[between(-5, 1), between(-3, 2), between(-10, 15)], + #[between(-5, 6), between(0, 4), between(-20, 24)], + #[between(-5, 6), between(3, 4), between(-20, 24)], + #[between(-5, 6), ZERO, ZERO], + #[between(0, 6), UNBOUNDED, UNBOUNDED], + #[between(0, 6), upTo(-2), upTo(0)], + #[between(0, 6), upTo(0), upTo(0)], + #[between(0, 6), upTo(3), upTo(18)], + #[between(0, 6), above(-2), above(-12)], + #[between(0, 6), above(0), above(0)], + #[between(0, 6), above(3), above(0)], + #[between(0, 6), between(-4, -3), between(-24, 0)], + #[between(0, 6), between(-4, 0), between(-24, 0)], + #[between(0, 6), between(-3, 4), between(-18, 24)], + #[between(0, 6), between(0, 4), between(0, 24)], + #[between(0, 6), between(3, 4), between(0, 24)], + #[between(0, 6), ZERO, ZERO], + #[between(5, 6), UNBOUNDED, UNBOUNDED], + #[between(5, 6), upTo(-2), upTo(-10)], + #[between(5, 6), upTo(0), upTo(0)], + #[between(5, 6), upTo(3), upTo(18)], + #[between(5, 6), above(-2), above(-12)], + #[between(5, 6), above(0), above(0)], + #[between(5, 6), above(3), above(15)], + #[between(5, 6), between(-4, -3), between(-24, -15)], + #[between(5, 6), between(-4, 0), between(-24, 0)], + #[between(5, 6), between(-3, 4), between(-18, 24)], + #[between(5, 6), between(0, 4), between(0, 24)], + #[between(5, 6), between(3, 4), between(15, 24)], + #[between(5, 6), ZERO, ZERO], + #[ZERO, UNBOUNDED, ZERO], + #[ZERO, upTo(-2), ZERO], + #[ZERO, upTo(0), ZERO], + #[ZERO, upTo(3), ZERO], + #[ZERO, above(-2), ZERO], + #[ZERO, above(0), ZERO], + #[ZERO, above(3), ZERO], + #[ZERO, between(-4, -3), ZERO], + #[ZERO, between(-4, 0), ZERO], + #[ZERO, between(-3, 4), ZERO], + #[ZERO, between(0, 4), ZERO], + #[ZERO, between(3, 4), ZERO], + #[ZERO, ZERO, ZERO] + ] + } + + @Parameter(0) public var Interval a + @Parameter(1) public var Interval b + @Parameter(2) public var Interval result + + @Test + def void multiplicatonTest() { + Assert.assertEquals(result, a * b) + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/NegationTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/NegationTest.xtend new file mode 100644 index 00000000..477e925e --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/NegationTest.xtend @@ -0,0 +1,34 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval + +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval +import java.util.Collection +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.junit.runners.Parameterized.Parameters + +import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* + +@RunWith(Parameterized) +class NegationTest { + @Parameters(name = "{index}: -{0} = {1}") + static def Collection data() { + #[ + #[EMPTY, EMPTY], + #[UNBOUNDED, UNBOUNDED], + #[upTo(1), above(-1)], + #[above(1), upTo(-1)], + #[between(2, 3), between(-3, -2)] + ] + } + + @Parameter(0) public var Interval a + @Parameter(1) public var Interval result + + @Test + def void negationTest() { + Assert.assertEquals(result, -a) + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SubtractionTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SubtractionTest.xtend new file mode 100644 index 00000000..30709a9e --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SubtractionTest.xtend @@ -0,0 +1,49 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval + +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval +import java.util.Collection +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.junit.runners.Parameterized.Parameters + +import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* + +@RunWith(Parameterized) +class SubtractionTest { + @Parameters(name = "{index}: {0} - {1} = {2}") + static def Collection data() { + #[ + #[EMPTY, EMPTY, EMPTY], + #[EMPTY, between(-1, 1), EMPTY], + #[between(-1, 1), EMPTY, EMPTY], + #[UNBOUNDED, UNBOUNDED, UNBOUNDED], + #[UNBOUNDED, upTo(2), UNBOUNDED], + #[UNBOUNDED, above(-2), UNBOUNDED], + #[UNBOUNDED, between(-1, 1), UNBOUNDED], + #[upTo(2), UNBOUNDED, UNBOUNDED], + #[upTo(2), upTo(1), UNBOUNDED], + #[upTo(2), above(-1), upTo(3)], + #[upTo(2), between(-1, 2), upTo(3)], + #[above(-2), UNBOUNDED, UNBOUNDED], + #[above(-2), upTo(1), above(-3)], + #[above(-2), above(-1), UNBOUNDED], + #[above(-2), between(-1, 2), above(-4)], + #[between(-2, 3), UNBOUNDED, UNBOUNDED], + #[between(-2, 3), upTo(1), above(-3)], + #[between(-2, 3), above(-1), upTo(4)], + #[between(-2, 3), between(-1, 2.5), between(-4.5, 4)] + ] + } + + @Parameter(0) public var Interval a + @Parameter(1) public var Interval b + @Parameter(2) public var Interval result + + @Test + def void subtractionTest() { + Assert.assertEquals(result, a - b) + } +} -- cgit v1.2.3-54-g00ecf From 1999ab4733071c6a4c9989c137eb44ec62b09847 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 9 May 2019 00:49:19 -0400 Subject: Interval comparison --- .../logic2viatra/interval/Interval.xtend | 101 ++++++++++++++++- .../logic2viatra/tests/interval/RelationTest.xtend | 120 +++++++++++++++++++++ 2 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/RelationTest.xtend (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend index cf22315b..93749767 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend @@ -13,7 +13,47 @@ abstract class Interval { private new() { } - abstract def boolean isZero() + abstract def boolean mustEqual(Interval other) + + abstract def boolean mayEqual(Interval other) + + def mustNotEqual(Interval other) { + !mayEqual(other) + } + + def mayNotEqual(Interval other) { + !mustEqual(other) + } + + abstract def boolean mustBeLessThan(Interval other) + + abstract def boolean mayBeLessThan(Interval other) + + def mustBeLessThanOrEqual(Interval other) { + !mayBeGreaterThan(other) + } + + def mayBeLessThanOrEqual(Interval other) { + !mustBeGreaterThan(other) + } + + def mustBeGreaterThan(Interval other) { + other.mustBeLessThan(this) + } + + def mayBeGreaterThan(Interval other) { + other.mayBeLessThan(this) + } + + def mustBeGreaterThanOrEqual(Interval other) { + other.mustBeLessThanOrEqual(this) + } + + def mayBeGreaterThanOrEqual(Interval other) { + other.mayBeLessThanOrEqual(this) + } + + abstract def Interval join(Interval other) def operator_plus() { this @@ -30,9 +70,25 @@ abstract class Interval { abstract def Interval operator_divide(Interval other) public static val EMPTY = new Interval { - override isZero() { + override mustEqual(Interval other) { + true + } + + override mayEqual(Interval other) { false } + + override mustBeLessThan(Interval other) { + true + } + + override mayBeLessThan(Interval other) { + false + } + + override join(Interval other) { + other + } override operator_minus() { EMPTY @@ -98,8 +154,45 @@ abstract class Interval { this.upper = upper } - override isZero() { - upper == BigDecimal.ZERO && lower == BigDecimal.ZERO + override mustEqual(Interval other) { + switch (other) { + case EMPTY: true + NonEmpty: lower == upper && lower == other.lower && lower == other.upper + default: throw new IllegalArgumentException("") + } + } + + override mayEqual(Interval other) { + if (other instanceof NonEmpty) { + (lower === null || other.upper === null || lower <= other.upper) && + (other.lower === null || upper === null || other.lower <= upper) + } else { + false + } + } + + override mustBeLessThan(Interval other) { + switch (other) { + case EMPTY: true + NonEmpty: upper !== null && other.lower !== null && upper < other.lower + default: throw new IllegalArgumentException("") + } + } + + override mayBeLessThan(Interval other) { + if (other instanceof NonEmpty) { + lower === null || other.upper === null || lower < other.upper + } else { + false + } + } + + override join(Interval other) { + switch (other) { + case EMPTY: this + NonEmpty: new NonEmpty(lower.tryMin(other.lower), upper.tryMin(other.upper)) + default: throw new IllegalArgumentException("") + } } override operator_minus() { diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/RelationTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/RelationTest.xtend new file mode 100644 index 00000000..23fc69ea --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/RelationTest.xtend @@ -0,0 +1,120 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval + +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval +import java.util.Collection +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.junit.runners.Parameterized.Parameters + +import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* + +@RunWith(Parameterized) +class RelationTest { + @Parameters(name = "{index}: {0} <> {1}") + static def Collection data() { + #[ + #[EMPTY, EMPTY, true, false, true, false], + #[EMPTY, between(1, 2), true, false, true, false], + #[between(1, 2), EMPTY, true, false, true, false], + #[upTo(1), upTo(0), false, true, false, true], + #[upTo(1), upTo(1), false, true, false, true], + #[upTo(1), upTo(2), false, true, false, true], + #[upTo(1), above(0), false, true, false, true], + #[upTo(1), above(1), false, true, false, true], + #[upTo(1), above(2), false, false, true, true], + #[upTo(1), between(-1, -1), false, true, false, true], + #[upTo(1), between(-1, 0), false, true, false, true], + #[upTo(1), between(-1, 1), false, true, false, true], + #[upTo(1), between(-1, 2), false, true, false, true], + #[upTo(1), between(1, 1), false, true, false, true], + #[upTo(1), between(1, 2), false, true, false, true], + #[upTo(1), between(2, 2), false, false, true, true], + #[upTo(1), between(2, 3), false, false, true, true], + #[above(1), upTo(0), false, false, false, false], + #[above(1), upTo(1), false, true, false, false], + #[above(1), upTo(2), false, true, false, true], + #[above(1), above(0), false, true, false, true], + #[above(1), above(1), false, true, false, true], + #[above(1), above(2), false, true, false, true], + #[above(1), between(-1, -1), false, false, false, false], + #[above(1), between(-1, 0), false, false, false, false], + #[above(1), between(-1, 1), false, true, false, false], + #[above(1), between(-1, 2), false, true, false, true], + #[above(1), between(1, 1), false, true, false, false], + #[above(1), between(1, 2), false, true, false, true], + #[above(1), between(2, 2), false, true, false, true], + #[above(1), between(2, 3), false, true, false, true], + #[between(1, 1), upTo(0), false, false, false, false], + #[between(1, 1), upTo(1), false, true, false, false], + #[between(1, 1), upTo(2), false, true, false, true], + #[between(1, 1), above(0), false, true, false, true], + #[between(1, 1), above(1), false, true, false, true], + #[between(1, 1), above(2), false, false, true, true], + #[between(1, 1), between(-1, -1), false, false, false, false], + #[between(1, 1), between(-1, 0), false, false, false, false], + #[between(1, 1), between(-1, 1), false, true, false, false], + #[between(1, 1), between(-1, 2), false, true, false, true], + #[between(1, 1), between(1, 1), true, true, false, false], + #[between(1, 1), between(1, 2), false, true, false, true], + #[between(1, 1), between(2, 2), false, false, true, true], + #[between(1, 1), between(2, 3), false, false, true, true], + #[between(-1, 1), upTo(-2), false, false, false, false], + #[between(-1, 1), upTo(-1), false, true, false, false], + #[between(-1, 1), upTo(0), false, true, false, true], + #[between(-1, 1), upTo(1), false, true, false, true], + #[between(-1, 1), upTo(2), false, true, false, true], + #[between(-1, 1), above(-2), false, true, false, true], + #[between(-1, 1), above(-1), false, true, false, true], + #[between(-1, 1), above(0), false, true, false, true], + #[between(-1, 1), above(1), false, true, false, true], + #[between(-1, 1), above(2), false, false, true, true], + #[between(-1, 1), between(-3, -2), false, false, false, false], + #[between(-1, 1), between(-2, -2), false, false, false, false], + #[between(-1, 1), between(-2, -1), false, true, false, false], + #[between(-1, 1), between(-2, 0), false, true, false, true], + #[between(-1, 1), between(-2, 1), false, true, false, true], + #[between(-1, 1), between(-2, 2), false, true, false, true], + #[between(-1, 1), between(-1, -1), false, true, false, false], + #[between(-1, 1), between(-1, 0), false, true, false, true], + #[between(-1, 1), between(-1, 1), false, true, false, true], + #[between(-1, 1), between(-1, 2), false, true, false, true], + #[between(-1, 1), between(0, 0), false, true, false, true], + #[between(-1, 1), between(0, 1), false, true, false, true], + #[between(-1, 1), between(0, 2), false, true, false, true], + #[between(-1, 1), between(1, 1), false, true, false, true], + #[between(-1, 1), between(1, 2), false, true, false, true], + #[between(-1, 1), between(2, 2), false, false, true, true], + #[between(-1, 1), between(2, 3), false, false, true, true] + ] + } + + @Parameter(0) public var Interval a + @Parameter(1) public var Interval b + @Parameter(2) public var boolean mustEqual + @Parameter(3) public var boolean mayEqual + @Parameter(4) public var boolean mustBeLessThan + @Parameter(5) public var boolean mayBeLessThan + + @Test + def void mustEqualTest() { + Assert.assertEquals(mustEqual, a.mustEqual(b)) + } + + @Test + def void mayEqualTest() { + Assert.assertEquals(mayEqual, a.mayEqual(b)) + } + + @Test + def void mustBeLessThanTest() { + Assert.assertEquals(mustBeLessThan, a.mustBeLessThan(b)) + } + + @Test + def void mayBeLessThanTest() { + Assert.assertEquals(mayBeLessThan, a.mayBeLessThan(b)) + } +} -- cgit v1.2.3-54-g00ecf From 94a7e721fba3c3bf6bcda75cde474e21c5afdf39 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 9 May 2019 09:28:40 -0400 Subject: Fix interval join --- .../logic2viatra/interval/Interval.xtend | 2 +- .../logic2viatra/tests/interval/RelationTest.xtend | 150 +++++++++++---------- 2 files changed, 79 insertions(+), 73 deletions(-) (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend index 93749767..6ea96866 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend @@ -190,7 +190,7 @@ abstract class Interval { override join(Interval other) { switch (other) { case EMPTY: this - NonEmpty: new NonEmpty(lower.tryMin(other.lower), upper.tryMin(other.upper)) + NonEmpty: new NonEmpty(lower.tryMin(other.lower), upper.tryMax(other.upper)) default: throw new IllegalArgumentException("") } } diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/RelationTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/RelationTest.xtend index 23fc69ea..5527fbaa 100644 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/RelationTest.xtend +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/RelationTest.xtend @@ -16,78 +16,78 @@ class RelationTest { @Parameters(name = "{index}: {0} <> {1}") static def Collection data() { #[ - #[EMPTY, EMPTY, true, false, true, false], - #[EMPTY, between(1, 2), true, false, true, false], - #[between(1, 2), EMPTY, true, false, true, false], - #[upTo(1), upTo(0), false, true, false, true], - #[upTo(1), upTo(1), false, true, false, true], - #[upTo(1), upTo(2), false, true, false, true], - #[upTo(1), above(0), false, true, false, true], - #[upTo(1), above(1), false, true, false, true], - #[upTo(1), above(2), false, false, true, true], - #[upTo(1), between(-1, -1), false, true, false, true], - #[upTo(1), between(-1, 0), false, true, false, true], - #[upTo(1), between(-1, 1), false, true, false, true], - #[upTo(1), between(-1, 2), false, true, false, true], - #[upTo(1), between(1, 1), false, true, false, true], - #[upTo(1), between(1, 2), false, true, false, true], - #[upTo(1), between(2, 2), false, false, true, true], - #[upTo(1), between(2, 3), false, false, true, true], - #[above(1), upTo(0), false, false, false, false], - #[above(1), upTo(1), false, true, false, false], - #[above(1), upTo(2), false, true, false, true], - #[above(1), above(0), false, true, false, true], - #[above(1), above(1), false, true, false, true], - #[above(1), above(2), false, true, false, true], - #[above(1), between(-1, -1), false, false, false, false], - #[above(1), between(-1, 0), false, false, false, false], - #[above(1), between(-1, 1), false, true, false, false], - #[above(1), between(-1, 2), false, true, false, true], - #[above(1), between(1, 1), false, true, false, false], - #[above(1), between(1, 2), false, true, false, true], - #[above(1), between(2, 2), false, true, false, true], - #[above(1), between(2, 3), false, true, false, true], - #[between(1, 1), upTo(0), false, false, false, false], - #[between(1, 1), upTo(1), false, true, false, false], - #[between(1, 1), upTo(2), false, true, false, true], - #[between(1, 1), above(0), false, true, false, true], - #[between(1, 1), above(1), false, true, false, true], - #[between(1, 1), above(2), false, false, true, true], - #[between(1, 1), between(-1, -1), false, false, false, false], - #[between(1, 1), between(-1, 0), false, false, false, false], - #[between(1, 1), between(-1, 1), false, true, false, false], - #[between(1, 1), between(-1, 2), false, true, false, true], - #[between(1, 1), between(1, 1), true, true, false, false], - #[between(1, 1), between(1, 2), false, true, false, true], - #[between(1, 1), between(2, 2), false, false, true, true], - #[between(1, 1), between(2, 3), false, false, true, true], - #[between(-1, 1), upTo(-2), false, false, false, false], - #[between(-1, 1), upTo(-1), false, true, false, false], - #[between(-1, 1), upTo(0), false, true, false, true], - #[between(-1, 1), upTo(1), false, true, false, true], - #[between(-1, 1), upTo(2), false, true, false, true], - #[between(-1, 1), above(-2), false, true, false, true], - #[between(-1, 1), above(-1), false, true, false, true], - #[between(-1, 1), above(0), false, true, false, true], - #[between(-1, 1), above(1), false, true, false, true], - #[between(-1, 1), above(2), false, false, true, true], - #[between(-1, 1), between(-3, -2), false, false, false, false], - #[between(-1, 1), between(-2, -2), false, false, false, false], - #[between(-1, 1), between(-2, -1), false, true, false, false], - #[between(-1, 1), between(-2, 0), false, true, false, true], - #[between(-1, 1), between(-2, 1), false, true, false, true], - #[between(-1, 1), between(-2, 2), false, true, false, true], - #[between(-1, 1), between(-1, -1), false, true, false, false], - #[between(-1, 1), between(-1, 0), false, true, false, true], - #[between(-1, 1), between(-1, 1), false, true, false, true], - #[between(-1, 1), between(-1, 2), false, true, false, true], - #[between(-1, 1), between(0, 0), false, true, false, true], - #[between(-1, 1), between(0, 1), false, true, false, true], - #[between(-1, 1), between(0, 2), false, true, false, true], - #[between(-1, 1), between(1, 1), false, true, false, true], - #[between(-1, 1), between(1, 2), false, true, false, true], - #[between(-1, 1), between(2, 2), false, false, true, true], - #[between(-1, 1), between(2, 3), false, false, true, true] + #[EMPTY, EMPTY, true, false, true, false, EMPTY], + #[EMPTY, between(1, 2), true, false, true, false, between(1, 2)], + #[between(1, 2), EMPTY, true, false, true, false, between(1, 2)], + #[upTo(1), upTo(0), false, true, false, true, upTo(1)], + #[upTo(1), upTo(1), false, true, false, true, upTo(1)], + #[upTo(1), upTo(2), false, true, false, true, upTo(2)], + #[upTo(1), above(0), false, true, false, true, UNBOUNDED], + #[upTo(1), above(1), false, true, false, true, UNBOUNDED], + #[upTo(1), above(2), false, false, true, true, UNBOUNDED], + #[upTo(1), between(-1, -1), false, true, false, true, upTo(1)], + #[upTo(1), between(-1, 0), false, true, false, true, upTo(1)], + #[upTo(1), between(-1, 1), false, true, false, true, upTo(1)], + #[upTo(1), between(-1, 2), false, true, false, true, upTo(2)], + #[upTo(1), between(1, 1), false, true, false, true, upTo(1)], + #[upTo(1), between(1, 2), false, true, false, true, upTo(2)], + #[upTo(1), between(2, 2), false, false, true, true, upTo(2)], + #[upTo(1), between(2, 3), false, false, true, true, upTo(3)], + #[above(1), upTo(0), false, false, false, false, UNBOUNDED], + #[above(1), upTo(1), false, true, false, false, UNBOUNDED], + #[above(1), upTo(2), false, true, false, true, UNBOUNDED], + #[above(1), above(0), false, true, false, true, above(0)], + #[above(1), above(1), false, true, false, true, above(1)], + #[above(1), above(2), false, true, false, true, above(1)], + #[above(1), between(-1, -1), false, false, false, false, above(-1)], + #[above(1), between(-1, 0), false, false, false, false, above(-1)], + #[above(1), between(-1, 1), false, true, false, false, above(-1)], + #[above(1), between(-1, 2), false, true, false, true, above(-1)], + #[above(1), between(1, 1), false, true, false, false, above(1)], + #[above(1), between(1, 2), false, true, false, true, above(1)], + #[above(1), between(2, 2), false, true, false, true, above(1)], + #[above(1), between(2, 3), false, true, false, true, above(1)], + #[between(1, 1), upTo(0), false, false, false, false, upTo(1)], + #[between(1, 1), upTo(1), false, true, false, false, upTo(1)], + #[between(1, 1), upTo(2), false, true, false, true, upTo(2)], + #[between(1, 1), above(0), false, true, false, true, above(0)], + #[between(1, 1), above(1), false, true, false, true, above(1)], + #[between(1, 1), above(2), false, false, true, true, above(1)], + #[between(1, 1), between(-1, -1), false, false, false, false, between(-1, 1)], + #[between(1, 1), between(-1, 0), false, false, false, false, between(-1, 1)], + #[between(1, 1), between(-1, 1), false, true, false, false, between(-1, 1)], + #[between(1, 1), between(-1, 2), false, true, false, true, between(-1, 2)], + #[between(1, 1), between(1, 1), true, true, false, false, between(1, 1)], + #[between(1, 1), between(1, 2), false, true, false, true, between(1, 2)], + #[between(1, 1), between(2, 2), false, false, true, true, between(1, 2)], + #[between(1, 1), between(2, 3), false, false, true, true, between(1, 3)], + #[between(-1, 1), upTo(-2), false, false, false, false, upTo(1)], + #[between(-1, 1), upTo(-1), false, true, false, false, upTo(1)], + #[between(-1, 1), upTo(0), false, true, false, true, upTo(1)], + #[between(-1, 1), upTo(1), false, true, false, true, upTo(1)], + #[between(-1, 1), upTo(2), false, true, false, true, upTo(2)], + #[between(-1, 1), above(-2), false, true, false, true, above(-2)], + #[between(-1, 1), above(-1), false, true, false, true, above(-1)], + #[between(-1, 1), above(0), false, true, false, true, above(-1)], + #[between(-1, 1), above(1), false, true, false, true, above(-1)], + #[between(-1, 1), above(2), false, false, true, true, above(-1)], + #[between(-1, 1), between(-3, -2), false, false, false, false, between(-3, 1)], + #[between(-1, 1), between(-2, -2), false, false, false, false, between(-2, 1)], + #[between(-1, 1), between(-2, -1), false, true, false, false, between(-2, 1)], + #[between(-1, 1), between(-2, 0), false, true, false, true, between(-2, 1)], + #[between(-1, 1), between(-2, 1), false, true, false, true, between(-2, 1)], + #[between(-1, 1), between(-2, 2), false, true, false, true, between(-2, 2)], + #[between(-1, 1), between(-1, -1), false, true, false, false, between(-1, 1)], + #[between(-1, 1), between(-1, 0), false, true, false, true, between(-1, 1)], + #[between(-1, 1), between(-1, 1), false, true, false, true, between(-1, 1)], + #[between(-1, 1), between(-1, 2), false, true, false, true, between(-1, 2)], + #[between(-1, 1), between(0, 0), false, true, false, true, between(-1, 1)], + #[between(-1, 1), between(0, 1), false, true, false, true, between(-1, 1)], + #[between(-1, 1), between(0, 2), false, true, false, true, between(-1, 2)], + #[between(-1, 1), between(1, 1), false, true, false, true, between(-1, 1)], + #[between(-1, 1), between(1, 2), false, true, false, true, between(-1, 2)], + #[between(-1, 1), between(2, 2), false, false, true, true, between(-1, 2)], + #[between(-1, 1), between(2, 3), false, false, true, true, between(-1, 3)] ] } @@ -97,6 +97,7 @@ class RelationTest { @Parameter(3) public var boolean mayEqual @Parameter(4) public var boolean mustBeLessThan @Parameter(5) public var boolean mayBeLessThan + @Parameter(6) public var Interval join @Test def void mustEqualTest() { @@ -117,4 +118,9 @@ class RelationTest { def void mayBeLessThanTest() { Assert.assertEquals(mayBeLessThan, a.mayBeLessThan(b)) } + + @Test + def void joinTest() { + Assert.assertEquals(join, a.join(b)) + } } -- cgit v1.2.3-54-g00ecf From ba167247757d76df603a6527d9ad51c3d9f150b9 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 9 May 2019 20:24:56 -0400 Subject: Interval aggregation operators --- .../build.properties | 3 +- .../logic2viatra/interval/Interval.xtend | 88 +- .../interval/IntervalAggregationMode.java | 66 + .../interval/IntervalAggregationOperator.xtend | 48 + .../interval/IntervalRedBlackNode.xtend | 177 +++ .../logic2viatra/interval/RedBlackNode.java | 1392 ++++++++++++++++++++ .../logic2viatra/interval/Reference.java | 51 + .../logic2viatra/tests/interval/SumTest.xtend | 140 ++ 8 files changed, 1947 insertions(+), 18 deletions(-) create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationMode.java create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationOperator.xtend create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalRedBlackNode.xtend create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/RedBlackNode.java create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Reference.java create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/build.properties b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/build.properties index 585df5ce..9ffc994a 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/build.properties +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/build.properties @@ -4,6 +4,5 @@ bin.includes = META-INF/,\ source.. = src/,\ patterns/,\ vql-gen/,\ - xtend-gen/,\ - src-gen/ + xtend-gen/ output.. = bin/ diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend index 6ea96866..229656c0 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend @@ -5,7 +5,7 @@ import java.math.MathContext import java.math.RoundingMode import org.eclipse.xtend.lib.annotations.Data -abstract class Interval { +abstract class Interval implements Comparable { static val PRECISION = 32 static val ROUND_DOWN = new MathContext(PRECISION, RoundingMode.FLOOR) static val ROUND_UP = new MathContext(PRECISION, RoundingMode.CEILING) @@ -24,35 +24,35 @@ abstract class Interval { def mayNotEqual(Interval other) { !mustEqual(other) } - + abstract def boolean mustBeLessThan(Interval other) - + abstract def boolean mayBeLessThan(Interval other) - + def mustBeLessThanOrEqual(Interval other) { !mayBeGreaterThan(other) } - + def mayBeLessThanOrEqual(Interval other) { !mustBeGreaterThan(other) } - + def mustBeGreaterThan(Interval other) { other.mustBeLessThan(this) } - + def mayBeGreaterThan(Interval other) { other.mayBeLessThan(this) } - + def mustBeGreaterThanOrEqual(Interval other) { other.mustBeLessThanOrEqual(this) } - + def mayBeGreaterThanOrEqual(Interval other) { other.mayBeLessThanOrEqual(this) } - + abstract def Interval join(Interval other) def operator_plus() { @@ -65,6 +65,8 @@ abstract class Interval { abstract def Interval operator_minus(Interval other) + abstract def Interval operator_multiply(int count) + abstract def Interval operator_multiply(Interval other) abstract def Interval operator_divide(Interval other) @@ -77,15 +79,15 @@ abstract class Interval { override mayEqual(Interval other) { false } - + override mustBeLessThan(Interval other) { true } - + override mayBeLessThan(Interval other) { false } - + override join(Interval other) { other } @@ -102,6 +104,10 @@ abstract class Interval { EMPTY } + override operator_multiply(int count) { + EMPTY + } + override operator_multiply(Interval other) { EMPTY } @@ -113,6 +119,15 @@ abstract class Interval { override toString() { "∅" } + + override compareTo(Interval o) { + if (o == EMPTY) { + 0 + } else { + -1 + } + } + } public static val Interval ZERO = new NonEmpty(BigDecimal.ZERO, BigDecimal.ZERO) @@ -170,7 +185,7 @@ abstract class Interval { false } } - + override mustBeLessThan(Interval other) { switch (other) { case EMPTY: true @@ -178,7 +193,7 @@ abstract class Interval { default: throw new IllegalArgumentException("") } } - + override mayBeLessThan(Interval other) { if (other instanceof NonEmpty) { lower === null || other.upper === null || lower < other.upper @@ -186,7 +201,7 @@ abstract class Interval { false } } - + override join(Interval other) { switch (other) { case EMPTY: this @@ -245,6 +260,14 @@ abstract class Interval { } } + override operator_multiply(int count) { + val bigCount = new BigDecimal(count) + new NonEmpty( + lower.tryMultiply(bigCount, ROUND_DOWN), + upper.tryMultiply(bigCount, ROUND_UP) + ) + } + override operator_multiply(Interval other) { switch (other) { case EMPTY: EMPTY @@ -431,5 +454,38 @@ abstract class Interval { override toString() { '''«IF lower === null»(-∞«ELSE»[«lower»«ENDIF», «IF upper === null»∞)«ELSE»«upper»]«ENDIF»''' } + + override compareTo(Interval o) { + switch (o) { + case EMPTY: 1 + NonEmpty: compareTo(o) + default: throw new IllegalArgumentException("") + } + } + + def compareTo(NonEmpty o) { + if (lower === null) { + if (o.lower !== null) { + return -1 + } + } else if (o.lower === null) { // lower !== null + return 1 + } else { // both lower and o.lower are finite + val lowerDifference = lower.compareTo(o.lower) + if (lowerDifference != 0) { + return lowerDifference + } + } + if (upper === null) { + if (o.upper === null) { + return 0 + } else { + return 1 + } + } else if (o.upper === null) { // upper !== null + return -1 + } + upper.compareTo(o.upper) + } } } diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationMode.java b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationMode.java new file mode 100644 index 00000000..f5bd2efc --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationMode.java @@ -0,0 +1,66 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval; + +import java.util.function.BinaryOperator; + +public enum IntervalAggregationMode implements BinaryOperator { + SUM("intervalSum", "Sum a set of intervals") { + @Override + public IntervalRedBlackNode createNode(Interval interval) { + return new IntervalRedBlackNode(interval) { + public boolean isMultiplicitySensitive() { + return true; + } + + public Interval multiply(Interval interval, int count) { + return interval.operator_multiply(count); + }; + + @Override + public Interval op(Interval left, Interval right) { + return left.operator_plus(right); + } + }; + } + }, + + JOIN("intervalJoin", "Calculate the smallest interval containing all the intervals in a set") { + @Override + public IntervalRedBlackNode createNode(Interval interval) { + return new IntervalRedBlackNode(interval) { + @Override + public Interval op(Interval left, Interval right) { + return left.join(right); + } + }; + } + }; + + private final String modeName; + private final String description; + private final IntervalRedBlackNode empty; + + IntervalAggregationMode(String modeName, String description) { + this.modeName = modeName; + this.description = description; + empty = createNode(null); + } + + public String getModeName() { + return modeName; + } + + public String getDescription() { + return description; + } + + public IntervalRedBlackNode getEmpty() { + return empty; + } + + @Override + public Interval apply(Interval left, Interval right) { + return empty.op(left, right); + } + + public abstract IntervalRedBlackNode createNode(Interval interval); +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationOperator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationOperator.xtend new file mode 100644 index 00000000..940c71bb --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationOperator.xtend @@ -0,0 +1,48 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval + +import java.util.stream.Stream +import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator +import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor + +@FinalFieldsConstructor +class IntervalAggregationOperator implements IMultisetAggregationOperator { + @Accessors val IntervalAggregationMode mode + + override getName() { + mode.modeName + } + + override getShortDescription() { + mode.description + } + + override createNeutral() { + mode.empty + } + + override isNeutral(IntervalRedBlackNode result) { + result.leaf + } + + override update(IntervalRedBlackNode oldResult, Interval updateValue, boolean isInsertion) { + if (isInsertion) { + val newNode = mode.createNode(updateValue) + oldResult.add(newNode) + } else { + oldResult.remove(updateValue) + } + } + + override getAggregate(IntervalRedBlackNode result) { + if (result.leaf) { + null + } else { + result.result + } + } + + override aggregateStream(Stream stream) { + stream.reduce(mode).orElse(null) + } +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalRedBlackNode.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalRedBlackNode.xtend new file mode 100644 index 00000000..3aa575bc --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalRedBlackNode.xtend @@ -0,0 +1,177 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval + +abstract class IntervalRedBlackNode extends RedBlackNode { + public val Interval interval + public var int count = 1 + public var Interval result + + new(Interval interval) { + this.interval = interval + } + + def boolean isMultiplicitySensitive() { + false + } + + def Interval multiply(Interval interval, int count) { + interval + } + + abstract def Interval op(Interval left, Interval right) + + override augment() { + val value = calcualteAugmentation() + if (result == value) { + false + } else { + result = value + true + } + } + + private def calcualteAugmentation() { + var value = multiply(interval, count) + if (!left.leaf) { + value = op(value, left.result) + } + if (!right.leaf) { + value = op(value, right.result) + } + value + } + + override assertNodeIsValid() { + super.assertNodeIsValid() + if (leaf) { + return + } + if (count <= 0) { + throw new IllegalStateException("Node with nonpositive count") + } + val value = calcualteAugmentation() + if (result != value) { + throw new IllegalStateException("Node with invalid augmentation: " + result + " != " + value) + } + } + + override assertSubtreeIsValid() { + super.assertSubtreeIsValid() + assertNodeIsValid() + } + + override compareTo(IntervalRedBlackNode other) { + if (leaf || other.leaf) { + throw new IllegalArgumentException("One of the nodes is a leaf node") + } + interval.compareTo(other.interval) + } + + def add(IntervalRedBlackNode newNode) { + if (parent !== null) { + throw new IllegalArgumentException("This is not the root of a tree") + } + if (leaf) { + newNode.isRed = false + newNode.left = this + newNode.right = this + newNode.parent = null + newNode.augment + return newNode + } + val modifiedNode = addWithoutFixup(newNode) + if (modifiedNode === newNode) { + // Must augment here, because fixInsertion() might call augment() + // on a node repeatedly, which might lose the change notification the + // second time it is called, and the augmentation will fail to + // reach the root. + modifiedNode.augmentRecursively + modifiedNode.isRed = true + return modifiedNode.fixInsertion + } + if (multiplicitySensitive) { + modifiedNode.augmentRecursively + } + this + } + + private def addWithoutFixup(IntervalRedBlackNode newNode) { + var node = this + while (!node.leaf) { + val comparison = node.interval.compareTo(newNode.interval) + if (comparison < 0) { + if (node.left.leaf) { + newNode.left = node.left + newNode.right = node.left + node.left = newNode + newNode.parent = node + return newNode + } else { + node = node.left + } + } else if (comparison > 0) { + if (node.right.leaf) { + newNode.left = node.right + newNode.right = node.right + node.right = newNode + newNode.parent = node + return newNode + } else { + node = node.right + } + } else { // comparison == 0 + newNode.parent = null + node.count++ + return node + } + } + throw new IllegalStateException("Reached leaf node while searching for insertion point") + } + + private def augmentRecursively() { + for (var node = this; node !== null; node = node.parent) { + if (!node.augment) { + return + } + } + } + + def remove(Interval interval) { + val node = find(interval) + node.count-- + if (node.count == 0) { + return node.remove + } + if (multiplicitySensitive) { + node.augmentRecursively + } + this + } + + private def find(Interval interval) { + var node = this + while (!node.leaf) { + val comparison = node.interval.compareTo(interval) + if (comparison < 0) { + node = node.left + } else if (comparison > 0) { + node = node.right + } else { // comparison == 0 + return node + } + } + throw new IllegalStateException("Reached leaf node while searching for interval to remove") + } + + override toString() { + if (leaf) { + "L" + } else { + ''' + «IF isRed»R«ELSE»B«ENDIF» «count»«interval» : «result» + «left» + «right» + ''' + } + } + +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/RedBlackNode.java b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/RedBlackNode.java new file mode 100644 index 00000000..8c40816b --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/RedBlackNode.java @@ -0,0 +1,1392 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 btrekkie + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * A node in a red-black tree ( https://en.wikipedia.org/wiki/Red%E2%80%93black_tree ). Compared to a class like Java's + * TreeMap, RedBlackNode is a low-level data structure. The internals of a node are exposed as public fields, allowing + * clients to directly observe and manipulate the structure of the tree. This gives clients flexibility, although it + * also enables them to violate the red-black or BST properties. The RedBlackNode class provides methods for performing + * various standard operations, such as insertion and removal. + * + * Unlike most implementations of binary search trees, RedBlackNode supports arbitrary augmentation. By subclassing + * RedBlackNode, clients can add arbitrary data and augmentation information to each node. For example, if we were to + * use a RedBlackNode subclass to implement a sorted set, the subclass would have a field storing an element in the set. + * If we wanted to keep track of the number of non-leaf nodes in each subtree, we would store this as a "size" field and + * override augment() to update this field. All RedBlackNode methods (such as "insert" and remove()) call augment() as + * necessary to correctly maintain the augmentation information, unless otherwise indicated. + * + * The values of the tree are stored in the non-leaf nodes. RedBlackNode does not support use cases where values must be + * stored in the leaf nodes. It is recommended that all of the leaf nodes in a given tree be the same (black) + * RedBlackNode instance, to save space. The root of an empty tree is a leaf node, as opposed to null. + * + * For reference, a red-black tree is a binary search tree satisfying the following properties: + * + * - Every node is colored red or black. + * - The leaf nodes, which are dummy nodes that do not store any values, are colored black. + * - The root is black. + * - Both children of each red node are black. + * - Every path from the root to a leaf contains the same number of black nodes. + * + * @param The type of node in the tree. For example, we might have + * "class FooNode extends RedBlackNode>". + * @author Bill Jacobs + */ +public abstract class RedBlackNode> implements Comparable { + /** A Comparator that compares Comparable elements using their natural order. */ + private static final Comparator> NATURAL_ORDER = new Comparator>() { + @Override + public int compare(Comparable value1, Comparable value2) { + return value1.compareTo(value2); + } + }; + + /** The parent of this node, if any. "parent" is null if this is a leaf node. */ + public N parent; + + /** The left child of this node. "left" is null if this is a leaf node. */ + public N left; + + /** The right child of this node. "right" is null if this is a leaf node. */ + public N right; + + /** Whether the node is colored red, as opposed to black. */ + public boolean isRed; + + /** + * Sets any augmentation information about the subtree rooted at this node that is stored in this node. For + * example, if we augment each node by subtree size (the number of non-leaf nodes in the subtree), this method would + * set the size field of this node to be equal to the size field of the left child plus the size field of the right + * child plus one. + * + * "Augmentation information" is information that we can compute about a subtree rooted at some node, preferably + * based only on the augmentation information in the node's two children and the information in the node. Examples + * of augmentation information are the sum of the values in a subtree and the number of non-leaf nodes in a subtree. + * Augmentation information may not depend on the colors of the nodes. + * + * This method returns whether the augmentation information in any of the ancestors of this node might have been + * affected by changes in this subtree since the last call to augment(). In the usual case, where the augmentation + * information depends only on the information in this node and the augmentation information in its immediate + * children, this is equivalent to whether the augmentation information changed as a result of this call to + * augment(). For example, in the case of subtree size, this returns whether the value of the size field prior to + * calling augment() differed from the size field of the left child plus the size field of the right child plus one. + * False positives are permitted. The return value is unspecified if we have not called augment() on this node + * before. + * + * This method may assume that this is not a leaf node. It may not assume that the augmentation information stored + * in any of the tree's nodes is correct. However, if the augmentation information stored in all of the node's + * descendants is correct, then the augmentation information stored in this node must be correct after calling + * augment(). + */ + public boolean augment() { + return false; + } + + /** + * Throws a RuntimeException if we detect that this node locally violates any invariants specific to this subclass + * of RedBlackNode. For example, if this stores the size of the subtree rooted at this node, this should throw a + * RuntimeException if the size field of this is not equal to the size field of the left child plus the size field + * of the right child plus one. Note that we may call this on a leaf node. + * + * assertSubtreeIsValid() calls assertNodeIsValid() on each node, or at least starts to do so until it detects a + * problem. assertNodeIsValid() should assume the node is in a tree that satisfies all properties common to all + * red-black trees, as assertSubtreeIsValid() is responsible for such checks. assertNodeIsValid() should be + * "downward-looking", i.e. it should ignore any information in "parent", and it should be "local", i.e. it should + * only check a constant number of descendants. To include "global" checks, such as verifying the BST property + * concerning ordering, override assertSubtreeIsValid(). assertOrderIsValid is useful for checking the BST + * property. + */ + public void assertNodeIsValid() { + + } + + /** Returns whether this is a leaf node. */ + public boolean isLeaf() { + return left == null; + } + + /** Returns the root of the tree that contains this node. */ + public N root() { + @SuppressWarnings("unchecked") + N node = (N)this; + while (node.parent != null) { + node = node.parent; + } + return node; + } + + /** Returns the first node in the subtree rooted at this node, if any. */ + public N min() { + if (isLeaf()) { + return null; + } + @SuppressWarnings("unchecked") + N node = (N)this; + while (!node.left.isLeaf()) { + node = node.left; + } + return node; + } + + /** Returns the last node in the subtree rooted at this node, if any. */ + public N max() { + if (isLeaf()) { + return null; + } + @SuppressWarnings("unchecked") + N node = (N)this; + while (!node.right.isLeaf()) { + node = node.right; + } + return node; + } + + /** Returns the node immediately before this in the tree that contains this node, if any. */ + public N predecessor() { + if (!left.isLeaf()) { + N node; + for (node = left; !node.right.isLeaf(); node = node.right); + return node; + } else if (parent == null) { + return null; + } else { + @SuppressWarnings("unchecked") + N node = (N)this; + while (node.parent != null && node.parent.left == node) { + node = node.parent; + } + return node.parent; + } + } + + /** Returns the node immediately after this in the tree that contains this node, if any. */ + public N successor() { + if (!right.isLeaf()) { + N node; + for (node = right; !node.left.isLeaf(); node = node.left); + return node; + } else if (parent == null) { + return null; + } else { + @SuppressWarnings("unchecked") + N node = (N)this; + while (node.parent != null && node.parent.right == node) { + node = node.parent; + } + return node.parent; + } + } + + /** + * Performs a left rotation about this node. This method assumes that !isLeaf() && !right.isLeaf(). It calls + * augment() on this node and on its resulting parent. However, it does not call augment() on any of the resulting + * parent's ancestors, because that is normally the responsibility of the caller. + * @return The return value from calling augment() on the resulting parent. + */ + public boolean rotateLeft() { + if (isLeaf() || right.isLeaf()) { + throw new IllegalArgumentException("The node or its right child is a leaf"); + } + N newParent = right; + right = newParent.left; + @SuppressWarnings("unchecked") + N nThis = (N)this; + if (!right.isLeaf()) { + right.parent = nThis; + } + newParent.parent = parent; + parent = newParent; + newParent.left = nThis; + if (newParent.parent != null) { + if (newParent.parent.left == this) { + newParent.parent.left = newParent; + } else { + newParent.parent.right = newParent; + } + } + augment(); + return newParent.augment(); + } + + /** + * Performs a right rotation about this node. This method assumes that !isLeaf() && !left.isLeaf(). It calls + * augment() on this node and on its resulting parent. However, it does not call augment() on any of the resulting + * parent's ancestors, because that is normally the responsibility of the caller. + * @return The return value from calling augment() on the resulting parent. + */ + public boolean rotateRight() { + if (isLeaf() || left.isLeaf()) { + throw new IllegalArgumentException("The node or its left child is a leaf"); + } + N newParent = left; + left = newParent.right; + @SuppressWarnings("unchecked") + N nThis = (N)this; + if (!left.isLeaf()) { + left.parent = nThis; + } + newParent.parent = parent; + parent = newParent; + newParent.right = nThis; + if (newParent.parent != null) { + if (newParent.parent.left == this) { + newParent.parent.left = newParent; + } else { + newParent.parent.right = newParent; + } + } + augment(); + return newParent.augment(); + } + + /** + * Performs red-black insertion fixup. To be more precise, this fixes a tree that satisfies all of the requirements + * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be + * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs + * any rotations by calling rotateLeft() and rotateRight(). This method is more efficient than fixInsertion if + * "augment" is false or augment() might return false. + * @param augment Whether to set the augmentation information for "node" and its ancestors, by calling augment(). + */ + public void fixInsertionWithoutGettingRoot(boolean augment) { + if (!isRed) { + throw new IllegalArgumentException("The node must be red"); + } + boolean changed = augment; + if (augment) { + augment(); + } + + RedBlackNode node = this; + while (node.parent != null && node.parent.isRed) { + N parent = node.parent; + N grandparent = parent.parent; + if (grandparent.left.isRed && grandparent.right.isRed) { + grandparent.left.isRed = false; + grandparent.right.isRed = false; + grandparent.isRed = true; + + if (changed) { + changed = parent.augment(); + if (changed) { + changed = grandparent.augment(); + } + } + node = grandparent; + } else { + if (parent.left == node) { + if (grandparent.right == parent) { + parent.rotateRight(); + node = parent; + parent = node.parent; + } + } else if (grandparent.left == parent) { + parent.rotateLeft(); + node = parent; + parent = node.parent; + } + + if (parent.left == node) { + boolean grandparentChanged = grandparent.rotateRight(); + if (augment) { + changed = grandparentChanged; + } + } else { + boolean grandparentChanged = grandparent.rotateLeft(); + if (augment) { + changed = grandparentChanged; + } + } + + parent.isRed = false; + grandparent.isRed = true; + node = parent; + break; + } + } + + if (node.parent == null) { + node.isRed = false; + } + if (changed) { + for (node = node.parent; node != null; node = node.parent) { + if (!node.augment()) { + break; + } + } + } + } + + /** + * Performs red-black insertion fixup. To be more precise, this fixes a tree that satisfies all of the requirements + * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be + * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs + * any rotations by calling rotateLeft() and rotateRight(). This method is more efficient than fixInsertion() if + * augment() might return false. + */ + public void fixInsertionWithoutGettingRoot() { + fixInsertionWithoutGettingRoot(true); + } + + /** + * Performs red-black insertion fixup. To be more precise, this fixes a tree that satisfies all of the requirements + * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be + * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs + * any rotations by calling rotateLeft() and rotateRight(). + * @param augment Whether to set the augmentation information for "node" and its ancestors, by calling augment(). + * @return The root of the resulting tree. + */ + public N fixInsertion(boolean augment) { + fixInsertionWithoutGettingRoot(augment); + return root(); + } + + /** + * Performs red-black insertion fixup. To be more precise, this fixes a tree that satisfies all of the requirements + * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be + * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs + * any rotations by calling rotateLeft() and rotateRight(). + * @return The root of the resulting tree. + */ + public N fixInsertion() { + fixInsertionWithoutGettingRoot(true); + return root(); + } + + /** Returns a Comparator that compares instances of N using their natural order, as in N.compareTo. */ + @SuppressWarnings({"rawtypes", "unchecked"}) + private Comparator naturalOrder() { + Comparator comparator = (Comparator)NATURAL_ORDER; + return (Comparator)comparator; + } + + /** + * Inserts the specified node into the tree rooted at this node. Assumes this is the root. We treat newNode as a + * solitary node that does not belong to any tree, and we ignore its initial "parent", "left", "right", and isRed + * fields. + * + * If it is not efficient or convenient to find the location for a node using a Comparator, then you should manually + * add the node to the appropriate location, color it red, and call fixInsertion(). + * + * @param newNode The node to insert. + * @param allowDuplicates Whether to insert newNode if there is an equal node in the tree. To check whether we + * inserted newNode, check whether newNode.parent is null and the return value differs from newNode. + * @param comparator A comparator indicating where to put the node. If this is null, we use the nodes' natural + * order, as in N.compareTo. If you are passing null, then you must override the compareTo method, because the + * default implementation requires the nodes to already be in the same tree. + * @return The root of the resulting tree. + */ + public N insert(N newNode, boolean allowDuplicates, Comparator comparator) { + if (parent != null) { + throw new IllegalArgumentException("This is not the root of a tree"); + } + @SuppressWarnings("unchecked") + N nThis = (N)this; + if (isLeaf()) { + newNode.isRed = false; + newNode.left = nThis; + newNode.right = nThis; + newNode.parent = null; + newNode.augment(); + return newNode; + } + if (comparator == null) { + comparator = naturalOrder(); + } + + N node = nThis; + int comparison; + while (true) { + comparison = comparator.compare(newNode, node); + if (comparison < 0) { + if (!node.left.isLeaf()) { + node = node.left; + } else { + newNode.left = node.left; + newNode.right = node.left; + node.left = newNode; + newNode.parent = node; + break; + } + } else if (comparison > 0 || allowDuplicates) { + if (!node.right.isLeaf()) { + node = node.right; + } else { + newNode.left = node.right; + newNode.right = node.right; + node.right = newNode; + newNode.parent = node; + break; + } + } else { + newNode.parent = null; + return nThis; + } + } + newNode.isRed = true; + return newNode.fixInsertion(); + } + + /** + * Moves this node to its successor's former position in the tree and vice versa, i.e. sets the "left", "right", + * "parent", and isRed fields of each. This method assumes that this is not a leaf node. + * @return The node with which we swapped. + */ + private N swapWithSuccessor() { + N replacement = successor(); + boolean oldReplacementIsRed = replacement.isRed; + N oldReplacementLeft = replacement.left; + N oldReplacementRight = replacement.right; + N oldReplacementParent = replacement.parent; + + replacement.isRed = isRed; + replacement.left = left; + replacement.right = right; + replacement.parent = parent; + if (parent != null) { + if (parent.left == this) { + parent.left = replacement; + } else { + parent.right = replacement; + } + } + + @SuppressWarnings("unchecked") + N nThis = (N)this; + isRed = oldReplacementIsRed; + left = oldReplacementLeft; + right = oldReplacementRight; + if (oldReplacementParent == this) { + parent = replacement; + parent.right = nThis; + } else { + parent = oldReplacementParent; + parent.left = nThis; + } + + replacement.right.parent = replacement; + if (!replacement.left.isLeaf()) { + replacement.left.parent = replacement; + } + if (!right.isLeaf()) { + right.parent = nThis; + } + return replacement; + } + + /** + * Performs red-black deletion fixup. To be more precise, this fixes a tree that satisfies all of the requirements + * of red-black trees, except that all paths from the root to a leaf that pass through the sibling of this node have + * one fewer black node than all other root-to-leaf paths. This method assumes that this is not a leaf node. + */ + private void fixSiblingDeletion() { + RedBlackNode sibling = this; + boolean changed = true; + boolean haveAugmentedParent = false; + boolean haveAugmentedGrandparent = false; + while (true) { + N parent = sibling.parent; + if (sibling.isRed) { + parent.isRed = true; + sibling.isRed = false; + if (parent.left == sibling) { + changed = parent.rotateRight(); + sibling = parent.left; + } else { + changed = parent.rotateLeft(); + sibling = parent.right; + } + haveAugmentedParent = true; + haveAugmentedGrandparent = true; + } else if (!sibling.left.isRed && !sibling.right.isRed) { + sibling.isRed = true; + if (parent.isRed) { + parent.isRed = false; + break; + } else { + if (changed && !haveAugmentedParent) { + changed = parent.augment(); + } + N grandparent = parent.parent; + if (grandparent == null) { + break; + } else if (grandparent.left == parent) { + sibling = grandparent.right; + } else { + sibling = grandparent.left; + } + haveAugmentedParent = haveAugmentedGrandparent; + haveAugmentedGrandparent = false; + } + } else { + if (sibling == parent.left) { + if (!sibling.left.isRed) { + sibling.rotateLeft(); + sibling = sibling.parent; + } + } else if (!sibling.right.isRed) { + sibling.rotateRight(); + sibling = sibling.parent; + } + sibling.isRed = parent.isRed; + parent.isRed = false; + if (sibling == parent.left) { + sibling.left.isRed = false; + changed = parent.rotateRight(); + } else { + sibling.right.isRed = false; + changed = parent.rotateLeft(); + } + haveAugmentedParent = haveAugmentedGrandparent; + haveAugmentedGrandparent = false; + break; + } + } + + // Update augmentation info + N parent = sibling.parent; + if (changed && parent != null) { + if (!haveAugmentedParent) { + changed = parent.augment(); + } + if (changed && parent.parent != null) { + parent = parent.parent; + if (!haveAugmentedGrandparent) { + changed = parent.augment(); + } + if (changed) { + for (parent = parent.parent; parent != null; parent = parent.parent) { + if (!parent.augment()) { + break; + } + } + } + } + } + } + + /** + * Removes this node from the tree that contains it. The effect of this method on the fields of this node is + * unspecified. This method assumes that this is not a leaf node. This method is more efficient than remove() if + * augment() might return false. + * + * If the node has two children, we begin by moving the node's successor to its former position, by changing the + * successor's "left", "right", "parent", and isRed fields. + */ + public void removeWithoutGettingRoot() { + if (isLeaf()) { + throw new IllegalArgumentException("Attempted to remove a leaf node"); + } + N replacement; + if (left.isLeaf() || right.isLeaf()) { + replacement = null; + } else { + replacement = swapWithSuccessor(); + } + + N child; + if (!left.isLeaf()) { + child = left; + } else if (!right.isLeaf()) { + child = right; + } else { + child = null; + } + + if (child != null) { + // Replace this node with its child + child.parent = parent; + if (parent != null) { + if (parent.left == this) { + parent.left = child; + } else { + parent.right = child; + } + } + child.isRed = false; + + if (child.parent != null) { + N parent; + for (parent = child.parent; parent != null; parent = parent.parent) { + if (!parent.augment()) { + break; + } + } + } + } else if (parent != null) { + // Replace this node with a leaf node + N leaf = left; + N parent = this.parent; + N sibling; + if (parent.left == this) { + parent.left = leaf; + sibling = parent.right; + } else { + parent.right = leaf; + sibling = parent.left; + } + + if (!isRed) { + RedBlackNode siblingNode = sibling; + siblingNode.fixSiblingDeletion(); + } else { + while (parent != null) { + if (!parent.augment()) { + break; + } + parent = parent.parent; + } + } + } + + if (replacement != null) { + replacement.augment(); + for (N parent = replacement.parent; parent != null; parent = parent.parent) { + if (!parent.augment()) { + break; + } + } + } + + // Clear any previously existing links, so that we're more likely to encounter an exception if we attempt to + // access the removed node + parent = null; + left = null; + right = null; + isRed = true; + } + + /** + * Removes this node from the tree that contains it. The effect of this method on the fields of this node is + * unspecified. This method assumes that this is not a leaf node. + * + * If the node has two children, we begin by moving the node's successor to its former position, by changing the + * successor's "left", "right", "parent", and isRed fields. + * + * @return The root of the resulting tree. + */ + public N remove() { + if (isLeaf()) { + throw new IllegalArgumentException("Attempted to remove a leaf node"); + } + + // Find an arbitrary non-leaf node in the tree other than this node + N node; + if (parent != null) { + node = parent; + } else if (!left.isLeaf()) { + node = left; + } else if (!right.isLeaf()) { + node = right; + } else { + return left; + } + + removeWithoutGettingRoot(); + return node.root(); + } + + /** + * Returns the root of a perfectly height-balanced subtree containing the next "size" (non-leaf) nodes from + * "iterator", in iteration order. This method is responsible for setting the "left", "right", "parent", and isRed + * fields of the nodes, and calling augment() as appropriate. It ignores the initial values of the "left", "right", + * "parent", and isRed fields. + * @param iterator The nodes. + * @param size The number of nodes. + * @param height The "height" of the subtree's root node above the deepest leaf in the tree that contains it. Since + * insertion fixup is slow if there are too many red nodes and deleteion fixup is slow if there are too few red + * nodes, we compromise and have red nodes at every fourth level. We color a node red iff its "height" is equal + * to 1 mod 4. + * @param leaf The leaf node. + * @return The root of the subtree. + */ + private static > N createTree( + Iterator iterator, int size, int height, N leaf) { + if (size == 0) { + return leaf; + } else { + N left = createTree(iterator, (size - 1) / 2, height - 1, leaf); + N node = iterator.next(); + N right = createTree(iterator, size / 2, height - 1, leaf); + + node.isRed = height % 4 == 1; + node.left = left; + node.right = right; + if (!left.isLeaf()) { + left.parent = node; + } + if (!right.isLeaf()) { + right.parent = node; + } + + node.augment(); + return node; + } + } + + /** + * Returns the root of a perfectly height-balanced tree containing the specified nodes, in iteration order. This + * method is responsible for setting the "left", "right", "parent", and isRed fields of the nodes (excluding + * "leaf"), and calling augment() as appropriate. It ignores the initial values of the "left", "right", "parent", + * and isRed fields. + * @param nodes The nodes. + * @param leaf The leaf node. + * @return The root of the tree. + */ + public static > N createTree(Collection nodes, N leaf) { + int size = nodes.size(); + if (size == 0) { + return leaf; + } + + int height = 0; + for (int subtreeSize = size; subtreeSize > 0; subtreeSize /= 2) { + height++; + } + + N node = createTree(nodes.iterator(), size, height, leaf); + node.parent = null; + node.isRed = false; + return node; + } + + /** + * Concatenates to the end of the tree rooted at this node. To be precise, given that all of the nodes in this + * precede the node "pivot", which precedes all of the nodes in "last", this returns the root of a tree containing + * all of these nodes. This method destroys the trees rooted at "this" and "last". We treat "pivot" as a solitary + * node that does not belong to any tree, and we ignore its initial "parent", "left", "right", and isRed fields. + * This method assumes that this node and "last" are the roots of their respective trees. + * + * This method takes O(log N) time. It is more efficient than inserting "pivot" and then calling concatenate(last). + * It is considerably more efficient than inserting "pivot" and all of the nodes in "last". + */ + public N concatenate(N last, N pivot) { + // If the black height of "first", where first = this, is less than or equal to that of "last", starting at the + // root of "last", we keep going left until we reach a black node whose black height is equal to that of + // "first". Then, we make "pivot" the parent of that node and of "first", coloring it red, and perform + // insertion fixup on the pivot. If the black height of "first" is greater than that of "last", we do the + // mirror image of the above. + + if (parent != null) { + throw new IllegalArgumentException("This is not the root of a tree"); + } + if (last.parent != null) { + throw new IllegalArgumentException("\"last\" is not the root of a tree"); + } + + // Compute the black height of the trees + int firstBlackHeight = 0; + @SuppressWarnings("unchecked") + N first = (N)this; + for (N node = first; node != null; node = node.right) { + if (!node.isRed) { + firstBlackHeight++; + } + } + int lastBlackHeight = 0; + for (N node = last; node != null; node = node.right) { + if (!node.isRed) { + lastBlackHeight++; + } + } + + // Identify the children and parent of pivot + N firstChild = first; + N lastChild = last; + N parent; + if (firstBlackHeight <= lastBlackHeight) { + parent = null; + int blackHeight = lastBlackHeight; + while (blackHeight > firstBlackHeight) { + if (!lastChild.isRed) { + blackHeight--; + } + parent = lastChild; + lastChild = lastChild.left; + } + if (lastChild.isRed) { + parent = lastChild; + lastChild = lastChild.left; + } + } else { + parent = null; + int blackHeight = firstBlackHeight; + while (blackHeight > lastBlackHeight) { + if (!firstChild.isRed) { + blackHeight--; + } + parent = firstChild; + firstChild = firstChild.right; + } + if (firstChild.isRed) { + parent = firstChild; + firstChild = firstChild.right; + } + } + + // Add "pivot" to the tree + pivot.isRed = true; + pivot.parent = parent; + if (parent != null) { + if (firstBlackHeight < lastBlackHeight) { + parent.left = pivot; + } else { + parent.right = pivot; + } + } + pivot.left = firstChild; + if (!firstChild.isLeaf()) { + firstChild.parent = pivot; + } + pivot.right = lastChild; + if (!lastChild.isLeaf()) { + lastChild.parent = pivot; + } + + // Perform insertion fixup + return pivot.fixInsertion(); + } + + /** + * Concatenates the tree rooted at "last" to the end of the tree rooted at this node. To be precise, given that all + * of the nodes in this precede all of the nodes in "last", this returns the root of a tree containing all of these + * nodes. This method destroys the trees rooted at "this" and "last". It assumes that this node and "last" are the + * roots of their respective trees. This method takes O(log N) time. It is considerably more efficient than + * inserting all of the nodes in "last". + */ + public N concatenate(N last) { + if (parent != null || last.parent != null) { + throw new IllegalArgumentException("The node is not the root of a tree"); + } + if (isLeaf()) { + return last; + } else if (last.isLeaf()) { + @SuppressWarnings("unchecked") + N nThis = (N)this; + return nThis; + } else { + N node = last.min(); + last = node.remove(); + return concatenate(last, node); + } + } + + /** + * Splits the tree rooted at this node into two trees, so that the first element of the return value is the root of + * a tree consisting of the nodes that were before the specified node, and the second element of the return value is + * the root of a tree consisting of the nodes that were equal to or after the specified node. This method is + * destructive, meaning it does not preserve the original tree. It assumes that this node is the root and is in the + * same tree as splitNode. It takes O(log N) time. It is considerably more efficient than removing all of the + * nodes at or after splitNode and then creating a new tree from those nodes. + * @param The node at which to split the tree. + * @return An array consisting of the resulting trees. + */ + public N[] split(N splitNode) { + // To split the tree, we accumulate a pre-split tree and a post-split tree. We walk down the tree toward the + // position where we are splitting. Whenever we go left, we concatenate the right subtree with the post-split + // tree, and whenever we go right, we concatenate the pre-split tree with the left subtree. We use the + // concatenation algorithm described in concatenate(Object, Object). For the pivot, we use the last node where + // we went left in the case of a left move, and the last node where we went right in the case of a right move. + // + // The method uses the following variables: + // + // node: The current node in our walk down the tree. + // first: A node on the right spine of the pre-split tree. At the beginning of each iteration, it is the black + // node with the same black height as "node". If the pre-split tree is empty, this is null instead. + // firstParent: The parent of "first". If the pre-split tree is empty, this is null. Otherwise, this is the + // same as first.parent, unless first.isLeaf(). + // firstPivot: The node where we last went right, i.e. the next node to use as a pivot when concatenating with + // the pre-split tree. + // advanceFirst: Whether to set "first" to be its next black descendant at the end of the loop. + // last, lastParent, lastPivot, advanceLast: Analogous to "first", firstParent, firstPivot, and advanceFirst, + // but for the post-split tree. + if (parent != null) { + throw new IllegalArgumentException("This is not the root of a tree"); + } + if (isLeaf() || splitNode.isLeaf()) { + throw new IllegalArgumentException("The root or the split node is a leaf"); + } + + // Create an array containing the path from the root to splitNode + int depth = 1; + N parent; + for (parent = splitNode; parent.parent != null; parent = parent.parent) { + depth++; + } + if (parent != this) { + throw new IllegalArgumentException("The split node does not belong to this tree"); + } + RedBlackNode[] path = new RedBlackNode[depth]; + for (parent = splitNode; parent != null; parent = parent.parent) { + depth--; + path[depth] = parent; + } + + @SuppressWarnings("unchecked") + N node = (N)this; + N first = null; + N firstParent = null; + N last = null; + N lastParent = null; + N firstPivot = null; + N lastPivot = null; + while (!node.isLeaf()) { + boolean advanceFirst = !node.isRed && firstPivot != null; + boolean advanceLast = !node.isRed && lastPivot != null; + if ((depth + 1 < path.length && path[depth + 1] == node.left) || depth + 1 == path.length) { + // Left move + if (lastPivot == null) { + // The post-split tree is empty + last = node.right; + last.parent = null; + if (last.isRed) { + last.isRed = false; + lastParent = last; + last = last.left; + } + } else { + // Concatenate node.right and the post-split tree + if (node.right.isRed) { + node.right.isRed = false; + } else if (!node.isRed) { + lastParent = last; + last = last.left; + if (last.isRed) { + lastParent = last; + last = last.left; + } + advanceLast = false; + } + lastPivot.isRed = true; + lastPivot.parent = lastParent; + if (lastParent != null) { + lastParent.left = lastPivot; + } + lastPivot.left = node.right; + if (!lastPivot.left.isLeaf()) { + lastPivot.left.parent = lastPivot; + } + lastPivot.right = last; + if (!last.isLeaf()) { + last.parent = lastPivot; + } + last = lastPivot.left; + lastParent = lastPivot; + lastPivot.fixInsertionWithoutGettingRoot(false); + } + lastPivot = node; + node = node.left; + } else { + // Right move + if (firstPivot == null) { + // The pre-split tree is empty + first = node.left; + first.parent = null; + if (first.isRed) { + first.isRed = false; + firstParent = first; + first = first.right; + } + } else { + // Concatenate the post-split tree and node.left + if (node.left.isRed) { + node.left.isRed = false; + } else if (!node.isRed) { + firstParent = first; + first = first.right; + if (first.isRed) { + firstParent = first; + first = first.right; + } + advanceFirst = false; + } + firstPivot.isRed = true; + firstPivot.parent = firstParent; + if (firstParent != null) { + firstParent.right = firstPivot; + } + firstPivot.right = node.left; + if (!firstPivot.right.isLeaf()) { + firstPivot.right.parent = firstPivot; + } + firstPivot.left = first; + if (!first.isLeaf()) { + first.parent = firstPivot; + } + first = firstPivot.right; + firstParent = firstPivot; + firstPivot.fixInsertionWithoutGettingRoot(false); + } + firstPivot = node; + node = node.right; + } + + depth++; + + // Update "first" and "last" to be the nodes at the proper black height + if (advanceFirst) { + firstParent = first; + first = first.right; + if (first.isRed) { + firstParent = first; + first = first.right; + } + } + if (advanceLast) { + lastParent = last; + last = last.left; + if (last.isRed) { + lastParent = last; + last = last.left; + } + } + } + + // Add firstPivot to the pre-split tree + N leaf = node; + if (first == null) { + first = leaf; + } else { + firstPivot.isRed = true; + firstPivot.parent = firstParent; + if (firstParent != null) { + firstParent.right = firstPivot; + } + firstPivot.left = leaf; + firstPivot.right = leaf; + firstPivot.fixInsertionWithoutGettingRoot(false); + for (first = firstPivot; first.parent != null; first = first.parent) { + first.augment(); + } + first.augment(); + } + + // Add lastPivot to the post-split tree + lastPivot.isRed = true; + lastPivot.parent = lastParent; + if (lastParent != null) { + lastParent.left = lastPivot; + } + lastPivot.left = leaf; + lastPivot.right = leaf; + lastPivot.fixInsertionWithoutGettingRoot(false); + for (last = lastPivot; last.parent != null; last = last.parent) { + last.augment(); + } + last.augment(); + + @SuppressWarnings("unchecked") + N[] result = (N[])Array.newInstance(getClass(), 2); + result[0] = first; + result[1] = last; + return result; + } + + /** + * Returns the lowest common ancestor of this node and "other" - the node that is an ancestor of both and is not the + * parent of a node that is an ancestor of both. Assumes that this is in the same tree as "other". Assumes that + * neither "this" nor "other" is a leaf node. This method may return "this" or "other". + * + * Note that while it is possible to compute the lowest common ancestor in O(P) time, where P is the length of the + * path from this node to "other", the "lca" method is not guaranteed to take O(P) time. If your application + * requires this, then you should write your own lowest common ancestor method. + */ + public N lca(N other) { + if (isLeaf() || other.isLeaf()) { + throw new IllegalArgumentException("One of the nodes is a leaf node"); + } + + // Compute the depth of each node + int depth = 0; + for (N parent = this.parent; parent != null; parent = parent.parent) { + depth++; + } + int otherDepth = 0; + for (N parent = other.parent; parent != null; parent = parent.parent) { + otherDepth++; + } + + // Go up to nodes of the same depth + @SuppressWarnings("unchecked") + N parent = (N)this; + N otherParent = other; + if (depth <= otherDepth) { + for (int i = otherDepth; i > depth; i--) { + otherParent = otherParent.parent; + } + } else { + for (int i = depth; i > otherDepth; i--) { + parent = parent.parent; + } + } + + // Find the LCA + while (parent != otherParent) { + parent = parent.parent; + otherParent = otherParent.parent; + } + if (parent != null) { + return parent; + } else { + throw new IllegalArgumentException("The nodes do not belong to the same tree"); + } + } + + /** + * Returns an integer comparing the position of this node in the tree that contains it with that of "other". Returns + * a negative number if this is earlier, a positive number if this is later, and 0 if this is at the same position. + * Assumes that this is in the same tree as "other". Assumes that neither "this" nor "other" is a leaf node. + * + * The base class's implementation takes O(log N) time. If a RedBlackNode subclass stores a value used to order the + * nodes, then it could override compareTo to compare the nodes' values, which would take O(1) time. + * + * Note that while it is possible to compare the positions of two nodes in O(P) time, where P is the length of the + * path from this node to "other", the default implementation of compareTo is not guaranteed to take O(P) time. If + * your application requires this, then you should write your own comparison method. + */ + @Override + public int compareTo(N other) { + if (isLeaf() || other.isLeaf()) { + throw new IllegalArgumentException("One of the nodes is a leaf node"); + } + + // The algorithm operates as follows: compare the depth of this node to that of "other". If the depth of + // "other" is greater, keep moving up from "other" until we find the ancestor at the same depth. Then, keep + // moving up from "this" and from that node until we reach the lowest common ancestor. The node that arrived + // from the left child of the common ancestor is earlier. The algorithm is analogous if the depth of "other" is + // not greater. + if (this == other) { + return 0; + } + + // Compute the depth of each node + int depth = 0; + RedBlackNode parent; + for (parent = this; parent.parent != null; parent = parent.parent) { + depth++; + } + int otherDepth = 0; + N otherParent; + for (otherParent = other; otherParent.parent != null; otherParent = otherParent.parent) { + otherDepth++; + } + + // Go up to nodes of the same depth + if (depth < otherDepth) { + otherParent = other; + for (int i = otherDepth - 1; i > depth; i--) { + otherParent = otherParent.parent; + } + if (otherParent.parent != this) { + otherParent = otherParent.parent; + } else if (left == otherParent) { + return 1; + } else { + return -1; + } + parent = this; + } else if (depth > otherDepth) { + parent = this; + for (int i = depth - 1; i > otherDepth; i--) { + parent = parent.parent; + } + if (parent.parent != other) { + parent = parent.parent; + } else if (other.left == parent) { + return -1; + } else { + return 1; + } + otherParent = other; + } else { + parent = this; + otherParent = other; + } + + // Keep going up until we reach the lowest common ancestor + while (parent.parent != otherParent.parent) { + parent = parent.parent; + otherParent = otherParent.parent; + } + if (parent.parent == null) { + throw new IllegalArgumentException("The nodes do not belong to the same tree"); + } + if (parent.parent.left == parent) { + return -1; + } else { + return 1; + } + } + + /** Throws a RuntimeException if the RedBlackNode fields of this are not correct for a leaf node. */ + private void assertIsValidLeaf() { + if (left != null || right != null || parent != null || isRed) { + throw new RuntimeException("A leaf node's \"left\", \"right\", \"parent\", or isRed field is incorrect"); + } + } + + /** + * Throws a RuntimeException if the subtree rooted at this node does not satisfy the red-black properties, excluding + * the requirement that the root be black, or it contains a repeated node other than a leaf node. + * @param blackHeight The required number of black nodes in each path from this to a leaf node, including this and + * the leaf node. + * @param visited The nodes we have reached thus far, other than leaf nodes. This method adds the non-leaf nodes in + * the subtree rooted at this node to "visited". + */ + private void assertSubtreeIsValidRedBlack(int blackHeight, Set> visited) { + @SuppressWarnings("unchecked") + N nThis = (N)this; + if (left == null || right == null) { + assertIsValidLeaf(); + if (blackHeight != 1) { + throw new RuntimeException("Not all root-to-leaf paths have the same number of black nodes"); + } + return; + } else if (!visited.add(new Reference(nThis))) { + throw new RuntimeException("The tree contains a repeated non-leaf node"); + } else { + int childBlackHeight; + if (isRed) { + if ((!left.isLeaf() && left.isRed) || (!right.isLeaf() && right.isRed)) { + throw new RuntimeException("A red node has a red child"); + } + childBlackHeight = blackHeight; + } else if (blackHeight == 0) { + throw new RuntimeException("Not all root-to-leaf paths have the same number of black nodes"); + } else { + childBlackHeight = blackHeight - 1; + } + + if (!left.isLeaf() && left.parent != this) { + throw new RuntimeException("left.parent != this"); + } + if (!right.isLeaf() && right.parent != this) { + throw new RuntimeException("right.parent != this"); + } + RedBlackNode leftNode = left; + RedBlackNode rightNode = right; + leftNode.assertSubtreeIsValidRedBlack(childBlackHeight, visited); + rightNode.assertSubtreeIsValidRedBlack(childBlackHeight, visited); + } + } + + /** Calls assertNodeIsValid() on every node in the subtree rooted at this node. */ + private void assertNodesAreValid() { + assertNodeIsValid(); + if (left != null) { + RedBlackNode leftNode = left; + RedBlackNode rightNode = right; + leftNode.assertNodesAreValid(); + rightNode.assertNodesAreValid(); + } + } + + /** + * Throws a RuntimeException if the subtree rooted at this node is not a valid red-black tree, e.g. if a red node + * has a red child or it contains a non-leaf node "node" for which node.left.parent != node. (If parent != null, + * it's okay if isRed is true.) This method is useful for debugging. See also assertSubtreeIsValid(). + */ + public void assertSubtreeIsValidRedBlack() { + if (isLeaf()) { + assertIsValidLeaf(); + } else { + if (parent == null && isRed) { + throw new RuntimeException("The root is red"); + } + + // Compute the black height of the tree + Set> nodes = new HashSet>(); + int blackHeight = 0; + @SuppressWarnings("unchecked") + N node = (N)this; + while (node != null) { + if (!nodes.add(new Reference(node))) { + throw new RuntimeException("The tree contains a repeated non-leaf node"); + } + if (!node.isRed) { + blackHeight++; + } + node = node.left; + } + + assertSubtreeIsValidRedBlack(blackHeight, new HashSet>()); + } + } + + /** + * Throws a RuntimeException if we detect a problem with the subtree rooted at this node, such as a red child of a + * red node or a non-leaf descendant "node" for which node.left.parent != node. This method is useful for + * debugging. RedBlackNode subclasses may want to override assertSubtreeIsValid() to call assertOrderIsValid. + */ + public void assertSubtreeIsValid() { + assertSubtreeIsValidRedBlack(); + assertNodesAreValid(); + } + + /** + * Throws a RuntimeException if the nodes in the subtree rooted at this node are not in the specified order or they + * do not lie in the specified range. Assumes that the subtree rooted at this node is a valid binary tree, i.e. it + * has no repeated nodes other than leaf nodes. + * @param comparator A comparator indicating how the nodes should be ordered. + * @param start The lower limit for nodes in the subtree, if any. + * @param end The upper limit for nodes in the subtree, if any. + */ + private void assertOrderIsValid(Comparator comparator, N start, N end) { + if (!isLeaf()) { + @SuppressWarnings("unchecked") + N nThis = (N)this; + if (start != null && comparator.compare(nThis, start) < 0) { + throw new RuntimeException("The nodes are not ordered correctly"); + } + if (end != null && comparator.compare(nThis, end) > 0) { + throw new RuntimeException("The nodes are not ordered correctly"); + } + RedBlackNode leftNode = left; + RedBlackNode rightNode = right; + leftNode.assertOrderIsValid(comparator, start, nThis); + rightNode.assertOrderIsValid(comparator, nThis, end); + } + } + + /** + * Throws a RuntimeException if the nodes in the subtree rooted at this node are not in the specified order. + * Assumes that this is a valid binary tree, i.e. there are no repeated nodes other than leaf nodes. This method is + * useful for debugging. RedBlackNode subclasses may want to override assertSubtreeIsValid() to call + * assertOrderIsValid. + * @param comparator A comparator indicating how the nodes should be ordered. If this is null, we use the nodes' + * natural order, as in N.compareTo. + */ + public void assertOrderIsValid(Comparator comparator) { + if (comparator == null) { + comparator = naturalOrder(); + } + assertOrderIsValid(comparator, null, null); + } +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Reference.java b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Reference.java new file mode 100644 index 00000000..a25c167d --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Reference.java @@ -0,0 +1,51 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 btrekkie + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval; + +/** + * Wraps a value using reference equality. In other words, two references are equal only if their values are the same + * object instance, as in ==. + * @param The type of value. + */ +class Reference { + /** The value this wraps. */ + private final T value; + + public Reference(T value) { + this.value = value; + } + + public boolean equals(Object obj) { + if (!(obj instanceof Reference)) { + return false; + } + Reference reference = (Reference)obj; + return value == reference.value; + } + + @Override + public int hashCode() { + return System.identityHashCode(value); + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend new file mode 100644 index 00000000..cbd7e71f --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend @@ -0,0 +1,140 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval + +import com.google.common.collect.HashMultiset +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalAggregationMode +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalAggregationOperator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalRedBlackNode +import java.math.BigDecimal +import java.util.Random +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* + +class SumTest { + val aggregator = new IntervalAggregationOperator(IntervalAggregationMode.SUM) + var IntervalRedBlackNode value = null + + @Before + def void reset() { + value = aggregator.createNeutral + } + + @Test + def void emptyTest() { + assertEquals(null) + } + + @Test + def void addSingleTest() { + add(between(-1, 1)) + assertEquals(between(-1, 1)) + } + + @Test + def void addRemoveTest() { + add(between(-1, 1)) + remove(between(-1, 1)) + assertEquals(null) + } + + @Test + def void addTwoTest() { + add(between(-1, 1)) + add(above(2)) + assertEquals(above(1)) + } + + @Test + def void addTwoRemoveFirstTest() { + add(between(-1, 1)) + add(above(2)) + remove(between(-1, 1)) + assertEquals(above(2)) + } + + @Test + def void addTwoRemoveSecondTest() { + add(between(-1, 1)) + add(above(2)) + remove(above(2)) + assertEquals(between(-1, 1)) + } + + @Test + def void addMultiplicityTest() { + add(between(-1, 1)) + add(between(-1, 1)) + add(between(-1, 1)) + assertEquals(between(-3, 3)) + } + + @Test + def void removeAllMultiplicityTest() { + add(between(-1, 1)) + add(between(-1, 1)) + add(between(-1, 1)) + remove(between(-1, 1)) + remove(between(-1, 1)) + remove(between(-1, 1)) + assertEquals(null) + } + + @Test + def void removeSomeMultiplicityTest() { + add(between(-1, 1)) + add(between(-1, 1)) + add(between(-1, 1)) + remove(between(-1, 1)) + remove(between(-1, 1)) + assertEquals(between(-1, 1)) + } + + @Test + def void largeTest() { + val starts = #[null, new BigDecimal(-3), new BigDecimal(-2), new BigDecimal(-1)] + val ends = #[new BigDecimal(1), new BigDecimal(2), new BigDecimal(3), null] + val current = HashMultiset.create + val random = new Random(1) + for (var int i = 0; i < 1000; i++) { + val start = starts.get(random.nextInt(starts.size)) + val end = ends.get(random.nextInt(ends.size)) + val interval = Interval.of(start, end) + val isInsert = !current.contains(interval) || random.nextInt(3) == 0 + if (isInsert) { + current.add(interval) + } else { + current.remove(interval) + } + val expected = current.stream.reduce(aggregator.mode).orElse(null) + update(interval, isInsert) + assertEquals(expected) + } + } + + private def update(Interval interval, boolean isInsert) { + value = aggregator.update(value, interval, isInsert) + val nodes = newArrayList + var node = value.min + while (node !== null) { + nodes += node + node = node.successor + } + value.assertSubtreeIsValid + } + + private def add(Interval interval) { + update(interval, true) + } + + private def remove(Interval interval) { + update(interval, false) + } + + private def assertEquals(Interval interval) { + val actual = aggregator.getAggregate(value) + Assert.assertEquals(interval, actual) + } +} -- cgit v1.2.3-54-g00ecf From 5a55d0d306e85a697aa86bdf3f9caf243d384faa Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Fri, 10 May 2019 00:01:57 -0400 Subject: Neutral element for sum is [0, 0] --- .../logic2viatra/interval/IntervalAggregationMode.java | 9 +++++++++ .../logic2viatra/interval/IntervalAggregationOperator.xtend | 4 ++-- .../viatrasolver/logic2viatra/tests/interval/SumTest.xtend | 8 ++++---- 3 files changed, 15 insertions(+), 6 deletions(-) (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationMode.java b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationMode.java index f5bd2efc..66dcb00f 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationMode.java +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationMode.java @@ -21,6 +21,11 @@ public enum IntervalAggregationMode implements BinaryOperator { } }; } + + @Override + public Interval getNeutral() { + return Interval.ZERO; + } }, JOIN("intervalJoin", "Calculate the smallest interval containing all the intervals in a set") { @@ -63,4 +68,8 @@ public enum IntervalAggregationMode implements BinaryOperator { } public abstract IntervalRedBlackNode createNode(Interval interval); + + public Interval getNeutral() { + return Interval.EMPTY; + } } diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationOperator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationOperator.xtend index 940c71bb..21d3d73b 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationOperator.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/IntervalAggregationOperator.xtend @@ -36,13 +36,13 @@ class IntervalAggregationOperator implements IMultisetAggregationOperator stream) { - stream.reduce(mode).orElse(null) + stream.reduce(mode).orElse(mode.neutral) } } diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend index cbd7e71f..530c081c 100644 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend @@ -24,7 +24,7 @@ class SumTest { @Test def void emptyTest() { - assertEquals(null) + assertEquals(ZERO) } @Test @@ -37,7 +37,7 @@ class SumTest { def void addRemoveTest() { add(between(-1, 1)) remove(between(-1, 1)) - assertEquals(null) + assertEquals(ZERO) } @Test @@ -79,7 +79,7 @@ class SumTest { remove(between(-1, 1)) remove(between(-1, 1)) remove(between(-1, 1)) - assertEquals(null) + assertEquals(ZERO) } @Test @@ -108,7 +108,7 @@ class SumTest { } else { current.remove(interval) } - val expected = current.stream.reduce(aggregator.mode).orElse(null) + val expected = current.stream.reduce(aggregator.mode).orElse(ZERO) update(interval, isInsert) assertEquals(expected) } -- cgit v1.2.3-54-g00ecf From 9670538a0e5630edecab8aaf4ba38ae6c81e8606 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Fri, 10 May 2019 17:27:13 -0400 Subject: Interval power and aggregator fix --- .../logic2viatra/interval/Interval.xtend | 123 ++++++++++++------ .../tests/interval/MinAggregatorTest.xtend | 67 ++++++++++ .../logic2viatra/tests/interval/PowerTest.xtend | 43 +++++++ .../tests/interval/SumAggregatorTest.xtend | 140 +++++++++++++++++++++ .../logic2viatra/tests/interval/SumTest.xtend | 140 --------------------- 5 files changed, 337 insertions(+), 176 deletions(-) create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/MinAggregatorTest.xtend create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/PowerTest.xtend create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumAggregatorTest.xtend delete mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend index 173be0be..4f0f594f 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/interval/Interval.xtend @@ -54,26 +54,28 @@ abstract class Interval implements Comparable { } abstract def Interval min(Interval other) - + abstract def Interval max(Interval other) abstract def Interval join(Interval other) - def operator_plus() { + def +() { this } - abstract def Interval operator_minus() + abstract def Interval -() + + abstract def Interval +(Interval other) - abstract def Interval operator_plus(Interval other) + abstract def Interval -(Interval other) - abstract def Interval operator_minus(Interval other) + abstract def Interval *(int count) - abstract def Interval operator_multiply(int count) + abstract def Interval *(Interval other) - abstract def Interval operator_multiply(Interval other) + abstract def Interval /(Interval other) - abstract def Interval operator_divide(Interval other) + abstract def Interval **(Interval other) public static val EMPTY = new Interval { override mustEqual(Interval other) { @@ -95,7 +97,7 @@ abstract class Interval implements Comparable { override min(Interval other) { EMPTY } - + override max(Interval other) { EMPTY } @@ -104,27 +106,31 @@ abstract class Interval implements Comparable { other } - override operator_minus() { + override -() { EMPTY } - override operator_plus(Interval other) { + override +(Interval other) { EMPTY } - override operator_minus(Interval other) { + override -(Interval other) { EMPTY } - override operator_multiply(int count) { + override *(int count) { EMPTY } - override operator_multiply(Interval other) { + override *(Interval other) { EMPTY } - override operator_divide(Interval other) { + override /(Interval other) { + EMPTY + } + + override **(Interval other) { EMPTY } @@ -221,14 +227,14 @@ abstract class Interval implements Comparable { default: throw new IllegalArgumentException("Unknown interval: " + other) } } - + def min(NonEmpty other) { new NonEmpty( lower.tryMin(other.lower), - if (other.upper === null) upper else upper?.min(other.upper) + if(other.upper === null) upper else if(upper === null) other.upper else upper.min(other.upper) ) } - + override max(Interval other) { switch (other) { case EMPTY: this @@ -236,10 +242,10 @@ abstract class Interval implements Comparable { default: throw new IllegalArgumentException("Unknown interval: " + other) } } - + def max(NonEmpty other) { new NonEmpty( - if (other.lower === null) lower else lower?.min(other.lower), + if(other.lower === null) lower else if(lower === null) other.lower else lower.max(other.lower), upper.tryMax(other.upper) ) } @@ -252,19 +258,19 @@ abstract class Interval implements Comparable { } } - override operator_minus() { + override -() { new NonEmpty(upper?.negate(ROUND_DOWN), lower?.negate(ROUND_UP)) } - override operator_plus(Interval other) { + override +(Interval other) { switch (other) { case EMPTY: EMPTY - NonEmpty: operator_plus(other) + NonEmpty: this + other default: throw new IllegalArgumentException("Unknown interval: " + other) } } - def operator_plus(NonEmpty other) { + def +(NonEmpty other) { new NonEmpty( lower.tryAdd(other.lower, ROUND_DOWN), upper.tryAdd(other.upper, ROUND_UP) @@ -279,15 +285,15 @@ abstract class Interval implements Comparable { } } - override operator_minus(Interval other) { + override -(Interval other) { switch (other) { case EMPTY: EMPTY - NonEmpty: operator_minus(other) + NonEmpty: this - other default: throw new IllegalArgumentException("Unknown interval: " + other) } } - def operator_minus(NonEmpty other) { + def -(NonEmpty other) { new NonEmpty( lower.trySubtract(other.upper, ROUND_DOWN), upper.trySubtract(other.lower, ROUND_UP) @@ -302,7 +308,7 @@ abstract class Interval implements Comparable { } } - override operator_multiply(int count) { + override *(int count) { val bigCount = new BigDecimal(count) new NonEmpty( lower.tryMultiply(bigCount, ROUND_DOWN), @@ -310,15 +316,15 @@ abstract class Interval implements Comparable { ) } - override operator_multiply(Interval other) { + override *(Interval other) { switch (other) { case EMPTY: EMPTY - NonEmpty: operator_multiply(other) - default: throw new IllegalArgumentException("") + NonEmpty: this * other + default: throw new IllegalArgumentException("Unknown interval: " + other) } } - def operator_multiply(NonEmpty other) { + def *(NonEmpty other) { if (this == ZERO || other == ZERO) { ZERO } else if (nonpositive) { @@ -407,15 +413,15 @@ abstract class Interval implements Comparable { } } - override operator_divide(Interval other) { + override /(Interval other) { switch (other) { case EMPTY: EMPTY - NonEmpty: operator_divide(other) + NonEmpty: this / other default: throw new IllegalArgumentException("Unknown interval: " + other) } } - def operator_divide(NonEmpty other) { + def /(NonEmpty other) { if (other == ZERO) { EMPTY } else if (this == ZERO) { @@ -493,6 +499,51 @@ abstract class Interval implements Comparable { } } + override **(Interval other) { + switch (other) { + case EMPTY: EMPTY + NonEmpty: this ** other + default: throw new IllegalArgumentException("Unknown interval: " + other) + } + } + + def **(NonEmpty other) { + // XXX This should use proper rounding for log and exp instead of + // converting to double. + // XXX We should not ignore (integer) powers of negative numbers. + val lowerLog = if (lower === null || lower <= BigDecimal.ZERO) { + null + } else { + new BigDecimal(Math.log(lower.doubleValue), ROUND_DOWN) + } + val upperLog = if (upper === null) { + null + } else if (upper == BigDecimal.ZERO) { + return ZERO + } else if (upper < BigDecimal.ZERO) { + return EMPTY + } else { + new BigDecimal(Math.log(upper.doubleValue), ROUND_UP) + } + val log = new NonEmpty(lowerLog, upperLog) + val product = log * other + if (product instanceof NonEmpty) { + val lowerResult = if (product.lower === null) { + BigDecimal.ZERO + } else { + new BigDecimal(Math.exp(product.lower.doubleValue), ROUND_DOWN) + } + val upperResult = if (product.upper === null) { + null + } else { + new BigDecimal(Math.exp(product.upper.doubleValue), ROUND_UP) + } + new NonEmpty(lowerResult, upperResult) + } else { + throw new IllegalArgumentException("Unknown interval: " + product) + } + } + override toString() { '''«IF lower === null»(-∞«ELSE»[«lower»«ENDIF», «IF upper === null»∞)«ELSE»«upper»]«ENDIF»''' } @@ -501,7 +552,7 @@ abstract class Interval implements Comparable { switch (o) { case EMPTY: 1 NonEmpty: compareTo(o) - default: throw new IllegalArgumentException("") + default: throw new IllegalArgumentException("Unknown interval: " + o) } } diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/MinAggregatorTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/MinAggregatorTest.xtend new file mode 100644 index 00000000..7d46e16c --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/MinAggregatorTest.xtend @@ -0,0 +1,67 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval + +import com.google.common.collect.HashMultiset +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalAggregationMode +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalAggregationOperator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalRedBlackNode +import java.math.BigDecimal +import java.util.Random +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* + +class MinAggregatorTest { + val aggregator = new IntervalAggregationOperator(IntervalAggregationMode.MIN) + var IntervalRedBlackNode value = null + + @Before + def void reset() { + value = aggregator.createNeutral + } + + @Test + def void emptyTest() { + assertEquals(EMPTY) + } + + @Test + def void largeTest() { + val starts = #[null, new BigDecimal(-3), new BigDecimal(-2), new BigDecimal(-1)] + val ends = #[new BigDecimal(1), new BigDecimal(2), new BigDecimal(3), null] + val current = HashMultiset.create + val random = new Random(1) + for (var int i = 0; i < 1000; i++) { + val start = starts.get(random.nextInt(starts.size)) + val end = ends.get(random.nextInt(ends.size)) + val interval = Interval.of(start, end) + val isInsert = !current.contains(interval) || random.nextInt(3) == 0 + if (isInsert) { + current.add(interval) + } else { + current.remove(interval) + } + val expected = current.stream.reduce(aggregator.mode).orElse(EMPTY) + update(interval, isInsert) + assertEquals(expected) + } + } + + private def update(Interval interval, boolean isInsert) { + value = aggregator.update(value, interval, isInsert) + val nodes = newArrayList + var node = value.min + while (node !== null) { + nodes += node + node = node.successor + } + value.assertSubtreeIsValid + } + + private def assertEquals(Interval interval) { + val actual = aggregator.getAggregate(value) + Assert.assertEquals(interval, actual) + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/PowerTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/PowerTest.xtend new file mode 100644 index 00000000..c842d90d --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/PowerTest.xtend @@ -0,0 +1,43 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval + +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval +import java.util.Collection +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.junit.runners.Parameterized.Parameters + +import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* + +@RunWith(Parameterized) +class PowerTest { + @Parameters(name="{index}: {0} ** {1} = {2}") + static def Collection data() { + #[ + #[EMPTY, EMPTY, EMPTY], + #[EMPTY, between(-1, 1), EMPTY], + #[between(-1, 1), EMPTY, EMPTY], + #[upTo(-1), between(-1, 2), EMPTY], + #[upTo(0), between(-1, 2), between(0, 0)], + #[upTo(2), between(-1, 2), above(0)], + #[upTo(2), between(1, 2), between(0, 4)], + #[above(1), between(1, 2), above(1)], + #[between(2, 4), upTo(1), between(0, 4)], + #[between(0.25, 0.5), upTo(1), above(0.25)], + #[between(2, 3), above(1), above(2)], + #[between(0.25, 0.5), above(1), between(0, 0.5)], + #[between(1, 2), between(-1, 2), between(0.5, 4)] + ] + } + + @Parameter(0) public var Interval a + @Parameter(1) public var Interval b + @Parameter(2) public var Interval result + + @Test + def void powerTest() { + Assert.assertEquals(result, a ** b) + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumAggregatorTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumAggregatorTest.xtend new file mode 100644 index 00000000..56172b6c --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumAggregatorTest.xtend @@ -0,0 +1,140 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval + +import com.google.common.collect.HashMultiset +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalAggregationMode +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalAggregationOperator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalRedBlackNode +import java.math.BigDecimal +import java.util.Random +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* + +class SumAggregatorTest { + val aggregator = new IntervalAggregationOperator(IntervalAggregationMode.SUM) + var IntervalRedBlackNode value = null + + @Before + def void reset() { + value = aggregator.createNeutral + } + + @Test + def void emptyTest() { + assertEquals(ZERO) + } + + @Test + def void addSingleTest() { + add(between(-1, 1)) + assertEquals(between(-1, 1)) + } + + @Test + def void addRemoveTest() { + add(between(-1, 1)) + remove(between(-1, 1)) + assertEquals(ZERO) + } + + @Test + def void addTwoTest() { + add(between(-1, 1)) + add(above(2)) + assertEquals(above(1)) + } + + @Test + def void addTwoRemoveFirstTest() { + add(between(-1, 1)) + add(above(2)) + remove(between(-1, 1)) + assertEquals(above(2)) + } + + @Test + def void addTwoRemoveSecondTest() { + add(between(-1, 1)) + add(above(2)) + remove(above(2)) + assertEquals(between(-1, 1)) + } + + @Test + def void addMultiplicityTest() { + add(between(-1, 1)) + add(between(-1, 1)) + add(between(-1, 1)) + assertEquals(between(-3, 3)) + } + + @Test + def void removeAllMultiplicityTest() { + add(between(-1, 1)) + add(between(-1, 1)) + add(between(-1, 1)) + remove(between(-1, 1)) + remove(between(-1, 1)) + remove(between(-1, 1)) + assertEquals(ZERO) + } + + @Test + def void removeSomeMultiplicityTest() { + add(between(-1, 1)) + add(between(-1, 1)) + add(between(-1, 1)) + remove(between(-1, 1)) + remove(between(-1, 1)) + assertEquals(between(-1, 1)) + } + + @Test + def void largeTest() { + val starts = #[null, new BigDecimal(-3), new BigDecimal(-2), new BigDecimal(-1)] + val ends = #[new BigDecimal(1), new BigDecimal(2), new BigDecimal(3), null] + val current = HashMultiset.create + val random = new Random(1) + for (var int i = 0; i < 1000; i++) { + val start = starts.get(random.nextInt(starts.size)) + val end = ends.get(random.nextInt(ends.size)) + val interval = Interval.of(start, end) + val isInsert = !current.contains(interval) || random.nextInt(3) == 0 + if (isInsert) { + current.add(interval) + } else { + current.remove(interval) + } + val expected = current.stream.reduce(aggregator.mode).orElse(ZERO) + update(interval, isInsert) + assertEquals(expected) + } + } + + private def update(Interval interval, boolean isInsert) { + value = aggregator.update(value, interval, isInsert) + val nodes = newArrayList + var node = value.min + while (node !== null) { + nodes += node + node = node.successor + } + value.assertSubtreeIsValid + } + + private def add(Interval interval) { + update(interval, true) + } + + private def remove(Interval interval) { + update(interval, false) + } + + private def assertEquals(Interval interval) { + val actual = aggregator.getAggregate(value) + Assert.assertEquals(interval, actual) + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend deleted file mode 100644 index 530c081c..00000000 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/interval/SumTest.xtend +++ /dev/null @@ -1,140 +0,0 @@ -package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval - -import com.google.common.collect.HashMultiset -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalAggregationMode -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalAggregationOperator -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.IntervalRedBlackNode -import java.math.BigDecimal -import java.util.Random -import org.junit.Assert -import org.junit.Before -import org.junit.Test - -import static hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.Interval.* - -class SumTest { - val aggregator = new IntervalAggregationOperator(IntervalAggregationMode.SUM) - var IntervalRedBlackNode value = null - - @Before - def void reset() { - value = aggregator.createNeutral - } - - @Test - def void emptyTest() { - assertEquals(ZERO) - } - - @Test - def void addSingleTest() { - add(between(-1, 1)) - assertEquals(between(-1, 1)) - } - - @Test - def void addRemoveTest() { - add(between(-1, 1)) - remove(between(-1, 1)) - assertEquals(ZERO) - } - - @Test - def void addTwoTest() { - add(between(-1, 1)) - add(above(2)) - assertEquals(above(1)) - } - - @Test - def void addTwoRemoveFirstTest() { - add(between(-1, 1)) - add(above(2)) - remove(between(-1, 1)) - assertEquals(above(2)) - } - - @Test - def void addTwoRemoveSecondTest() { - add(between(-1, 1)) - add(above(2)) - remove(above(2)) - assertEquals(between(-1, 1)) - } - - @Test - def void addMultiplicityTest() { - add(between(-1, 1)) - add(between(-1, 1)) - add(between(-1, 1)) - assertEquals(between(-3, 3)) - } - - @Test - def void removeAllMultiplicityTest() { - add(between(-1, 1)) - add(between(-1, 1)) - add(between(-1, 1)) - remove(between(-1, 1)) - remove(between(-1, 1)) - remove(between(-1, 1)) - assertEquals(ZERO) - } - - @Test - def void removeSomeMultiplicityTest() { - add(between(-1, 1)) - add(between(-1, 1)) - add(between(-1, 1)) - remove(between(-1, 1)) - remove(between(-1, 1)) - assertEquals(between(-1, 1)) - } - - @Test - def void largeTest() { - val starts = #[null, new BigDecimal(-3), new BigDecimal(-2), new BigDecimal(-1)] - val ends = #[new BigDecimal(1), new BigDecimal(2), new BigDecimal(3), null] - val current = HashMultiset.create - val random = new Random(1) - for (var int i = 0; i < 1000; i++) { - val start = starts.get(random.nextInt(starts.size)) - val end = ends.get(random.nextInt(ends.size)) - val interval = Interval.of(start, end) - val isInsert = !current.contains(interval) || random.nextInt(3) == 0 - if (isInsert) { - current.add(interval) - } else { - current.remove(interval) - } - val expected = current.stream.reduce(aggregator.mode).orElse(ZERO) - update(interval, isInsert) - assertEquals(expected) - } - } - - private def update(Interval interval, boolean isInsert) { - value = aggregator.update(value, interval, isInsert) - val nodes = newArrayList - var node = value.min - while (node !== null) { - nodes += node - node = node.successor - } - value.assertSubtreeIsValid - } - - private def add(Interval interval) { - update(interval, true) - } - - private def remove(Interval interval) { - update(interval, false) - } - - private def assertEquals(Interval interval) { - val actual = aggregator.getAggregate(value) - Assert.assertEquals(interval, actual) - } -} -- cgit v1.2.3-54-g00ecf From fc505b6b171a2d54c3bad6078031b028b55131d3 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sun, 14 Jul 2019 00:56:19 +0200 Subject: Polyhedron abstraction with Z3 for cardinality propagation --- Solvers/SMT-Solver/com.microsoft.z3/.classpath | 15 ++ Solvers/SMT-Solver/com.microsoft.z3/.gitignore | 1 + Solvers/SMT-Solver/com.microsoft.z3/.project | 28 +++ .../com.microsoft.z3/META-INF/MANIFEST.MF | 22 +++ .../SMT-Solver/com.microsoft.z3/build.properties | 3 + Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.dll | Bin 0 -> 13731840 bytes .../SMT-Solver/com.microsoft.z3/lib/libz3.dylib | Bin 0 -> 20880828 bytes Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.so | Bin 0 -> 23841920 bytes .../SMT-Solver/com.microsoft.z3/lib/libz3java.dll | Bin 0 -> 97280 bytes .../com.microsoft.z3/lib/libz3java.dylib | Bin 0 -> 165680 bytes .../SMT-Solver/com.microsoft.z3/lib/libz3java.so | Bin 0 -> 270824 bytes .../META-INF/MANIFEST.MF | 4 +- .../ModelGenerationMethodProvider.xtend | 24 ++- .../logic2viatra/ScopePropagator.xtend | 156 ---------------- .../cardinality/PolyhedronScopePropagator.xtend | 153 +++++++++++++++ .../cardinality/PolyhedronSolver.xtend | 115 ++++++++++++ .../logic2viatra/cardinality/ScopePropagator.xtend | 149 +++++++++++++++ .../cardinality/Z3PolyhedronSolver.xtend | 206 +++++++++++++++++++++ .../rules/RefinementRuleProvider.xtend | 2 +- .../viatrasolver/reasoner/ViatraReasoner.xtend | 7 +- .../reasoner/ViatraReasonerConfiguration.xtend | 3 + .../META-INF/MANIFEST.MF | 2 + .../tests/cardinality/Z3PolyhedronSolverTest.xtend | 199 ++++++++++++++++++++ 23 files changed, 925 insertions(+), 164 deletions(-) create mode 100644 Solvers/SMT-Solver/com.microsoft.z3/.classpath create mode 100644 Solvers/SMT-Solver/com.microsoft.z3/.gitignore create mode 100644 Solvers/SMT-Solver/com.microsoft.z3/.project create mode 100644 Solvers/SMT-Solver/com.microsoft.z3/META-INF/MANIFEST.MF create mode 100644 Solvers/SMT-Solver/com.microsoft.z3/build.properties create mode 100644 Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.dll create mode 100755 Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.dylib create mode 100755 Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.so create mode 100644 Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.dll create mode 100755 Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.dylib create mode 100755 Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.so delete mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ScopePropagator.xtend create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronSolver.xtend create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagator.xtend create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/Z3PolyhedronSolver.xtend create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Solvers/SMT-Solver/com.microsoft.z3/.classpath b/Solvers/SMT-Solver/com.microsoft.z3/.classpath new file mode 100644 index 00000000..ffdc022a --- /dev/null +++ b/Solvers/SMT-Solver/com.microsoft.z3/.classpath @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/Solvers/SMT-Solver/com.microsoft.z3/.gitignore b/Solvers/SMT-Solver/com.microsoft.z3/.gitignore new file mode 100644 index 00000000..ae3c1726 --- /dev/null +++ b/Solvers/SMT-Solver/com.microsoft.z3/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/Solvers/SMT-Solver/com.microsoft.z3/.project b/Solvers/SMT-Solver/com.microsoft.z3/.project new file mode 100644 index 00000000..ec5bbc58 --- /dev/null +++ b/Solvers/SMT-Solver/com.microsoft.z3/.project @@ -0,0 +1,28 @@ + + + com.microsoft.z3 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/Solvers/SMT-Solver/com.microsoft.z3/META-INF/MANIFEST.MF b/Solvers/SMT-Solver/com.microsoft.z3/META-INF/MANIFEST.MF new file mode 100644 index 00000000..01faa2ad --- /dev/null +++ b/Solvers/SMT-Solver/com.microsoft.z3/META-INF/MANIFEST.MF @@ -0,0 +1,22 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Z3 +Bundle-SymbolicName: com.microsoft.z3 +Bundle-Version: 4.8.5.qualifier +Bundle-Vendor: Microsoft +Automatic-Module-Name: com.microsoft.z3 +Bundle-ClassPath: com.microsoft.z3.jar +Bundle-NativeCode: lib/libz3.so; + lib/libz3java.so; + osname=Linux; + processor=x86_64; + lib/libz3.dylib; + lib/libz3java.dylib; + osname=MacOSX; + processor=x86_64, + lib/libz3.dll; + lib/libz3java.dll; + osname=win32; + processor=x86_64 +Export-Package: com.microsoft.z3, + com.microsoft.z3.enumerations diff --git a/Solvers/SMT-Solver/com.microsoft.z3/build.properties b/Solvers/SMT-Solver/com.microsoft.z3/build.properties new file mode 100644 index 00000000..87f4e73f --- /dev/null +++ b/Solvers/SMT-Solver/com.microsoft.z3/build.properties @@ -0,0 +1,3 @@ +bin.includes = META-INF/,\ + lib/,\ + com.microsoft.z3.jar diff --git a/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.dll b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.dll new file mode 100644 index 00000000..0b518988 Binary files /dev/null and b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.dll differ diff --git a/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.dylib b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.dylib new file mode 100755 index 00000000..7884a0e7 Binary files /dev/null and b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.dylib differ diff --git a/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.so b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.so new file mode 100755 index 00000000..5beffe36 Binary files /dev/null and b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3.so differ diff --git a/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.dll b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.dll new file mode 100644 index 00000000..5e8dbc9b Binary files /dev/null and b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.dll differ diff --git a/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.dylib b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.dylib new file mode 100755 index 00000000..2d9116ac Binary files /dev/null and b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.dylib differ diff --git a/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.so b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.so new file mode 100755 index 00000000..e2d2618d Binary files /dev/null and b/Solvers/SMT-Solver/com.microsoft.z3/lib/libz3java.so differ diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/META-INF/MANIFEST.MF b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/META-INF/MANIFEST.MF index b2ee3981..37495e50 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/META-INF/MANIFEST.MF +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/META-INF/MANIFEST.MF @@ -4,6 +4,7 @@ Bundle-Name: Logic2viatra Bundle-SymbolicName: hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatraquery;singleton:=true Bundle-Version: 1.0.0.qualifier Export-Package: hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra, + hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality, hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval, hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.interval.aggregators, hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns, @@ -22,7 +23,8 @@ Require-Bundle: hu.bme.mit.inf.dslreasoner.logic.model;bundle-version="1.0.0", com.google.inject;bundle-version="3.0.0", org.eclipse.xtext;bundle-version="2.10.0", org.eclipse.viatra.transformation.runtime.emf;bundle-version="1.5.0", - org.eclipse.xtext.xbase;bundle-version="2.10.0" + org.eclipse.xtext.xbase;bundle-version="2.10.0", + com.microsoft.z3;bundle-version="4.8.5" Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: org.apache.log4j Automatic-Module-Name: hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatraquery diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend index b6918294..0040dbcd 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend @@ -4,6 +4,11 @@ import com.google.common.collect.ImmutableMap import hu.bme.mit.inf.dslreasoner.logic.model.builder.DocumentationLevel import hu.bme.mit.inf.dslreasoner.logic.model.logicproblem.LogicProblem import hu.bme.mit.inf.dslreasoner.viatra2logic.viatra2logicannotations.TransfomedViatraQuery +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronScopePropagator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagatorStrategy +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Z3PolyhedronSolver +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.GeneratedPatterns import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.ModalPatternQueries import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.PatternProvider import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.rules.GoalConstraintProvider @@ -63,7 +68,7 @@ class ModelGenerationMethodProvider { ReasonerWorkspace workspace, boolean nameNewElements, TypeInferenceMethod typeInferenceMethod, - ScopePropagator scopePropagator, + ScopePropagatorStrategy scopePropagatorStrategy, DocumentationLevel debugLevel ) { val statistics = new ModelGenerationStatistics @@ -74,6 +79,8 @@ class ModelGenerationMethodProvider { val queries = patternProvider.generateQueries(logicProblem, emptySolution, statistics, existingQueries, workspace, typeInferenceMethod, writeFiles) + val scopePropagator = createScopePropagator(scopePropagatorStrategy, emptySolution, queries) + scopePropagator.propagateAllScopeConstraints val // LinkedHashMap, BatchTransformationRule>> objectRefinementRules = refinementRuleProvider.createObjectRefinementRules(queries, scopePropagator, nameNewElements, statistics) @@ -104,4 +111,19 @@ class ModelGenerationMethodProvider { queries.allQueries ) } + + private def createScopePropagator(ScopePropagatorStrategy scopePropagatorStrategy, + PartialInterpretation emptySolution, GeneratedPatterns queries) { + switch (scopePropagatorStrategy) { + case BasicTypeHierarchy: + new ScopePropagator(emptySolution) + case PolyhedralTypeHierarchy: { + val types = queries.refineObjectQueries.keySet.map[newType].toSet + val solver = new Z3PolyhedronSolver + new PolyhedronScopePropagator(emptySolution, types, solver) + } + default: + throw new IllegalArgumentException("Unknown scope propagator strategy: " + scopePropagatorStrategy) + } + } } diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ScopePropagator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ScopePropagator.xtend deleted file mode 100644 index 38633c07..00000000 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ScopePropagator.xtend +++ /dev/null @@ -1,156 +0,0 @@ -package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra - -import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialInterpretation -import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialTypeInterpratation -import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.Scope -import java.util.HashMap -import java.util.Map -import java.util.Set -import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialComplexTypeInterpretation -import java.util.HashSet - -class ScopePropagator { - PartialInterpretation partialInterpretation - Map type2Scope - - val Map> superScopes - val Map> subScopes - - public new(PartialInterpretation p) { - partialInterpretation = p - type2Scope = new HashMap - for(scope : p.scopes) { - type2Scope.put(scope.targetTypeInterpretation,scope) - } - - superScopes = new HashMap - subScopes = new HashMap - for(scope : p.scopes) { - superScopes.put(scope,new HashSet) - subScopes.put(scope,new HashSet) - } - - for(scope : p.scopes) { - val target = scope.targetTypeInterpretation - if(target instanceof PartialComplexTypeInterpretation) { - val supertypeInterpretations = target.supertypeInterpretation - for(supertypeInterpretation : supertypeInterpretations) { - val supertypeScope = type2Scope.get(supertypeInterpretation) - superScopes.get(scope).add(supertypeScope) - subScopes.get(supertypeScope).add(scope) - } - } - } - } - - def public propagateAllScopeConstraints() { - var boolean hadChanged - do{ - hadChanged = false - for(superScopeEntry : superScopes.entrySet) { - val sub = superScopeEntry.key - hadChanged = propagateLowerLimitUp(sub,partialInterpretation) || hadChanged - hadChanged = propagateUpperLimitDown(sub,partialInterpretation) || hadChanged - for(sup: superScopeEntry.value) { - hadChanged = propagateLowerLimitUp(sub,sup) || hadChanged - hadChanged = propagateUpperLimitDown(sub,sup) || hadChanged - } - } - } while(hadChanged) -// println('''All constraints are propagated.''') - } - - def public propagateAdditionToType(PartialTypeInterpratation t) { -// println('''Adding to «(t as PartialComplexTypeInterpretation).interpretationOf.name»''') - val targetScope = type2Scope.get(t) - targetScope.removeOne - val sups = superScopes.get(targetScope) - sups.forEach[removeOne] - if(this.partialInterpretation.minNewElements > 0) { - this.partialInterpretation.minNewElements = this.partialInterpretation.minNewElements-1 - } - if(this.partialInterpretation.maxNewElements > 0) { - this.partialInterpretation.maxNewElements = this.partialInterpretation.maxNewElements-1 - } else if(this.partialInterpretation.maxNewElements === 0) { - throw new IllegalArgumentException('''Inconsistent object creation: lower node limit is 0!''') - } - -// subScopes.get(targetScope).forEach[propagateUpperLimitDown(it,targetScope)] -// for(sup: sups) { -// subScopes.get(sup).forEach[propagateUpperLimitDown(it,sup)] -// } -// for(scope : type2Scope.values) { -// propagateUpperLimitDown(scope,partialInterpretation) -// } - - propagateAllScopeConstraints - -// println('''Target Scope: «targetScope.minNewElements» - «targetScope.maxNewElements»''') -// println(''' «this.partialInterpretation.minNewElements» - «this.partialInterpretation.maxNewElements»''') -// this.partialInterpretation.scopes.forEach[println(''' «(it.targetTypeInterpretation as PartialComplexTypeInterpretation).interpretationOf.name»: «it.minNewElements»-«it.maxNewElements»''')] -// println('''All constraints are propagated upon increasing «(t as PartialComplexTypeInterpretation).interpretationOf.name»''') - } - - private def propagateLowerLimitUp(Scope subScope, Scope superScope) { - if(subScope.minNewElements>superScope.minNewElements) { -// println(''' -// «(subScope.targetTypeInterpretation as PartialComplexTypeInterpretation).interpretationOf.name» -> «(superScope.targetTypeInterpretation as PartialComplexTypeInterpretation).interpretationOf.name» -// superScope.minNewElements «superScope.minNewElements» = subScope.minNewElements «subScope.minNewElements» -// ''') - superScope.minNewElements = subScope.minNewElements - return true - } else { - return false - } - } - - private def propagateUpperLimitDown(Scope subScope, Scope superScope) { - if(superScope.maxNewElements>=0 && (superScope.maxNewElements «(superScope.targetTypeInterpretation as PartialComplexTypeInterpretation).interpretationOf.name» -// subScope.maxNewElements «subScope.maxNewElements» = superScope.maxNewElements «superScope.maxNewElements» -// ''') - subScope.maxNewElements = superScope.maxNewElements - return true - } else { - return false - } - } - - private def propagateLowerLimitUp(Scope subScope, PartialInterpretation p) { - if(subScope.minNewElements>p.minNewElements) { -// println(''' -// «(subScope.targetTypeInterpretation as PartialComplexTypeInterpretation).interpretationOf.name» -> nodes -// p.minNewElements «p.minNewElements» = subScope.minNewElements «subScope.minNewElements» -// ''') - p.minNewElements = subScope.minNewElements - return true - } else { - return false - } - } - - private def propagateUpperLimitDown(Scope subScope, PartialInterpretation p) { - if(p.maxNewElements>=0 && (p.maxNewElements nodes -// subScope.maxNewElements «subScope.maxNewElements» = p.maxNewElements «p.maxNewElements» -// ''') - subScope.maxNewElements = p.maxNewElements - return true - } else { - return false - } - } - private def removeOne(Scope scope) { - if(scope.maxNewElements===0) { - throw new IllegalArgumentException('''Inconsistent object creation: «scope.targetTypeInterpretation»''') - } else if(scope.maxNewElements>0) { - scope.maxNewElements= scope.maxNewElements-1 - } - if(scope.minNewElements>0) { - scope.minNewElements= scope.minNewElements-1 - } - } -} - \ No newline at end of file diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend new file mode 100644 index 00000000..8f210ffb --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend @@ -0,0 +1,153 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.Type +import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialComplexTypeInterpretation +import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialInterpretation +import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialPrimitiveInterpretation +import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.Scope +import java.util.ArrayDeque +import java.util.HashMap +import java.util.HashSet +import java.util.Map +import java.util.Set + +class PolyhedronScopePropagator extends ScopePropagator { + val Map scopeBounds + val LinearConstraint topLevelBounds + val PolyhedronSaturationOperator operator + + new(PartialInterpretation p, Set possibleNewDynamicTypes, PolyhedronSolver solver) { + super(p) + val instanceCounts = possibleNewDynamicTypes.toInvertedMap[new Dimension(name, 0, null)] + val primitiveDimensions = new HashMap + val constraintsBuilder = ImmutableList.builder + val scopeBoundsBuilder = ImmutableMap.builder + // Dimensions for instantiable types were created according to the type analysis, + // but for any possible primitive types, we create them on demand, + // as there is no Type directly associated with a PartialPrimitiveInterpretation. + for (scope : p.scopes) { + switch (targetTypeInterpretation : scope.targetTypeInterpretation) { + PartialPrimitiveInterpretation: { + val dimension = primitiveDimensions.computeIfAbsent(targetTypeInterpretation) [ interpretation | + new Dimension(interpretation.eClass.name, 0, null) + ] + scopeBoundsBuilder.put(scope, dimension) + } + PartialComplexTypeInterpretation: { + val complexType = targetTypeInterpretation.interpretationOf + val dimensions = findSubtypeDimensions(complexType, instanceCounts) + switch (dimensions.size) { + case 0: + if (scope.minNewElements > 0) { + throw new IllegalArgumentException("Found scope for " + complexType.name + + ", but the type cannot be instantiated") + } + case 1: + scopeBoundsBuilder.put(scope, dimensions.head) + default: { + val constraint = new LinearConstraint(dimensions.toInvertedMap[1], null, null) + constraintsBuilder.add(constraint) + scopeBoundsBuilder.put(scope, constraint) + } + } + } + default: + throw new IllegalArgumentException("Unknown PartialTypeInterpretation: " + targetTypeInterpretation) + } + } + val allDimensions = ImmutableList.builder.addAll(instanceCounts.values).addAll(primitiveDimensions.values).build + scopeBounds = scopeBoundsBuilder.build + topLevelBounds = new LinearConstraint(allDimensions.toInvertedMap[1], null, null) + constraintsBuilder.add(topLevelBounds) + val expressionsToSaturate = ImmutableList.builder.addAll(scopeBounds.values).add(topLevelBounds).build + val polyhedron = new Polyhedron(allDimensions, constraintsBuilder.build, expressionsToSaturate) + operator = solver.createSaturationOperator(polyhedron) + } + + private def findSubtypeDimensions(Type type, Map instanceCounts) { + val subtypes = new HashSet + val dimensions = new HashSet + val stack = new ArrayDeque + stack.addLast(type) + while (!stack.empty) { + val subtype = stack.removeLast + if (subtypes.add(subtype)) { + val dimension = instanceCounts.get(subtype) + if (dimension !== null) { + dimensions.add(dimension) + } + stack.addAll(subtype.subtypes) + } + } + dimensions + } + + override void propagateAllScopeConstraints() { + populatePolyhedronFromScope() + val result = operator.saturate() + if (result == PolyhedronSaturationResult.EMPTY) { + throw new IllegalStateException("Scope bounds cannot be satisfied") + } else { + populateScopesFromPolyhedron() + if (result != PolyhedronSaturationResult.SATURATED) { + super.propagateAllScopeConstraints() + } + } + } + + private def populatePolyhedronFromScope() { + topLevelBounds.lowerBound = partialInterpretation.minNewElements + if (partialInterpretation.maxNewElements >= 0) { + topLevelBounds.upperBound = partialInterpretation.maxNewElements + } + for (pair : scopeBounds.entrySet) { + val scope = pair.key + val bounds = pair.value + bounds.lowerBound = scope.minNewElements + if (scope.maxNewElements >= 0) { + bounds.upperBound = scope.maxNewElements + } + } + } + + private def populateScopesFromPolyhedron() { + checkFiniteBounds(topLevelBounds) + if (partialInterpretation.minNewElements > topLevelBounds.lowerBound) { + throw new IllegalArgumentException('''Lower bound of «topLevelBounds» smaller than top-level scope: «partialInterpretation.minNewElements»''') + } else if (partialInterpretation.minNewElements != topLevelBounds.lowerBound) { + partialInterpretation.minNewElements = topLevelBounds.lowerBound + } + if (partialInterpretation.maxNewElements >= 0 && + partialInterpretation.maxNewElements < topLevelBounds.upperBound) { + throw new IllegalArgumentException('''Upper bound of «topLevelBounds» larger than top-level scope: «partialInterpretation.maxNewElements»''') + } else if (partialInterpretation.maxNewElements != topLevelBounds.upperBound) { + partialInterpretation.maxNewElements = topLevelBounds.upperBound + } + for (pair : scopeBounds.entrySet) { + val scope = pair.key + val bounds = pair.value + checkFiniteBounds(bounds) + if (scope.minNewElements > bounds.lowerBound) { + throw new IllegalArgumentException('''Lower bound of «bounds» smaller than «scope.targetTypeInterpretation» scope: «scope.minNewElements»''') + } else if (scope.minNewElements != bounds.lowerBound) { + scope.minNewElements = bounds.lowerBound + } + if (scope.maxNewElements >= 0 && scope.maxNewElements < bounds.upperBound) { + throw new IllegalArgumentException('''Upper bound of «bounds» larger than «scope.targetTypeInterpretation» scope: «scope.maxNewElements»''') + } else if (scope.maxNewElements != bounds.upperBound) { + scope.maxNewElements = bounds.upperBound + } + } + } + + private def checkFiniteBounds(LinearBoundedExpression bounds) { + if (bounds.lowerBound === null) { + throw new IllegalArgumentException("Infinite lower bound: " + bounds) + } + if (bounds.upperBound === null) { + throw new IllegalArgumentException("Infinite upper bound: " + bounds) + } + } +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronSolver.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronSolver.xtend new file mode 100644 index 00000000..08bf25b9 --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronSolver.xtend @@ -0,0 +1,115 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality + +import java.util.List +import java.util.Map +import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor + +interface PolyhedronSolver { + def PolyhedronSaturationOperator createSaturationOperator(Polyhedron polyhedron) +} + +enum PolyhedronSaturationResult { + SATURATED, + EMPTY, + UNKNOWN +} + +interface PolyhedronSaturationOperator extends AutoCloseable { + def Polyhedron getPolyhedron() + + def PolyhedronSaturationResult saturate() +} + +@FinalFieldsConstructor +@Accessors +class Polyhedron { + /** + * The list of dimensions (variables) for this polyhedron. + * + * This list must not be modified after the polyhedron was created. + * However, lower and upper bounds of the dimensions may be changed. + * + * Names of dimensions in this list are assumed to be unique. + */ + val List dimensions + + /** + * The list of constraints defining this polyhedron. + * + * The list and its elements may be freely modified. + */ + val List constraints + + /** + * The list of constraints that should be saturated (tightened) + * when a {@link PolyhedronSaturationOperator} is invoked. + * + * This list may be freely modified. + * + * Place all dimensions and constraints here to saturate all the bounds. + */ + val List expressionsToSaturate + + override toString() ''' + Dimensions: + «FOR dimension : dimensions» + «dimension» + «ENDFOR» + Constraints: + «FOR constraint : constraints» + «constraint» + «ENDFOR» +««« Saturate: +««« «FOR expression : expressionsToSaturate» +««« «IF expression instanceof Dimension»dimension«ELSEIF expression instanceof LinearConstraint»constraint«ELSE»unknown«ENDIF» «expression» +««« «ENDFOR» + ''' + +} + +@Accessors +abstract class LinearBoundedExpression { + var Integer lowerBound + var Integer upperBound +} + +@Accessors +class Dimension extends LinearBoundedExpression { + val String name + + @FinalFieldsConstructor + new() { + } + + new(String name, Integer lowerBound, Integer upperBound) { + this(name) + this.lowerBound = lowerBound + this.upperBound = upperBound + } + + override toString() { + '''«IF lowerBound !== null»«lowerBound» <= «ENDIF»«name»«IF upperBound !== null» <= «upperBound»«ENDIF»''' + } + +} + +@Accessors +class LinearConstraint extends LinearBoundedExpression { + val Map coefficients + + @FinalFieldsConstructor + new() { + } + + new(Map coefficients, Integer lowerBound, Integer upperBound) { + this(coefficients) + this.lowerBound = lowerBound + this.upperBound = upperBound + } + + override toString() { + '''«IF lowerBound !== null»«lowerBound» <= «ENDIF»«FOR pair : coefficients.entrySet SEPARATOR " + "»«IF pair.value != 1»«pair.value» * «ENDIF»«pair.key.name»«ENDFOR»«IF upperBound !== null» <= «upperBound»«ENDIF»''' + } + +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagator.xtend new file mode 100644 index 00000000..c8fb3409 --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagator.xtend @@ -0,0 +1,149 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality + +import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialComplexTypeInterpretation +import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialInterpretation +import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialTypeInterpratation +import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.Scope +import java.util.HashMap +import java.util.HashSet +import java.util.Map +import java.util.Set +import org.eclipse.xtend.lib.annotations.Accessors + +enum ScopePropagatorStrategy { + BasicTypeHierarchy, + PolyhedralTypeHierarchy +} + +class ScopePropagator { + @Accessors(PROTECTED_GETTER) PartialInterpretation partialInterpretation + Map type2Scope + + val Map> superScopes + val Map> subScopes + + new(PartialInterpretation p) { + partialInterpretation = p + type2Scope = new HashMap + for (scope : p.scopes) { + type2Scope.put(scope.targetTypeInterpretation, scope) + } + + superScopes = new HashMap + subScopes = new HashMap + for (scope : p.scopes) { + superScopes.put(scope, new HashSet) + subScopes.put(scope, new HashSet) + } + + for (scope : p.scopes) { + val target = scope.targetTypeInterpretation + if (target instanceof PartialComplexTypeInterpretation) { + val supertypeInterpretations = target.supertypeInterpretation + for (supertypeInterpretation : supertypeInterpretations) { + val supertypeScope = type2Scope.get(supertypeInterpretation) + superScopes.get(scope).add(supertypeScope) + subScopes.get(supertypeScope).add(scope) + } + } + } + } + + def propagateAllScopeConstraints() { + var boolean hadChanged + do { + hadChanged = false + for (superScopeEntry : superScopes.entrySet) { + val sub = superScopeEntry.key + hadChanged = propagateLowerLimitUp(sub, partialInterpretation) || hadChanged + hadChanged = propagateUpperLimitDown(sub, partialInterpretation) || hadChanged + for (sup : superScopeEntry.value) { + hadChanged = propagateLowerLimitUp(sub, sup) || hadChanged + hadChanged = propagateUpperLimitDown(sub, sup) || hadChanged + } + } + } while (hadChanged) + } + + def propagateAdditionToType(PartialTypeInterpratation t) { +// println('''Adding to «(t as PartialComplexTypeInterpretation).interpretationOf.name»''') + val targetScope = type2Scope.get(t) + targetScope.removeOne + val sups = superScopes.get(targetScope) + sups.forEach[removeOne] + if (this.partialInterpretation.minNewElements > 0) { + this.partialInterpretation.minNewElements = this.partialInterpretation.minNewElements - 1 + } + if (this.partialInterpretation.maxNewElements > 0) { + this.partialInterpretation.maxNewElements = this.partialInterpretation.maxNewElements - 1 + } else if (this.partialInterpretation.maxNewElements === 0) { + throw new IllegalArgumentException('''Inconsistent object creation: lower node limit is 0!''') + } + propagateAllScopeConstraints + +// println('''Target Scope: «targetScope.minNewElements» - «targetScope.maxNewElements»''') +// println(''' «this.partialInterpretation.minNewElements» - «this.partialInterpretation.maxNewElements»''') +// this.partialInterpretation.scopes.forEach[println(''' «(it.targetTypeInterpretation as PartialComplexTypeInterpretation).interpretationOf.name»: «it.minNewElements»-«it.maxNewElements»''')] +// println('''All constraints are propagated upon increasing «(t as PartialComplexTypeInterpretation).interpretationOf.name»''') + } + + private def propagateLowerLimitUp(Scope subScope, Scope superScope) { + if (subScope.minNewElements > superScope.minNewElements) { + superScope.minNewElements = subScope.minNewElements + return true + } else { + return false + } + } + + private def propagateUpperLimitDown(Scope subScope, Scope superScope) { + if (superScope.maxNewElements >= 0 && + (superScope.maxNewElements < subScope.maxNewElements || subScope.maxNewElements < 0)) { +// println(''' +// «(subScope.targetTypeInterpretation as PartialComplexTypeInterpretation).interpretationOf.name» -> «(superScope.targetTypeInterpretation as PartialComplexTypeInterpretation).interpretationOf.name» +// subScope.maxNewElements «subScope.maxNewElements» = superScope.maxNewElements «superScope.maxNewElements» +// ''') + subScope.maxNewElements = superScope.maxNewElements + return true + } else { + return false + } + } + + private def propagateLowerLimitUp(Scope subScope, PartialInterpretation p) { + if (subScope.minNewElements > p.minNewElements) { +// println(''' +// «(subScope.targetTypeInterpretation as PartialComplexTypeInterpretation).interpretationOf.name» -> nodes +// p.minNewElements «p.minNewElements» = subScope.minNewElements «subScope.minNewElements» +// ''') + p.minNewElements = subScope.minNewElements + return true + } else { + return false + } + } + + private def propagateUpperLimitDown(Scope subScope, PartialInterpretation p) { + if (p.maxNewElements >= 0 && (p.maxNewElements < subScope.maxNewElements || subScope.maxNewElements < 0)) { +// println(''' +// «(subScope.targetTypeInterpretation as PartialComplexTypeInterpretation).interpretationOf.name» -> nodes +// subScope.maxNewElements «subScope.maxNewElements» = p.maxNewElements «p.maxNewElements» +// ''') + subScope.maxNewElements = p.maxNewElements + return true + } else { + return false + } + } + + private def removeOne(Scope scope) { + if (scope.maxNewElements === 0) { + throw new IllegalArgumentException('''Inconsistent object creation: «scope.targetTypeInterpretation»''') + } else if (scope.maxNewElements > 0) { + scope.maxNewElements = scope.maxNewElements - 1 + } + if (scope.minNewElements > 0) { + scope.minNewElements = scope.minNewElements - 1 + } + } +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/Z3PolyhedronSolver.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/Z3PolyhedronSolver.xtend new file mode 100644 index 00000000..f1a84f2d --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/Z3PolyhedronSolver.xtend @@ -0,0 +1,206 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality + +import com.microsoft.z3.ArithExpr +import com.microsoft.z3.Context +import com.microsoft.z3.Expr +import com.microsoft.z3.IntNum +import com.microsoft.z3.Optimize +import com.microsoft.z3.Status +import com.microsoft.z3.Symbol +import java.util.Map +import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor + +class Z3PolyhedronSolver implements PolyhedronSolver { + val boolean lpRelaxation + + @FinalFieldsConstructor + new() { + } + + new() { + this(true) + } + + override createSaturationOperator(Polyhedron polyhedron) { + new Z3SaturationOperator(polyhedron, lpRelaxation) + } +} + +class Z3SaturationOperator implements PolyhedronSaturationOperator { + static val INFINITY_SYMBOL_NAME = "oo" + static val MULT_SYMBOL_NAME = "*" + + extension val Context context + val Symbol infinitySymbol + val Symbol multSymbol + @Accessors val Polyhedron polyhedron + val Map variables + + new(Polyhedron polyhedron, boolean lpRelaxation) { + context = new Context + infinitySymbol = context.mkSymbol(INFINITY_SYMBOL_NAME) + multSymbol = context.mkSymbol(MULT_SYMBOL_NAME) + this.polyhedron = polyhedron + variables = polyhedron.dimensions.toInvertedMap [ dimension | + val name = dimension.name + if (lpRelaxation) { + mkRealConst(name) + } else { + mkIntConst(name) + } + ] + } + + override saturate() { + val status = doSaturate() + convertStatusToSaturationResult(status) + } + + private def convertStatusToSaturationResult(Status status) { + switch (status) { + case SATISFIABLE: + PolyhedronSaturationResult.SATURATED + case UNSATISFIABLE: + PolyhedronSaturationResult.EMPTY + case UNKNOWN: + PolyhedronSaturationResult.UNKNOWN + default: + throw new IllegalArgumentException("Unknown Status: " + status) + } + } + + private def doSaturate() { + for (expressionToSaturate : polyhedron.expressionsToSaturate) { + val expr = expressionToSaturate.toExpr + val lowerResult = saturateLowerBound(expr, expressionToSaturate) + if (lowerResult != Status.SATISFIABLE) { + return lowerResult + } + val upperResult = saturateUpperBound(expr, expressionToSaturate) + if (upperResult != Status.SATISFIABLE) { + return upperResult + } + } + Status.SATISFIABLE + } + + private def saturateLowerBound(ArithExpr expr, LinearBoundedExpression expressionToSaturate) { + val optimize = prepareOptimize + val handle = optimize.MkMinimize(expr) + val status = optimize.Check() + if (status == Status.SATISFIABLE) { + val value = switch (resultExpr : handle.lower) { + IntNum: + resultExpr.getInt() + default: + if (isNegativeInfinity(resultExpr)) { + null + } else { + throw new IllegalArgumentException("Integer result expected, got: " + resultExpr) + } + } + expressionToSaturate.lowerBound = value + } + status + } + + private def saturateUpperBound(ArithExpr expr, LinearBoundedExpression expressionToSaturate) { + val optimize = prepareOptimize + val handle = optimize.MkMaximize(expr) + val status = optimize.Check() + if (status == Status.SATISFIABLE) { + val value = switch (resultExpr : handle.upper) { + IntNum: + resultExpr.getInt() + default: + if (isPositiveInfinity(resultExpr)) { + null + } else { + throw new IllegalArgumentException("Integer result expected, got: " + resultExpr) + } + } + expressionToSaturate.upperBound = value + } + status + } + + private def isPositiveInfinity(Expr expr) { + expr.app && expr.getFuncDecl.name == infinitySymbol + } + + private def isNegativeInfinity(Expr expr) { + // Negative infinity is represented as (* (- 1) oo) + if (!expr.app || expr.getFuncDecl.name != multSymbol || expr.numArgs != 2) { + return false + } + isPositiveInfinity(expr.args.get(1)) + } + + private def prepareOptimize() { + val optimize = mkOptimize() + assertConstraints(optimize) + optimize + } + + private def assertConstraints(Optimize it) { + for (pair : variables.entrySet) { + assertBounds(pair.value, pair.key) + } + for (constraint : polyhedron.constraints) { + val expr = createLinearCombination(constraint.coefficients) + assertBounds(expr, constraint) + } + } + + private def assertBounds(Optimize it, ArithExpr expression, LinearBoundedExpression bounds) { + val lowerBound = bounds.lowerBound + val upperBound = bounds.upperBound + if (lowerBound == upperBound) { + if (lowerBound === null) { + return + } + Assert(mkEq(expression, mkInt(lowerBound))) + } else { + if (lowerBound !== null) { + Assert(mkGe(expression, mkInt(lowerBound))) + } + if (upperBound !== null) { + Assert(mkLe(expression, mkInt(upperBound))) + } + } + } + + private def toExpr(LinearBoundedExpression linearBoundedExpression) { + switch (linearBoundedExpression) { + Dimension: variables.get(linearBoundedExpression) + LinearConstraint: createLinearCombination(linearBoundedExpression.coefficients) + default: throw new IllegalArgumentException("Unknown linear bounded expression:" + linearBoundedExpression) + } + } + + private def createLinearCombination(Map coefficients) { + val size = coefficients.size + val array = newArrayOfSize(size) + var int i = 0 + for (pair : coefficients.entrySet) { + val variable = variables.get(pair.key) + if (variable === null) { + throw new IllegalArgumentException("Unknown dimension: " + pair.key.name) + } + val coefficient = pair.value + val term = if (coefficient == 1) { + variable + } else { + mkMul(mkInt(coefficient), variable) + } + array.set(i, term) + i++ + } + mkAdd(array) + } + + override close() throws Exception { + context.close() + } +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/RefinementRuleProvider.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/RefinementRuleProvider.xtend index 20d24b77..5fefa551 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/RefinementRuleProvider.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/RefinementRuleProvider.xtend @@ -6,7 +6,7 @@ import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.Relation import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.RelationDeclaration import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.Type import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.ModelGenerationStatistics -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.ScopePropagator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagator import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.GeneratedPatterns import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.ObjectCreationPrecondition import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialComplexTypeInterpretation diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasoner.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasoner.xtend index 701eb054..101f0a3e 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasoner.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasoner.xtend @@ -12,7 +12,7 @@ import hu.bme.mit.inf.dslreasoner.logic.model.logicproblem.LogicproblemPackage import hu.bme.mit.inf.dslreasoner.logic.model.logicresult.LogicresultFactory import hu.bme.mit.inf.dslreasoner.logic.model.logicresult.ModelResult import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.ModelGenerationMethodProvider -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.ScopePropagator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagator import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.PartialInterpretationInitialiser import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialInterpretation import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialinterpretationPackage @@ -71,16 +71,13 @@ class ViatraReasoner extends LogicReasoner { } emptySolution.problemConainer = problem - val ScopePropagator scopePropagator = new ScopePropagator(emptySolution) - scopePropagator.propagateAllScopeConstraints - val method = modelGenerationMethodProvider.createModelGenerationMethod( problem, emptySolution, workspace, viatraConfig.nameNewElements, viatraConfig.typeInferenceMethod, - scopePropagator, + viatraConfig.scopePropagatorStrategy, viatraConfig.documentationLevel ) diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasonerConfiguration.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasonerConfiguration.xtend index 99decdd9..7a3a2d67 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasonerConfiguration.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasonerConfiguration.xtend @@ -6,6 +6,7 @@ import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.RelationDeclaration import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.TypeDeclaration import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.ModelGenerationMethod import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.TypeInferenceMethod +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagatorStrategy import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.visualisation.PartialInterpretationVisualiser import hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner.optimization.ObjectiveKind import hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner.optimization.ObjectiveThreshold @@ -49,6 +50,8 @@ class ViatraReasonerConfiguration extends LogicSolverConfiguration { * Configuration for cutting search space. */ public var SearchSpaceConstraint searchSpaceConstraints = new SearchSpaceConstraint + + public var ScopePropagatorStrategy scopePropagatorStrategy = ScopePropagatorStrategy.PolyhedralTypeHierarchy public var List costObjectives = newArrayList } diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/META-INF/MANIFEST.MF b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/META-INF/MANIFEST.MF index 76c113c1..43e40319 100644 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/META-INF/MANIFEST.MF +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/META-INF/MANIFEST.MF @@ -11,3 +11,5 @@ Require-Bundle: com.google.guava, org.eclipse.xtend.lib, org.eclipse.xtend.lib.macro, hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatraquery +Export-Package: hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.cardinality, + hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.interval diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend new file mode 100644 index 00000000..2d159752 --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend @@ -0,0 +1,199 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.cardinality + +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Dimension +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.LinearConstraint +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Polyhedron +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronSaturationOperator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronSaturationResult +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Z3PolyhedronSolver +import org.junit.After +import org.junit.Before +import org.junit.Test + +import static org.junit.Assert.* + +class Z3PolyhedronSolverTest { + var Z3PolyhedronSolver solver + var PolyhedronSaturationOperator operator + + @Before + def void setUp() { + solver = new Z3PolyhedronSolver(false) + } + + @After + def void tearDown() { + destroyOperatorIfExists() + } + + @Test + def void singleDimensionTest() { + val x = new Dimension("x", 0, 1) + createSaturationOperator(new Polyhedron(#[x], #[], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(1, x.upperBound) + } + + @Test + def void singleDimensionNegativeValueTest() { + val x = new Dimension("x", -2, -1) + createSaturationOperator(new Polyhedron(#[x], #[], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(-2, x.lowerBound) + assertEquals(-1, x.upperBound) + } + + @Test + def void singleDimensionConstraintTest() { + val x = new Dimension("x", null, null) + val constraint = new LinearConstraint(#{x -> 2}, 0, 2) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(1, x.upperBound) + } + + @Test + def void singleDimensionConstraintUnitCoefficientTest() { + val x = new Dimension("x", null, null) + val constraint = new LinearConstraint(#{x -> 1}, 1, 3) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(1, x.lowerBound) + assertEquals(3, x.upperBound) + } + + @Test + def void singleDimensionConstraintIntegerTest() { + val x = new Dimension("x", null, null) + val constraint = new LinearConstraint(#{x -> 2}, 0, 3) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(1, x.upperBound) + } + + @Test + def void singleDimensionUnboundedFromAboveTest() { + val x = new Dimension("x", 0, null) + createSaturationOperator(new Polyhedron(#[x], #[], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(null, x.upperBound) + } + + @Test + def void singleDimensionUnboundedFromBelowTest() { + val x = new Dimension("x", null, 0) + createSaturationOperator(new Polyhedron(#[x], #[], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(null, x.lowerBound) + assertEquals(0, x.upperBound) + } + + @Test + def void singleDimensionUnsatisfiableTest() { + val x = new Dimension("x", 0, 1) + val constraint = new LinearConstraint(#{x -> 2}, -2, -1) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.EMPTY, result) + } + + @Test + def void equalityConstraintTest() { + val x = new Dimension("x", null, null) + val y = new Dimension("y", 1, 2) + val constraint = new LinearConstraint(#{x -> 2, y -> 2}, 6, 6) + createSaturationOperator(new Polyhedron(#[x, y], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(1, x.lowerBound) + assertEquals(2, x.upperBound) + } + + @Test + def void saturateConstraintTest() { + val x = new Dimension("x", 0, 2) + val y = new Dimension("y", 1, 2) + val constraint = new LinearConstraint(#{x -> 2, y -> 1}, 0, 8) + createSaturationOperator(new Polyhedron(#[x, y], #[constraint], #[constraint])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(1, constraint.lowerBound) + assertEquals(6, constraint.upperBound) + } + + @Test(expected=IllegalArgumentException) + def void unknownVariableTest() { + val x = new Dimension("x", 0, 1) + val y = new Dimension("y", 0, 1) + val constraint = new LinearConstraint(#{y -> 2}, 0, 2) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + saturate() + } + + @Test + def void unsatisfiableMultipleInheritanceTest() { + val x = new Dimension("x", 0, 1) + val y = new Dimension("y", 0, 1) + val z = new Dimension("z", 0, 1) + createSaturationOperator(new Polyhedron( + #[x, y, z], + #[ + new LinearConstraint(#{x -> 1, y -> 1}, 1, 1), + new LinearConstraint(#{x -> 1, z -> 1}, 1, 1), + new LinearConstraint(#{y -> 1, z -> 1}, 1, 1) + ], + #[x, y, z] + )) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.EMPTY, result) + } + + private def createSaturationOperator(Polyhedron polyhedron) { + destroyOperatorIfExists() + operator = solver.createSaturationOperator(polyhedron) + } + + private def destroyOperatorIfExists() { + if (operator !== null) { + operator.close + } + } + + private def saturate() { + operator.saturate + } +} -- cgit v1.2.3-54-g00ecf From b217dfc7e7bd7beb73c8cc23ad82383309ceb697 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 18 Jul 2019 15:21:56 +0200 Subject: Implement Coin-OR CBC polyhedron saturation operator --- .../hu.bme.mit.inf.dslreasoner.ilp.cbc/.classpath | 15 ++ .../hu.bme.mit.inf.dslreasoner.ilp.cbc/.gitignore | 2 + .../hu.bme.mit.inf.dslreasoner.ilp.cbc/.project | 28 +++ .../META-INF/MANIFEST.MF | 10 + .../build.properties | 4 + .../cpp/CMakeLists.txt | 23 ++ .../cpp/viatracbc.cpp | 261 +++++++++++++++++++++ .../cpp/viatracbc.hpp | 16 ++ .../lib/libviatracbc.so | Bin 0 -> 38248 bytes .../mit/inf/dslreasoner/ilp/cbc/CbcException.java | 30 +++ .../bme/mit/inf/dslreasoner/ilp/cbc/CbcResult.java | 54 +++++ .../bme/mit/inf/dslreasoner/ilp/cbc/CbcSolver.java | 71 ++++++ .../com.microsoft.z3/META-INF/MANIFEST.MF | 2 +- .../META-INF/MANIFEST.MF | 3 +- .../ModelGenerationMethodProvider.xtend | 4 +- .../cardinality/CbcPolyhedronSolver.xtend | 182 ++++++++++++++ .../cardinality/CbcPolyhedronSolverTest.xtend | 31 +++ .../tests/cardinality/PolyhedronSolverTest.xtend | 216 +++++++++++++++++ .../tests/cardinality/Z3PolyhedronSolverTest.xtend | 197 +--------------- 19 files changed, 952 insertions(+), 197 deletions(-) create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.classpath create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.gitignore create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.project create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/META-INF/MANIFEST.MF create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/build.properties create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/CMakeLists.txt create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.cpp create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.hpp create mode 100755 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/lib/libviatracbc.so create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcException.java create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcResult.java create mode 100644 Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcSolver.java create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/CbcPolyhedronSolver.xtend create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend create mode 100644 Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.classpath b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.classpath new file mode 100644 index 00000000..e19039ae --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.classpath @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.gitignore b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.gitignore new file mode 100644 index 00000000..0cc6a59e --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/cpp/build/ diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.project b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.project new file mode 100644 index 00000000..6c32e464 --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/.project @@ -0,0 +1,28 @@ + + + hu.bme.mit.inf.dslreasoner.ilp.cbc + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/META-INF/MANIFEST.MF b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/META-INF/MANIFEST.MF new file mode 100644 index 00000000..04478746 --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/META-INF/MANIFEST.MF @@ -0,0 +1,10 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Cbc +Bundle-SymbolicName: hu.bme.mit.inf.dslreasoner.ilp.cbc +Bundle-Version: 1.0.0.qualifier +Automatic-Module-Name: hu.bme.mit.inf.dslreasoner.ilp.cbc +Export-Package: hu.bme.mit.inf.dslreasoner.ilp.cbc +Bundle-NativeCode: libviatracbc.so; + osname=Linux; + processor=x86_64 diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/build.properties b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/build.properties new file mode 100644 index 00000000..34d2e4d2 --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/CMakeLists.txt b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/CMakeLists.txt new file mode 100644 index 00000000..5dbcb071 --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.14.5) +project(hu.bme.mit.inf.dslreasoner.ilp.cbc) + +set(CMAKE_CXX_STANDARD 17) + +find_package(JNI REQUIRED) +find_package(PkgConfig REQUIRED) + +pkg_check_modules(CBC REQUIRED cbc) + +add_library(viatracbc SHARED viatracbc.cpp) + +target_link_libraries(viatracbc + ${JAVA_JVM_LIBRARY} + ${CBC_LIBRARIES}) +target_include_directories(viatracbc + PUBLIC ${JNI_INCLUDE_DIRS} + PRIVATE ${CBC_INCLUDE_DIRS}) + +set(VIATRACBC_NATIVES_DIR ${CMAKE_SOURCE_DIR}/../lib) +add_custom_command(TARGET viatracbc POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ${VIATRACBC_NATIVES_DIR} + COMMAND ${CMAKE_COMMAND} -E copy $ ${VIATRACBC_NATIVES_DIR}) diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.cpp b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.cpp new file mode 100644 index 00000000..49994244 --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.cpp @@ -0,0 +1,261 @@ + +#include +#include +#include +#include + +#include + +#include "CbcBranchDefaultDecision.hpp" +#include "CbcCompareDefault.hpp" +#include "CbcHeuristic.hpp" +#include "CbcHeuristicLocal.hpp" +#include "CbcModel.hpp" +#include "CglClique.hpp" +#include "CglFlowCover.hpp" +#include "CglGomory.hpp" +#include "CglKnapsackCover.hpp" +#include "CglMixedIntegerRounding.hpp" +#include "CglOddHole.hpp" +#include "CglProbing.hpp" +#include "CoinModel.hpp" +#include "OsiClpSolverInterface.hpp" + +#include "viatracbc.hpp" + +static const char *const kCbcExceptionClassName = "hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcException"; +static const char *const kRuntimeExceptionClassName = "java/lang/RuntimeException"; + +static const jint kCbcSolutionBounded = 0; +static const jint kCbcSolutionUnbounded = 1; +static const jint kCbcUnsat = 2; +static const jint kCbcAbandoned = 3; +static const jint kCbcTimeout = 4; +static const jint kCbcError = 5; + +static CoinModel CreateModel(JNIEnv *env, jdoubleArray columnLowerBoundsArray, + jdoubleArray columnUpperBoundsArray, jintArray rowStartsArray, jintArray columnIndicesArray, + jdoubleArray entriedArray, jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, + jdoubleArray objectiveArray); +static void CreateModelColumns(JNIEnv *env, jdoubleArray columnLowerBoundsArray, + jdoubleArray columnUpperBoundsArray, jdoubleArray objectiveArray, CoinModel &build); +static void CreateModelRows(JNIEnv *env, jintArray rowStartsArray, jintArray columnIndicesArray, + jdoubleArray entriesArray, jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, + CoinModel &build); +static jint SolveModel(CoinModel &build, jdouble timeoutSeconds, jboolean silent, jdouble &value); +static void ThrowException(JNIEnv *env, const char *message); + +template < + typename Array, + typename Element, + Element *(JNIEnv::*GetElementsPtr)(Array, jboolean *), + void (JNIEnv::*ReleaseElementsPtr)(Array, Element *, jint) +> +class PinnedArray { +public: + PinnedArray(JNIEnv *env, Array array) + : env_{env}, array_{array}, elements_{(env->*GetElementsPtr)(array, nullptr)} { + if (elements_ == nullptr) { + throw std::runtime_error("Failed to pin array elements"); + } + } + PinnedArray(const PinnedArray &) = delete; + PinnedArray(PinnedArray &&) = delete; + PinnedArray &operator=(const PinnedArray &) = delete; + PinnedArray &operator=(PinnedArray &&) = delete; + ~PinnedArray() { + (env_->*ReleaseElementsPtr)(array_, elements_, 0); + } + + operator Element *() { return elements_; } + operator const Element *() const { return elements_; } + +private: + JNIEnv *env_; + Array array_; + Element *elements_; +}; + +using PinnedIntArray = PinnedArray; +using PinnedDoubleArray = PinnedArray; + +jint Java_hu_bme_mit_inf_dslreasoner_ilp_cbc_CbcSolver_solveIlpProblem( + JNIEnv *env, jclass klazz, jdoubleArray columnLowerBoundsArray, jdoubleArray columnUpperBoundsArray, + jintArray rowStartsArray, jintArray columnIndicesArray, jdoubleArray entriesArray, + jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, jdoubleArray objectiveArray, + jdoubleArray outputArray, jdouble timeoutSeconds, jboolean silent) { + try { + auto build = CreateModel(env, columnLowerBoundsArray, columnUpperBoundsArray, + rowStartsArray, columnIndicesArray, entriesArray, rowLowerBoundsArray, rowUpperBoundsArray, + objectiveArray); + double value; + jint result = SolveModel(build, timeoutSeconds, silent, value); + if (result == kCbcSolutionBounded) { + PinnedDoubleArray output{env, outputArray}; + *output = value; + } + return result; + } catch (const std::exception &e) { + ThrowException(env, e.what()); + } catch (...) { + ThrowException(env, "Unknown solver error"); + } + return kCbcError; +} + +CoinModel CreateModel(JNIEnv *env, jdoubleArray columnLowerBoundsArray, + jdoubleArray columnUpperBoundsArray, jintArray rowStartsArray, jintArray columnIndicesArray, + jdoubleArray entriesArray, jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, + jdoubleArray objectiveArray) { + CoinModel build; + CreateModelColumns(env, columnLowerBoundsArray, columnUpperBoundsArray, objectiveArray, build); + CreateModelRows(env, rowStartsArray, columnIndicesArray, entriesArray, rowLowerBoundsArray, + rowUpperBoundsArray, build); + return build; +} + +void CreateModelColumns(JNIEnv *env, jdoubleArray columnLowerBoundsArray, + jdoubleArray columnUpperBoundsArray, jdoubleArray objectiveArray, CoinModel &build) { + int numColumns = env->GetArrayLength(columnLowerBoundsArray); + PinnedDoubleArray columnLowerBounds{env, columnLowerBoundsArray}; + PinnedDoubleArray columnUpperBounds{env, columnUpperBoundsArray}; + PinnedDoubleArray objective{env, objectiveArray}; + for (int i = 0; i < numColumns; i++) { + build.setColumnBounds(i, columnLowerBounds[i], columnUpperBounds[i]); + build.setObjective(i, objective[i]); + build.setInteger(i); + } +} + +void CreateModelRows(JNIEnv *env, jintArray rowStartsArray, jintArray columnIndicesArray, + jdoubleArray entriesArray, jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, + CoinModel &build) { + int numRows = env->GetArrayLength(rowLowerBoundsArray); + PinnedIntArray rowStarts{env, rowStartsArray}; + PinnedIntArray columnIndices{env, columnIndicesArray}; + PinnedDoubleArray entries{env, entriesArray}; + PinnedDoubleArray rowLowerBounds{env, rowLowerBoundsArray}; + PinnedDoubleArray rowUpperBounds{env, rowUpperBoundsArray}; + for (int i = 0; i < numRows; i++) { + int rowStart = rowStarts[i]; + int numbersInRow = rowStarts[i + 1] - rowStart; + build.addRow(numbersInRow, &columnIndices[rowStart], &entries[rowStart], + rowLowerBounds[i], rowUpperBounds[i]); + } +} + +jint SolveModel(CoinModel &build, jdouble timeoutSeconds, jboolean silent, jdouble &value) { + OsiClpSolverInterface solver; + solver.loadFromCoinModel(build); + CbcModel model{solver}; + + model.setDblParam(CbcModel::CbcMaximumSeconds, timeoutSeconds); + if (silent == JNI_FALSE) { + model.messageHandler()->setLogLevel(2); + model.solver()->messageHandler()->setLogLevel(1); + } else { + model.solver()->setHintParam(OsiDoReducePrint, true, OsiHintTry); + model.messageHandler()->setLogLevel(0); + model.solver()->messageHandler()->setLogLevel(0); + } + + // Cut generators and heuristics are used according to + // https://github.com/coin-or/Cbc/blob/6b977b6707f1755520c64fea57b95891c1f3ddc0/Cbc/examples/sample2.cpp + + CglProbing probing; + probing.setUsingObjective(true); + probing.setMaxPass(1); + probing.setMaxPassRoot(5); + probing.setMaxProbe(10); + probing.setMaxProbeRoot(1000); + probing.setMaxLook(50); + probing.setMaxLookRoot(500); + probing.setMaxElements(200); + probing.setRowCuts(3); + model.addCutGenerator(&probing, -1, "Probing"); + + CglGomory gomory; + gomory.setLimit(300); + model.addCutGenerator(&gomory, -1, "Gomory"); + + CglKnapsackCover knapsackCover; + model.addCutGenerator(&knapsackCover, -1, "KnapsackCover"); + + CglClique clique; + clique.setStarCliqueReport(false); + clique.setRowCliqueReport(false); + model.addCutGenerator(&clique, -1, "Clique"); + + CglFlowCover flowCover; + model.addCutGenerator(&flowCover, -1, "FlowCover"); + + CglMixedIntegerRounding mixedIntegerRounding; + model.addCutGenerator(&mixedIntegerRounding, -1, "MixedIntegerRounding"); + + OsiClpSolverInterface *osiClp = dynamic_cast(model.solver()); + if (osiClp != nullptr) { + osiClp->setSpecialOptions(128); + osiClp->setupForRepeatedUse(0, 0); + } + + CbcRounding rounding; + model.addHeuristic(&rounding); + + CbcHeuristicLocal localHeuristic; + model.addHeuristic(&localHeuristic); + + CbcBranchDefaultDecision branchDecision; + model.setBranchingMethod(&branchDecision); + + CbcCompareDefault nodeComparison; + model.setNodeComparison(nodeComparison); + + model.initialSolve(); + + if (model.isInitialSolveProvenPrimalInfeasible()) { + return kCbcUnsat; + } + if (model.isInitialSolveAbandoned()) { + return kCbcTimeout; + } + + model.setMinimumDrop(CoinMin(1.0, fabs(model.getMinimizationObjValue()) * 1.0e-3 + 1.0e-4)); + model.setMaximumCutPassesAtRoot(-100); + model.setNumberStrong(10); + model.solver()->setIntParam(OsiMaxNumIterationHotStart, 100); + + model.branchAndBound(); + + if (model.isProvenInfeasible()) { + return kCbcUnsat; + } + if (model.isProvenDualInfeasible()) { + return kCbcSolutionUnbounded; + } + if (model.isProvenOptimal()) { + value = model.getMinimizationObjValue(); + return kCbcSolutionBounded; + } + if (model.maximumSecondsReached()) { + return kCbcTimeout; + } + return kCbcAbandoned; +} + +void ThrowException(JNIEnv *env, const char *message) { + jclass exceptionClass = env->FindClass(kCbcExceptionClassName); + if (exceptionClass == nullptr) { + std::cerr << "WARNING: " << kCbcExceptionClassName << " class was not found" << std::endl; + exceptionClass = env->FindClass(kRuntimeExceptionClassName); + if (exceptionClass == nullptr) { + std::cerr << "FATAL: " << kRuntimeExceptionClassName << " class was not found" << std::endl; + std::cerr << "FATAL: " << message << std::endl; + std::exit(EXIT_FAILURE); + } + } + if (env->ThrowNew(exceptionClass, message) < 0) { + std::cerr << "FATAL: Could not throw java exception" << std::endl; + std::cerr << "FATAL: " << message << std::endl; + std::exit(EXIT_FAILURE); + } +} diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.hpp b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.hpp new file mode 100644 index 00000000..c65f71e3 --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.hpp @@ -0,0 +1,16 @@ +#ifndef HU_BME_MIT_INF_DSLREASONER_ILP_CBC_ +#define HU_BME_MIT_INF_DSLREASONER_ILP_CBC_ + +#include + +extern "C" { + +JNIEXPORT jint JNICALL Java_hu_bme_mit_inf_dslreasoner_ilp_cbc_CbcSolver_solveIlpProblem( + JNIEnv *env, jclass klazz, jdoubleArray columnLowerBoundsArray, jdoubleArray columnUpperBoundsArray, + jintArray rowStartsArray, jintArray columnIndicesArray, jdoubleArray entriesArray, + jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, jdoubleArray objectiveArray, + jdoubleArray outputArray, jdouble timeoutSeconds, jboolean silent); + +} + +#endif // HU_BME_MIT_INF_DSLREASONER_ILP_CBC_ diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/lib/libviatracbc.so b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/lib/libviatracbc.so new file mode 100755 index 00000000..21fd2ff2 Binary files /dev/null and b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/lib/libviatracbc.so differ diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcException.java b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcException.java new file mode 100644 index 00000000..26846958 --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcException.java @@ -0,0 +1,30 @@ +package hu.bme.mit.inf.dslreasoner.ilp.cbc; + +public class CbcException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 2691773509078511887L; + + public CbcException() { + super(); + } + + public CbcException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public CbcException(String message, Throwable cause) { + super(message, cause); + } + + public CbcException(String message) { + super(message); + } + + public CbcException(Throwable cause) { + super(cause); + } + +} diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcResult.java b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcResult.java new file mode 100644 index 00000000..dae3a447 --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcResult.java @@ -0,0 +1,54 @@ +package hu.bme.mit.inf.dslreasoner.ilp.cbc; + +public abstract class CbcResult { + public static final CbcResult SOLUTION_UNBOUNDED = new CbcResult() { + }; + + public static final CbcResult UNSAT = new CbcResult() { + }; + + public static final CbcResult ABANDONED = new CbcResult() { + }; + + public static final CbcResult TIMEOUT = new CbcResult() { + }; + + private CbcResult() { + } + + public static class SolutionBounded extends CbcResult { + public final double value; + + public SolutionBounded(double value) { + this.value = value; + } + + public double getValue() { + return value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(value); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SolutionBounded other = (SolutionBounded) obj; + if (Double.doubleToLongBits(value) != Double.doubleToLongBits(other.value)) + return false; + return true; + } + } +} diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcSolver.java b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcSolver.java new file mode 100644 index 00000000..39b9d537 --- /dev/null +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcSolver.java @@ -0,0 +1,71 @@ +package hu.bme.mit.inf.dslreasoner.ilp.cbc; + +public class CbcSolver { + private static int CBC_SOLUTION_BOUNDED = 0; + private static int CBC_SOLUTION_UNBOUNDED = 1; + private static int CBC_UNSAT = 2; + private static int CBC_ABANDONED = 3; + private static int CBC_TIMEOUT = 4; + private static int CBC_ERROR = 5; + + private static boolean nativesLoaded = false; + + private CbcSolver() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly."); + } + + public static CbcResult solve(double[] columnLowerBounds, double[] columnUpperBounds, int[] rowStarts, + int[] columnIndices, double[] entries, double[] rowLowerBounds, double[] rowUpperBounds, + double[] objective, double timeoutSeconds, boolean silent) { + loadNatives(); + validate(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices, entries, rowLowerBounds, + rowUpperBounds, objective); + double[] output = new double[1]; + int result = solveIlpProblem(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices, entries, + rowLowerBounds, rowUpperBounds, objective, output, timeoutSeconds, silent); + if (result == CBC_SOLUTION_BOUNDED) { + return new CbcResult.SolutionBounded(output[0]); + } else if (result == CBC_SOLUTION_UNBOUNDED) { + return CbcResult.SOLUTION_UNBOUNDED; + } else if (result == CBC_UNSAT) { + return CbcResult.UNSAT; + } else if (result == CBC_ABANDONED) { + return CbcResult.ABANDONED; + } else if (result == CBC_TIMEOUT) { + return CbcResult.TIMEOUT; + } else if (result == CBC_ERROR) { + throw new CbcException("Solver signalled error, but no exception was thrown"); + } else { + throw new CbcException("Unknown return value: " + result); + } + } + + private static void loadNatives() { + if (!nativesLoaded) { + synchronized (CbcSolver.class) { + System.loadLibrary("viatracbc"); + nativesLoaded = true; + } + } + } + + private static void validate(double[] columnLowerBounds, double[] columnUpperBounds, int[] rowStarts, + int[] columnIndices, double[] entries, double[] rowLowerBounds, double[] rowUpperBounds, + double[] objective) { + int numColumns = columnLowerBounds.length; + if (columnUpperBounds.length != numColumns) { + throw new CbcException("Lengths of columnLowerBounds and columnUpperBounds must match"); + } + if (objective.length != numColumns) { + throw new CbcException("Lengths of columnLowerBounds and objective must match"); + } + int numRows = rowLowerBounds.length; + if (rowUpperBounds.length != numRows) { + throw new CbcException("Lengths of rowLowerBounds and rowUpperBounds must match"); + } + } + + private static native int solveIlpProblem(double[] columnLowerBounds, double[] columnUpperBounds, int[] rowStarts, + int[] columnIndices, double[] entries, double[] rowLowerBounds, double[] rowUpperBounds, double[] objective, + double[] output, double timeoutSeconds, boolean silent); +} diff --git a/Solvers/SMT-Solver/com.microsoft.z3/META-INF/MANIFEST.MF b/Solvers/SMT-Solver/com.microsoft.z3/META-INF/MANIFEST.MF index 01faa2ad..401fa6cf 100644 --- a/Solvers/SMT-Solver/com.microsoft.z3/META-INF/MANIFEST.MF +++ b/Solvers/SMT-Solver/com.microsoft.z3/META-INF/MANIFEST.MF @@ -9,7 +9,7 @@ Bundle-ClassPath: com.microsoft.z3.jar Bundle-NativeCode: lib/libz3.so; lib/libz3java.so; osname=Linux; - processor=x86_64; + processor=x86_64, lib/libz3.dylib; lib/libz3java.dylib; osname=MacOSX; diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/META-INF/MANIFEST.MF b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/META-INF/MANIFEST.MF index 37495e50..f9090901 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/META-INF/MANIFEST.MF +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/META-INF/MANIFEST.MF @@ -24,7 +24,8 @@ Require-Bundle: hu.bme.mit.inf.dslreasoner.logic.model;bundle-version="1.0.0", org.eclipse.xtext;bundle-version="2.10.0", org.eclipse.viatra.transformation.runtime.emf;bundle-version="1.5.0", org.eclipse.xtext.xbase;bundle-version="2.10.0", - com.microsoft.z3;bundle-version="4.8.5" + com.microsoft.z3;bundle-version="4.8.5", + hu.bme.mit.inf.dslreasoner.ilp.cbc;bundle-version="1.0.0" Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: org.apache.log4j Automatic-Module-Name: hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatraquery diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend index 0040dbcd..0ceb5b2e 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend @@ -4,10 +4,10 @@ import com.google.common.collect.ImmutableMap import hu.bme.mit.inf.dslreasoner.logic.model.builder.DocumentationLevel import hu.bme.mit.inf.dslreasoner.logic.model.logicproblem.LogicProblem import hu.bme.mit.inf.dslreasoner.viatra2logic.viatra2logicannotations.TransfomedViatraQuery +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.CbcPolyhedronSolver import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronScopePropagator import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagator import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagatorStrategy -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Z3PolyhedronSolver import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.GeneratedPatterns import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.ModalPatternQueries import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.PatternProvider @@ -119,7 +119,7 @@ class ModelGenerationMethodProvider { new ScopePropagator(emptySolution) case PolyhedralTypeHierarchy: { val types = queries.refineObjectQueries.keySet.map[newType].toSet - val solver = new Z3PolyhedronSolver + val solver = new CbcPolyhedronSolver new PolyhedronScopePropagator(emptySolution, types, solver) } default: diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/CbcPolyhedronSolver.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/CbcPolyhedronSolver.xtend new file mode 100644 index 00000000..1f6d4e8f --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/CbcPolyhedronSolver.xtend @@ -0,0 +1,182 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality + +import com.google.common.collect.ImmutableMap +import hu.bme.mit.inf.dslreasoner.ilp.cbc.CbcResult +import hu.bme.mit.inf.dslreasoner.ilp.cbc.CbcSolver +import java.util.Map +import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor + +@FinalFieldsConstructor +class CbcPolyhedronSolver implements PolyhedronSolver { + val double timeoutSeconds + val boolean silent + + new() { + this(10, true) + } + + override createSaturationOperator(Polyhedron polyhedron) { + new CbcSaturationOperator(polyhedron, timeoutSeconds, silent) + } +} + +class CbcSaturationOperator implements PolyhedronSaturationOperator { + @Accessors val Polyhedron polyhedron + val double timeoutSeconds + val boolean silent + val double[] columnLowerBounds + val double[] columnUpperBounds + val double[] objective + val Map dimensionsToIndicesMap + + new(Polyhedron polyhedron, double timeoutSeconds, boolean silent) { + this.polyhedron = polyhedron + this.timeoutSeconds = timeoutSeconds + this.silent = silent + val numDimensions = polyhedron.dimensions.size + columnLowerBounds = newDoubleArrayOfSize(numDimensions) + columnUpperBounds = newDoubleArrayOfSize(numDimensions) + objective = newDoubleArrayOfSize(numDimensions) + dimensionsToIndicesMap = ImmutableMap.copyOf(polyhedron.dimensions.indexed.toMap([value], [key])) + } + + override saturate() { + val numDimensions = polyhedron.dimensions.size + for (var int j = 0; j < numDimensions; j++) { + val dimension = polyhedron.dimensions.get(j) + columnLowerBounds.set(j, dimension.lowerBound.toDouble(Double.NEGATIVE_INFINITY)) + columnUpperBounds.set(j, dimension.upperBound.toDouble(Double.POSITIVE_INFINITY)) + } + val numConstraints = polyhedron.constraints.size + val rowStarts = newIntArrayOfSize(numConstraints + 1) + val rowLowerBounds = newDoubleArrayOfSize(numConstraints) + val rowUpperBounds = newDoubleArrayOfSize(numConstraints) + val numEntries = polyhedron.constraints.map[coefficients.size].reduce[a, b|a + b] ?: 0 + rowStarts.set(numConstraints, numEntries) + val columnIndices = newIntArrayOfSize(numEntries) + val entries = newDoubleArrayOfSize(numEntries) + var int index = 0 + for (var int i = 0; i < numConstraints; i++) { + rowStarts.set(i, index) + val constraint = polyhedron.constraints.get(i) + rowLowerBounds.set(i, constraint.lowerBound.toDouble(Double.NEGATIVE_INFINITY)) + rowUpperBounds.set(i, constraint.upperBound.toDouble(Double.POSITIVE_INFINITY)) + if (!dimensionsToIndicesMap.keySet.containsAll(constraint.coefficients.keySet)) { + throw new IllegalArgumentException("Constrains has unknown dimensions") + } + for (var int j = 0; j < numDimensions; j++) { + val dimension = polyhedron.dimensions.get(j) + val coefficient = constraint.coefficients.get(dimension) + if (coefficient !== null && coefficient != 0) { + columnIndices.set(index, j) + entries.set(index, coefficient) + index++ + } + } + } + if (index != numEntries) { + throw new AssertionError("Last entry does not equal the number of entries in the constraint matrix") + } + for (expressionToSaturate : polyhedron.expressionsToSaturate) { + for (var int j = 0; j < numDimensions; j++) { + objective.set(j, 0) + } + switch (expressionToSaturate) { + Dimension: { + val j = getIndex(expressionToSaturate) + objective.set(j, 1) + } + LinearConstraint: { + for (pair : expressionToSaturate.coefficients.entrySet) { + val j = getIndex(pair.key) + objective.set(j, pair.value) + } + } + default: + throw new IllegalArgumentException("Unknown expression: " + expressionToSaturate) + } + val minimizationResult = CbcSolver.solve(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices, + entries, rowLowerBounds, rowUpperBounds, objective, timeoutSeconds, silent) + switch (minimizationResult) { + CbcResult.SolutionBounded: { + val value = Math.floor(minimizationResult.value) + expressionToSaturate.lowerBound = value as int + setBound(expressionToSaturate, value, columnLowerBounds, rowLowerBounds) + } + case CbcResult.SOLUTION_UNBOUNDED: { + expressionToSaturate.lowerBound = null + setBound(expressionToSaturate, Double.NEGATIVE_INFINITY, columnLowerBounds, rowLowerBounds) + } + case CbcResult.UNSAT: + return PolyhedronSaturationResult.EMPTY + case CbcResult.ABANDONED, + case CbcResult.TIMEOUT: + return PolyhedronSaturationResult.UNKNOWN + default: + throw new RuntimeException("Unknown CbcResult: " + minimizationResult) + } + for (var int j = 0; j < numDimensions; j++) { + val objectiveCoefficient = objective.get(j) + objective.set(j, -objectiveCoefficient) + } + val maximizationResult = CbcSolver.solve(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices, + entries, rowLowerBounds, rowUpperBounds, objective, timeoutSeconds, silent) + switch (maximizationResult) { + CbcResult.SolutionBounded: { + val value = Math.ceil(-maximizationResult.value) + expressionToSaturate.upperBound = value as int + setBound(expressionToSaturate, value, columnUpperBounds, rowUpperBounds) + } + case CbcResult.SOLUTION_UNBOUNDED: { + expressionToSaturate.upperBound = null + setBound(expressionToSaturate, Double.POSITIVE_INFINITY, columnUpperBounds, rowUpperBounds) + } + case CbcResult.UNSAT: + throw new RuntimeException("Minimization was SAT, but maximization is UNSAT") + case CbcResult.ABANDONED, + case CbcResult.TIMEOUT: + return PolyhedronSaturationResult.UNKNOWN + default: + throw new RuntimeException("Unknown CbcResult: " + maximizationResult) + } + } + PolyhedronSaturationResult.SATURATED + } + + private def toDouble(Integer nullableInt, double defaultValue) { + if (nullableInt === null) { + defaultValue + } else { + nullableInt.doubleValue + } + } + + private def int getIndex(Dimension dimension) { + val index = dimensionsToIndicesMap.get(dimension) + if (index === null) { + throw new IllegalArgumentException("Unknown dimension: " + dimension) + } + index + } + + private def void setBound(LinearBoundedExpression expression, double bound, double[] columnBounds, + double[] rowBounds) { + switch (expression) { + Dimension: { + val j = getIndex(expression) + columnBounds.set(j, bound) + } + LinearConstraint: { + val i = polyhedron.constraints.indexOf(expression) + if (i >= 0) { + rowBounds.set(i, bound) + } + } + } + } + + override close() throws Exception { + // Nothing to close + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend new file mode 100644 index 00000000..3d911bfb --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend @@ -0,0 +1,31 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.cardinality + +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.CbcPolyhedronSolver +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Dimension +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Polyhedron +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronSaturationResult +import org.junit.Test + +import static org.junit.Assert.* + +class CbcPolyhedronSolverTest extends PolyhedronSolverTest { + + override protected createSolver() { + new CbcPolyhedronSolver(10, false) + } + + @Test + def void timeoutTest() { + val solver = new CbcPolyhedronSolver(0, false) + val x = new Dimension("x", 0, 1) + val polyhedron = new Polyhedron(#[x], #[], #[x]) + val operator = solver.createSaturationOperator(polyhedron) + try { + val result = operator.saturate + + assertEquals(PolyhedronSaturationResult.UNKNOWN, result) + } finally { + operator.close() + } + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend new file mode 100644 index 00000000..789018cb --- /dev/null +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend @@ -0,0 +1,216 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.cardinality + +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Dimension +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.LinearConstraint +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Polyhedron +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronSaturationOperator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronSaturationResult +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronSolver +import org.junit.After +import org.junit.Before +import org.junit.Test + +import static org.junit.Assert.* + +abstract class PolyhedronSolverTest { + var PolyhedronSolver solver + var PolyhedronSaturationOperator operator + + protected def PolyhedronSolver createSolver() + + @Before + def void setUp() { + solver = createSolver() + } + + @After + def void tearDown() { + destroyOperatorIfExists() + } + + @Test + def void singleDimensionTest() { + val x = new Dimension("x", 0, 1) + createSaturationOperator(new Polyhedron(#[x], #[], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(1, x.upperBound) + } + + @Test + def void singleDimensionNegativeValueTest() { + val x = new Dimension("x", -2, -1) + createSaturationOperator(new Polyhedron(#[x], #[], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(-2, x.lowerBound) + assertEquals(-1, x.upperBound) + } + + @Test + def void singleDimensionConstraintTest() { + val x = new Dimension("x", null, null) + val constraint = new LinearConstraint(#{x -> 2}, 0, 2) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(1, x.upperBound) + } + + @Test + def void singleDimensionConstraintUnitCoefficientTest() { + val x = new Dimension("x", null, null) + val constraint = new LinearConstraint(#{x -> 1}, 1, 3) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(1, x.lowerBound) + assertEquals(3, x.upperBound) + } + + @Test + def void singleDimensionConstraintIntegerTest() { + val x = new Dimension("x", null, null) + val constraint = new LinearConstraint(#{x -> 2}, 0, 3) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(1, x.upperBound) + } + + @Test + def void singleDimensionUnboundedFromAboveTest() { + val x = new Dimension("x", 0, null) + createSaturationOperator(new Polyhedron(#[x], #[], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(null, x.upperBound) + } + + @Test + def void singleDimensionUnboundedFromBelowTest() { + val x = new Dimension("x", null, 0) + createSaturationOperator(new Polyhedron(#[x], #[], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(null, x.lowerBound) + assertEquals(0, x.upperBound) + } + + @Test + def void singleDimensionUnsatisfiableTest() { + val x = new Dimension("x", 0, 1) + val constraint = new LinearConstraint(#{x -> 2}, -2, -1) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.EMPTY, result) + } + + @Test + def void equalityConstraintTest() { + val x = new Dimension("x", null, null) + val y = new Dimension("y", 1, 2) + val constraint = new LinearConstraint(#{x -> 2, y -> 2}, 6, 6) + createSaturationOperator(new Polyhedron(#[x, y], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(1, x.lowerBound) + assertEquals(2, x.upperBound) + } + + @Test + def void saturateConstraintTest() { + val x = new Dimension("x", 0, 2) + val y = new Dimension("y", 1, 2) + val constraint = new LinearConstraint(#{x -> 2, y -> 1}, 0, 8) + createSaturationOperator(new Polyhedron(#[x, y], #[constraint], #[constraint])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(1, constraint.lowerBound) + assertEquals(6, constraint.upperBound) + } + + @Test(expected=IllegalArgumentException) + def void unknownVariableTest() { + val x = new Dimension("x", 0, 1) + val y = new Dimension("y", 0, 1) + val constraint = new LinearConstraint(#{y -> 2}, 0, 2) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + saturate() + } + + @Test + def void unsatisfiableMultipleInheritanceTest() { + val x = new Dimension("x", 0, 1) + val y = new Dimension("y", 0, 1) + val z = new Dimension("z", 0, 1) + createSaturationOperator(new Polyhedron( + #[x, y, z], + #[ + new LinearConstraint(#{x -> 1, y -> 1}, 1, 1), + new LinearConstraint(#{x -> 1, z -> 1}, 1, 1), + new LinearConstraint(#{y -> 1, z -> 1}, 1, 1) + ], + #[x, y, z] + )) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.EMPTY, result) + } + + @Test + def void unboundedRelaxationWithNoIntegerSolutionTest() { + val x = new Dimension("x", 0, 1) + val y = new Dimension("y", 0, null) + createSaturationOperator(new Polyhedron( + #[x, y], + #[new LinearConstraint(#{x -> 2}, 1, 1)], + #[x, y] + )) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.EMPTY, result) + } + + private def createSaturationOperator(Polyhedron polyhedron) { + destroyOperatorIfExists() + operator = solver.createSaturationOperator(polyhedron) + } + + private def destroyOperatorIfExists() { + if (operator !== null) { + operator.close + } + } + + private def saturate() { + operator.saturate + } +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend index 2d159752..b6d9b3b2 100644 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend @@ -1,199 +1,10 @@ package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.cardinality -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Dimension -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.LinearConstraint -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Polyhedron -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronSaturationOperator -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronSaturationResult import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Z3PolyhedronSolver -import org.junit.After -import org.junit.Before -import org.junit.Test -import static org.junit.Assert.* - -class Z3PolyhedronSolverTest { - var Z3PolyhedronSolver solver - var PolyhedronSaturationOperator operator - - @Before - def void setUp() { - solver = new Z3PolyhedronSolver(false) - } - - @After - def void tearDown() { - destroyOperatorIfExists() - } - - @Test - def void singleDimensionTest() { - val x = new Dimension("x", 0, 1) - createSaturationOperator(new Polyhedron(#[x], #[], #[x])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(0, x.lowerBound) - assertEquals(1, x.upperBound) - } - - @Test - def void singleDimensionNegativeValueTest() { - val x = new Dimension("x", -2, -1) - createSaturationOperator(new Polyhedron(#[x], #[], #[x])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(-2, x.lowerBound) - assertEquals(-1, x.upperBound) - } - - @Test - def void singleDimensionConstraintTest() { - val x = new Dimension("x", null, null) - val constraint = new LinearConstraint(#{x -> 2}, 0, 2) - createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(0, x.lowerBound) - assertEquals(1, x.upperBound) - } - - @Test - def void singleDimensionConstraintUnitCoefficientTest() { - val x = new Dimension("x", null, null) - val constraint = new LinearConstraint(#{x -> 1}, 1, 3) - createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(1, x.lowerBound) - assertEquals(3, x.upperBound) - } - - @Test - def void singleDimensionConstraintIntegerTest() { - val x = new Dimension("x", null, null) - val constraint = new LinearConstraint(#{x -> 2}, 0, 3) - createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(0, x.lowerBound) - assertEquals(1, x.upperBound) - } - - @Test - def void singleDimensionUnboundedFromAboveTest() { - val x = new Dimension("x", 0, null) - createSaturationOperator(new Polyhedron(#[x], #[], #[x])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(0, x.lowerBound) - assertEquals(null, x.upperBound) - } - - @Test - def void singleDimensionUnboundedFromBelowTest() { - val x = new Dimension("x", null, 0) - createSaturationOperator(new Polyhedron(#[x], #[], #[x])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(null, x.lowerBound) - assertEquals(0, x.upperBound) - } - - @Test - def void singleDimensionUnsatisfiableTest() { - val x = new Dimension("x", 0, 1) - val constraint = new LinearConstraint(#{x -> 2}, -2, -1) - createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.EMPTY, result) - } - - @Test - def void equalityConstraintTest() { - val x = new Dimension("x", null, null) - val y = new Dimension("y", 1, 2) - val constraint = new LinearConstraint(#{x -> 2, y -> 2}, 6, 6) - createSaturationOperator(new Polyhedron(#[x, y], #[constraint], #[x])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(1, x.lowerBound) - assertEquals(2, x.upperBound) - } - - @Test - def void saturateConstraintTest() { - val x = new Dimension("x", 0, 2) - val y = new Dimension("y", 1, 2) - val constraint = new LinearConstraint(#{x -> 2, y -> 1}, 0, 8) - createSaturationOperator(new Polyhedron(#[x, y], #[constraint], #[constraint])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(1, constraint.lowerBound) - assertEquals(6, constraint.upperBound) - } - - @Test(expected=IllegalArgumentException) - def void unknownVariableTest() { - val x = new Dimension("x", 0, 1) - val y = new Dimension("y", 0, 1) - val constraint = new LinearConstraint(#{y -> 2}, 0, 2) - createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) - - saturate() - } - - @Test - def void unsatisfiableMultipleInheritanceTest() { - val x = new Dimension("x", 0, 1) - val y = new Dimension("y", 0, 1) - val z = new Dimension("z", 0, 1) - createSaturationOperator(new Polyhedron( - #[x, y, z], - #[ - new LinearConstraint(#{x -> 1, y -> 1}, 1, 1), - new LinearConstraint(#{x -> 1, z -> 1}, 1, 1), - new LinearConstraint(#{y -> 1, z -> 1}, 1, 1) - ], - #[x, y, z] - )) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.EMPTY, result) - } - - private def createSaturationOperator(Polyhedron polyhedron) { - destroyOperatorIfExists() - operator = solver.createSaturationOperator(polyhedron) - } - - private def destroyOperatorIfExists() { - if (operator !== null) { - operator.close - } - } - - private def saturate() { - operator.saturate +class Z3PolyhedronSolverTest extends PolyhedronSolverTest { + + override protected createSolver() { + new Z3PolyhedronSolver(false) } } -- cgit v1.2.3-54-g00ecf From 64138e8d91bc8d7bb54d9b042f872b43550dec16 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Wed, 24 Jul 2019 10:59:02 +0200 Subject: Cardinality propagator WIP --- .../ModelGenerationMethodProvider.xtend | 12 +- .../MultiplicityGoalConstraintCalculator.xtend | 46 --- .../MultiplicityGoalConstraintCalculator.xtend | 46 +++ .../cardinality/PolyhedronScopePropagator.xtend | 355 +++++++++++++++++---- .../cardinality/PolyhedronSolver.xtend | 32 +- .../cardinality/RelationConstraintCalculator.xtend | 133 ++++++++ .../logic2viatra/cardinality/ScopePropagator.xtend | 5 - .../cardinality/ScopePropagatorStrategy.java | 18 ++ .../logic2viatra/patterns/PatternGenerator.xtend | 150 +++++---- .../logic2viatra/patterns/PatternProvider.xtend | 115 ++++--- .../patterns/RelationRefinementGenerator.xtend | 102 +++--- .../patterns/TypeRefinementGenerator.xtend | 4 +- .../logic2viatra/patterns/UnfinishedIndexer.xtend | 222 +++++++++---- .../rules/GoalConstraintProvider.xtend | 2 +- .../reasoner/ViatraReasonerConfiguration.xtend | 2 +- .../dse/UnfinishedMultiplicityObjective.xtend | 10 +- .../tests/cardinality/PolyhedronSolverTest.xtend | 28 +- 17 files changed, 902 insertions(+), 380 deletions(-) delete mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/MultiplicityGoalConstraintCalculator.xtend create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/MultiplicityGoalConstraintCalculator.xtend create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/RelationConstraintCalculator.xtend create mode 100644 Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagatorStrategy.java (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend index 0ceb5b2e..3a99d3bf 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/ModelGenerationMethodProvider.xtend @@ -5,7 +5,9 @@ import hu.bme.mit.inf.dslreasoner.logic.model.builder.DocumentationLevel import hu.bme.mit.inf.dslreasoner.logic.model.logicproblem.LogicProblem import hu.bme.mit.inf.dslreasoner.viatra2logic.viatra2logicannotations.TransfomedViatraQuery import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.CbcPolyhedronSolver +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.MultiplicityGoalConstraintCalculator import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.PolyhedronScopePropagator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.RelationConstraintCalculator import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagator import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagatorStrategy import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.GeneratedPatterns @@ -61,6 +63,7 @@ class ModelGenerationMethodProvider { val PatternProvider patternProvider = new PatternProvider val RefinementRuleProvider refinementRuleProvider = new RefinementRuleProvider val GoalConstraintProvider goalConstraintProvider = new GoalConstraintProvider + val relationConstraintCalculator = new RelationConstraintCalculator def ModelGenerationMethod createModelGenerationMethod( LogicProblem logicProblem, @@ -77,8 +80,9 @@ class ModelGenerationMethodProvider { val Set existingQueries = logicProblem.relations.map[annotations].flatten.filter(TransfomedViatraQuery). map[it.patternPQuery as PQuery].toSet + val relationConstraints = relationConstraintCalculator.calculateRelationConstraints(logicProblem) val queries = patternProvider.generateQueries(logicProblem, emptySolution, statistics, existingQueries, - workspace, typeInferenceMethod, writeFiles) + workspace, typeInferenceMethod, scopePropagatorStrategy, relationConstraints, writeFiles) val scopePropagator = createScopePropagator(scopePropagatorStrategy, emptySolution, queries) scopePropagator.propagateAllScopeConstraints val // LinkedHashMap, BatchTransformationRule>> @@ -117,10 +121,12 @@ class ModelGenerationMethodProvider { switch (scopePropagatorStrategy) { case BasicTypeHierarchy: new ScopePropagator(emptySolution) - case PolyhedralTypeHierarchy: { + case PolyhedralTypeHierarchy, + case PolyhedralRelations: { val types = queries.refineObjectQueries.keySet.map[newType].toSet val solver = new CbcPolyhedronSolver - new PolyhedronScopePropagator(emptySolution, types, solver) + new PolyhedronScopePropagator(emptySolution, types, queries.multiplicityConstraintQueries, solver, + scopePropagatorStrategy.requiresUpperBoundIndexing) } default: throw new IllegalArgumentException("Unknown scope propagator strategy: " + scopePropagatorStrategy) diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/MultiplicityGoalConstraintCalculator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/MultiplicityGoalConstraintCalculator.xtend deleted file mode 100644 index 4b9629df..00000000 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/MultiplicityGoalConstraintCalculator.xtend +++ /dev/null @@ -1,46 +0,0 @@ -package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra - -import org.eclipse.emf.common.notify.Notifier -import org.eclipse.viatra.query.runtime.api.IQuerySpecification -import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine -import org.eclipse.viatra.query.runtime.api.ViatraQueryMatcher -import org.eclipse.viatra.query.runtime.emf.EMFScope - -class MultiplicityGoalConstraintCalculator { - val String targetRelationName; - val IQuerySpecification querySpecification; - var ViatraQueryMatcher matcher; - - new(String targetRelationName, IQuerySpecification querySpecification) { - this.targetRelationName = targetRelationName - this.querySpecification = querySpecification - this.matcher = null - } - - new(MultiplicityGoalConstraintCalculator other) { - this.targetRelationName = other.targetRelationName - this.querySpecification = other.querySpecification - this.matcher = null - } - - def getName() { - targetRelationName - } - - def init(Notifier notifier) { - val engine = ViatraQueryEngine.on(new EMFScope(notifier)) - matcher = querySpecification.getMatcher(engine) - } - - def calculateValue() { - var res = 0 - val allMatches = this.matcher.allMatches - for(match : allMatches) { - //println(targetRelationName+ " missing multiplicity: "+match.get(3)) - val missingMultiplicity = match.get(4) as Integer - res += missingMultiplicity - } - //println(targetRelationName+ " all missing multiplicities: "+res) - return res - } -} \ No newline at end of file diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/MultiplicityGoalConstraintCalculator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/MultiplicityGoalConstraintCalculator.xtend new file mode 100644 index 00000000..86a59aa1 --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/MultiplicityGoalConstraintCalculator.xtend @@ -0,0 +1,46 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality + +import org.eclipse.emf.common.notify.Notifier +import org.eclipse.viatra.query.runtime.api.IQuerySpecification +import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine +import org.eclipse.viatra.query.runtime.api.ViatraQueryMatcher +import org.eclipse.viatra.query.runtime.emf.EMFScope + +class MultiplicityGoalConstraintCalculator { + val String targetRelationName; + val IQuerySpecification querySpecification; + var ViatraQueryMatcher matcher; + + new(String targetRelationName, IQuerySpecification querySpecification) { + this.targetRelationName = targetRelationName + this.querySpecification = querySpecification + this.matcher = null + } + + new(MultiplicityGoalConstraintCalculator other) { + this.targetRelationName = other.targetRelationName + this.querySpecification = other.querySpecification + this.matcher = null + } + + def getName() { + targetRelationName + } + + def init(Notifier notifier) { + val engine = ViatraQueryEngine.on(new EMFScope(notifier)) + matcher = querySpecification.getMatcher(engine) + } + + def calculateValue() { + var res = 0 + val allMatches = this.matcher.allMatches + for(match : allMatches) { + //println(targetRelationName+ " missing multiplicity: "+match.get(3)) + val missingMultiplicity = match.get(2) as Integer + res += missingMultiplicity + } + //println(targetRelationName+ " all missing multiplicities: "+res) + return res + } +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend index cebd89da..4f0c8f20 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronScopePropagator.xtend @@ -2,90 +2,60 @@ package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap +import com.google.common.collect.Maps import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.Type +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.UnifinishedMultiplicityQueries import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialComplexTypeInterpretation import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialInterpretation import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialPrimitiveInterpretation import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.Scope import java.util.ArrayDeque +import java.util.ArrayList import java.util.HashMap import java.util.HashSet +import java.util.List import java.util.Map import java.util.Set +import javax.naming.OperationNotSupportedException +import org.eclipse.viatra.query.runtime.api.IPatternMatch +import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine +import org.eclipse.viatra.query.runtime.api.ViatraQueryMatcher +import org.eclipse.viatra.query.runtime.emf.EMFScope +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor class PolyhedronScopePropagator extends ScopePropagator { val Map scopeBounds - val LinearConstraint topLevelBounds + val LinearBoundedExpression topLevelBounds + val Polyhedron polyhedron val PolyhedronSaturationOperator operator + List updaters = emptyList - new(PartialInterpretation p, Set possibleNewDynamicTypes, PolyhedronSolver solver) { + new(PartialInterpretation p, Set possibleNewDynamicTypes, + Map unfinishedMultiplicityQueries, + PolyhedronSolver solver, boolean propagateRelations) { super(p) - val instanceCounts = possibleNewDynamicTypes.toInvertedMap[new Dimension(name, 0, null)] - val primitiveDimensions = new HashMap - val constraintsBuilder = ImmutableList.builder - val scopeBoundsBuilder = ImmutableMap.builder - // Dimensions for instantiable types were created according to the type analysis, - // but for any possible primitive types, we create them on demand, - // as there is no Type directly associated with a PartialPrimitiveInterpretation. - // Below we will assume that each PartialTypeInterpretation has at most one Scope. - for (scope : p.scopes) { - switch (targetTypeInterpretation : scope.targetTypeInterpretation) { - PartialPrimitiveInterpretation: { - val dimension = primitiveDimensions.computeIfAbsent(targetTypeInterpretation) [ interpretation | - new Dimension(interpretation.eClass.name, 0, null) - ] - scopeBoundsBuilder.put(scope, dimension) - } - PartialComplexTypeInterpretation: { - val complexType = targetTypeInterpretation.interpretationOf - val dimensions = findSubtypeDimensions(complexType, instanceCounts) - switch (dimensions.size) { - case 0: - if (scope.minNewElements > 0) { - throw new IllegalArgumentException("Found scope for " + complexType.name + - ", but the type cannot be instantiated") - } - case 1: - scopeBoundsBuilder.put(scope, dimensions.head) - default: { - val constraint = new LinearConstraint(dimensions.toInvertedMap[1], null, null) - constraintsBuilder.add(constraint) - scopeBoundsBuilder.put(scope, constraint) - } - } - } - default: - throw new IllegalArgumentException("Unknown PartialTypeInterpretation: " + targetTypeInterpretation) - } - } - val allDimensions = ImmutableList.builder.addAll(instanceCounts.values).addAll(primitiveDimensions.values).build - scopeBounds = scopeBoundsBuilder.build - topLevelBounds = new LinearConstraint(allDimensions.toInvertedMap[1], null, null) - constraintsBuilder.add(topLevelBounds) - val expressionsToSaturate = ImmutableList.builder.addAll(scopeBounds.values).add(topLevelBounds).build - val polyhedron = new Polyhedron(allDimensions, constraintsBuilder.build, expressionsToSaturate) + val builder = new PolyhedronBuilder(p) + builder.buildPolyhedron(possibleNewDynamicTypes) + scopeBounds = builder.scopeBounds + topLevelBounds = builder.topLevelBounds + polyhedron = builder.polyhedron operator = solver.createSaturationOperator(polyhedron) - } - - private def findSubtypeDimensions(Type type, Map instanceCounts) { - val subtypes = new HashSet - val dimensions = new HashSet - val stack = new ArrayDeque - stack.addLast(type) - while (!stack.empty) { - val subtype = stack.removeLast - if (subtypes.add(subtype)) { - val dimension = instanceCounts.get(subtype) - if (dimension !== null) { - dimensions.add(dimension) - } - stack.addAll(subtype.subtypes) + if (propagateRelations) { + propagateAllScopeConstraints() + val maximumNumberOfNewNodes = topLevelBounds.upperBound + if (maximumNumberOfNewNodes === null) { + throw new IllegalStateException("Could not determine maximum number of new nodes, it may be unbounded") + } + if (maximumNumberOfNewNodes <= 0) { + throw new IllegalStateException("Maximum number of new nodes is negative") } + builder.buildMultiplicityConstraints(unfinishedMultiplicityQueries, maximumNumberOfNewNodes) + updaters = builder.updaters } - dimensions } override void propagateAllScopeConstraints() { + resetBounds() populatePolyhedronFromScope() val result = operator.saturate() if (result == PolyhedronSaturationResult.EMPTY) { @@ -96,21 +66,36 @@ class PolyhedronScopePropagator extends ScopePropagator { super.propagateAllScopeConstraints() } } + // println(polyhedron) + } + + def resetBounds() { + for (dimension : polyhedron.dimensions) { + dimension.lowerBound = 0 + dimension.upperBound = null + } + for (constraint : polyhedron.constraints) { + constraint.lowerBound = null + constraint.upperBound = null + } } private def populatePolyhedronFromScope() { - topLevelBounds.lowerBound = partialInterpretation.minNewElements + topLevelBounds.tightenLowerBound(partialInterpretation.minNewElements) if (partialInterpretation.maxNewElements >= 0) { - topLevelBounds.upperBound = partialInterpretation.maxNewElements + topLevelBounds.tightenUpperBound(partialInterpretation.maxNewElements) } for (pair : scopeBounds.entrySet) { val scope = pair.key val bounds = pair.value - bounds.lowerBound = scope.minNewElements + bounds.tightenLowerBound(scope.minNewElements) if (scope.maxNewElements >= 0) { - bounds.upperBound = scope.maxNewElements + bounds.tightenUpperBound(scope.maxNewElements) } } + for (updater : updaters) { + updater.update(partialInterpretation) + } } private def populateScopesFromPolyhedron() { @@ -151,4 +136,242 @@ class PolyhedronScopePropagator extends ScopePropagator { throw new IllegalArgumentException("Infinite upper bound: " + bounds) } } + + private static def getCalculatedMultiplicity(ViatraQueryMatcher matcher, + PartialInterpretation p) { + val match = matcher.newEmptyMatch + match.set(0, p.problem) + match.set(1, p) + val iterator = matcher.streamAllMatches(match).iterator + if (!iterator.hasNext) { + return null + } + val value = iterator.next.get(2) as Integer + if (iterator.hasNext) { + throw new IllegalArgumentException("Multiplicity calculation query has more than one match") + } + value + } + + @FinalFieldsConstructor + private static class PolyhedronBuilder { + static val INFINITY_SCALE = 10 + + val PartialInterpretation p + + Map instanceCounts + Map> subtypeDimensions + Map, LinearBoundedExpression> expressionsCache + Map typeBounds + int infinity + ViatraQueryEngine queryEngine + ImmutableList.Builder updatersBuilder + + Map scopeBounds + LinearBoundedExpression topLevelBounds + Polyhedron polyhedron + List updaters + + def buildPolyhedron(Set possibleNewDynamicTypes) { + instanceCounts = possibleNewDynamicTypes.toInvertedMap[new Dimension(name, 0, null)] + val types = p.problem.types + expressionsCache = Maps.newHashMapWithExpectedSize(types.size) + subtypeDimensions = types.toInvertedMap[findSubtypeDimensions.toInvertedMap[1]] + typeBounds = ImmutableMap.copyOf(subtypeDimensions.mapValues[toExpression]) + scopeBounds = buildScopeBounds + topLevelBounds = instanceCounts.values.toInvertedMap[1].toExpression + val dimensions = ImmutableList.copyOf(instanceCounts.values) + val expressionsToSaturate = ImmutableList.copyOf(scopeBounds.values) + polyhedron = new Polyhedron(dimensions, new ArrayList, expressionsToSaturate) + addCachedConstraintsToPolyhedron() + } + + def buildMultiplicityConstraints( + Map constraints, + int maximumNuberOfNewNodes) { + infinity = maximumNuberOfNewNodes * INFINITY_SCALE + queryEngine = ViatraQueryEngine.on(new EMFScope(p)) + updatersBuilder = ImmutableList.builder + for (pair : constraints.entrySet.filter[key.containment].groupBy[key.targetType].entrySet) { + buildContainmentConstraints(pair.key, pair.value) + } + for (pair : constraints.entrySet) { + val constraint = pair.key + if (!constraint.containment) { + buildNonContainmentConstraints(constraint, pair.value) + } + } + updaters = updatersBuilder.build + addCachedConstraintsToPolyhedron() + } + + private def addCachedConstraintsToPolyhedron() { + val constraints = new HashSet + constraints.addAll(expressionsCache.values.filter(LinearConstraint)) + constraints.removeAll(polyhedron.constraints) + polyhedron.constraints.addAll(constraints) + } + + private def buildContainmentConstraints(Type containedType, + List> constraints) { + val typeCoefficients = subtypeDimensions.get(containedType) + val orphansLowerBoundCoefficients = new HashMap(typeCoefficients) + val orphansUpperBoundCoefficients = new HashMap(typeCoefficients) + val unfinishedMultiplicitiesMatchersBuilder = ImmutableList.builder + val remainingContentsQueriesBuilder = ImmutableList.builder + for (pair : constraints) { + val constraint = pair.key + val containerCoefficients = subtypeDimensions.get(constraint.sourceType) + if (constraint.isUpperBoundFinite) { + orphansLowerBoundCoefficients.addCoefficients(-constraint.upperBound, containerCoefficients) + } else { + orphansLowerBoundCoefficients.addCoefficients(-infinity, containerCoefficients) + } + orphansUpperBoundCoefficients.addCoefficients(-constraint.lowerBound, containerCoefficients) + val queries = pair.value + if (constraint.constrainsUnfinished) { + if (queries.unfinishedMultiplicityQuery === null) { + throw new IllegalArgumentException( + "Containment constraints need unfinished multiplicity queries") + } + unfinishedMultiplicitiesMatchersBuilder.add( + queries.unfinishedMultiplicityQuery.getMatcher(queryEngine)) + } + if (queries.remainingContentsQuery === null) { + throw new IllegalArgumentException("Containment constraints need remaining contents queries") + } + remainingContentsQueriesBuilder.add(queries.remainingContentsQuery.getMatcher(queryEngine)) + } + val orphanLowerBound = orphansLowerBoundCoefficients.toExpression + val orphanUpperBound = orphansUpperBoundCoefficients.toExpression + val updater = new ContainmentConstraintUpdater(containedType.name, orphanLowerBound, orphanUpperBound, + unfinishedMultiplicitiesMatchersBuilder.build, remainingContentsQueriesBuilder.build) + updatersBuilder.add(updater) + } + + private def buildNonContainmentConstraints(RelationMultiplicityConstraint constraint, + UnifinishedMultiplicityQueries queries) { + } + + private def addCoefficients(Map accumulator, int scale, Map a) { + for (pair : a.entrySet) { + val dimension = pair.key + val currentValue = accumulator.get(pair.key) ?: 0 + val newValue = currentValue + scale * pair.value + if (newValue == 0) { + accumulator.remove(dimension) + } else { + accumulator.put(dimension, newValue) + } + } + } + + private def findSubtypeDimensions(Type type) { + val subtypes = new HashSet + val dimensions = new HashSet + val stack = new ArrayDeque + stack.addLast(type) + while (!stack.empty) { + val subtype = stack.removeLast + if (subtypes.add(subtype)) { + val dimension = instanceCounts.get(subtype) + if (dimension !== null) { + dimensions.add(dimension) + } + stack.addAll(subtype.subtypes) + } + } + dimensions + } + + private def toExpression(Map coefficients) { + expressionsCache.computeIfAbsent(coefficients) [ c | + if (c.size == 1 && c.entrySet.head.value == 1) { + c.entrySet.head.key + } else { + new LinearConstraint(c, null, null) + } + ] + } + + private def buildScopeBounds() { + val scopeBoundsBuilder = ImmutableMap.builder + for (scope : p.scopes) { + switch (targetTypeInterpretation : scope.targetTypeInterpretation) { + PartialPrimitiveInterpretation: + throw new OperationNotSupportedException("Primitive type scopes are not yet implemented") + PartialComplexTypeInterpretation: { + val complexType = targetTypeInterpretation.interpretationOf + val typeBound = typeBounds.get(complexType) + if (typeBound === null) { + if (scope.minNewElements > 0) { + throw new IllegalArgumentException("Found scope for " + complexType.name + + ", but the type cannot be instantiated") + } + } else { + scopeBoundsBuilder.put(scope, typeBound) + } + } + default: + throw new IllegalArgumentException("Unknown PartialTypeInterpretation: " + + targetTypeInterpretation) + } + } + scopeBoundsBuilder.build + } + } + + private static interface RelationConstraintUpdater { + def void update(PartialInterpretation p) + } + + @FinalFieldsConstructor + static class ContainmentConstraintUpdater implements RelationConstraintUpdater { + val String name + val LinearBoundedExpression orphansLowerBound + val LinearBoundedExpression orphansUpperBound + val List> unfinishedMultiplicitiesMatchers + val List> remainingContentsQueries + + override update(PartialInterpretation p) { + tightenLowerBound(p) + tightenUpperBound(p) + } + + private def tightenLowerBound(PartialInterpretation p) { + var int sum = 0 + for (matcher : remainingContentsQueries) { + val value = matcher.getCalculatedMultiplicity(p) + if (value === null) { + throw new IllegalArgumentException("Remaining contents count is missing for " + name) + } + if (value == -1) { + // Infinite upper bound, no need to tighten. + return + } + sum += value + } + orphansLowerBound.tightenUpperBound(sum) + } + + private def tightenUpperBound(PartialInterpretation p) { + var int sum = 0 + for (matcher : unfinishedMultiplicitiesMatchers) { + val value = matcher.getCalculatedMultiplicity(p) + if (value === null) { + throw new IllegalArgumentException("Unfinished multiplicity is missing for " + name) + } + sum += value + } + orphansUpperBound.tightenLowerBound(sum) + } + } + + @FinalFieldsConstructor + static class ContainmentRootConstraintUpdater implements RelationConstraintUpdater { + + override update(PartialInterpretation p) { + throw new UnsupportedOperationException("TODO: auto-generated method stub") + } + } } diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronSolver.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronSolver.xtend index 08bf25b9..9c6cb82e 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronSolver.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/PolyhedronSolver.xtend @@ -52,18 +52,14 @@ class Polyhedron { val List expressionsToSaturate override toString() ''' - Dimensions: - «FOR dimension : dimensions» - «dimension» - «ENDFOR» - Constraints: - «FOR constraint : constraints» - «constraint» - «ENDFOR» -««« Saturate: -««« «FOR expression : expressionsToSaturate» -««« «IF expression instanceof Dimension»dimension«ELSEIF expression instanceof LinearConstraint»constraint«ELSE»unknown«ENDIF» «expression» -««« «ENDFOR» + Dimensions: + «FOR dimension : dimensions» + «dimension» + «ENDFOR» + Constraints: + «FOR constraint : constraints» + «constraint» + «ENDFOR» ''' } @@ -72,6 +68,18 @@ class Polyhedron { abstract class LinearBoundedExpression { var Integer lowerBound var Integer upperBound + + def void tightenLowerBound(Integer tighterBound) { + if (lowerBound === null || (tighterBound !== null && lowerBound < tighterBound)) { + lowerBound = tighterBound + } + } + + def void tightenUpperBound(Integer tighterBound) { + if (upperBound === null || (tighterBound !== null && upperBound > tighterBound)) { + upperBound = tighterBound + } + } } @Accessors diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/RelationConstraintCalculator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/RelationConstraintCalculator.xtend new file mode 100644 index 00000000..ffa9e6e6 --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/RelationConstraintCalculator.xtend @@ -0,0 +1,133 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSet +import hu.bme.mit.inf.dslreasoner.ecore2logic.ecore2logicannotations.InverseRelationAssertion +import hu.bme.mit.inf.dslreasoner.ecore2logic.ecore2logicannotations.LowerMultiplicityAssertion +import hu.bme.mit.inf.dslreasoner.ecore2logic.ecore2logicannotations.UpperMultiplicityAssertion +import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.ComplexTypeReference +import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.Relation +import hu.bme.mit.inf.dslreasoner.logic.model.logicproblem.LogicProblem +import java.util.HashMap +import java.util.List +import org.eclipse.xtend.lib.annotations.Data + +@Data +class RelationConstraints { + val List multiplicityConstraints +} + +@Data +class RelationMultiplicityConstraint { + Relation relation + boolean containment + boolean container + int lowerBound + int upperBound + int inverseUpperBound + + def isUpperBoundFinite() { + upperBound >= 0 + } + + private def isInverseUpperBoundFinite() { + inverseUpperBound >= 0 + } + + private def canHaveMultipleSourcesPerTarget() { + inverseUpperBound != 1 + } + + def constrainsUnfinished() { + lowerBound >= 1 && (!container || lowerBound >= 2) + } + + def constrainsUnrepairable() { + constrainsUnfinished && canHaveMultipleSourcesPerTarget + } + + def constrainsRemainingInverse() { + !containment && inverseUpperBoundFinite + } + + def constrainsRemainingContents() { + containment + } + + def isActive() { + constrainsUnfinished || constrainsUnrepairable || constrainsRemainingInverse || constrainsRemainingContents + } + + def getSourceType() { + getParamType(0) + } + + def getTargetType() { + getParamType(1) + } + + private def getParamType(int i) { + val parameters = relation.parameters + if (i < parameters.size) { + val firstParam = parameters.get(i) + if (firstParam instanceof ComplexTypeReference) { + return firstParam.referred + } + } + throw new IllegalArgumentException("Constraint with unknown source type") + } +} + +class RelationConstraintCalculator { + def calculateRelationConstraints(LogicProblem problem) { + val containmentRelations = switch (problem.containmentHierarchies.size) { + case 0: + emptySet + case 1: + ImmutableSet.copyOf(problem.containmentHierarchies.head.containmentRelations) + default: + throw new IllegalArgumentException("Only a single containment hierarchy is supported") + } + val inverseRelations = new HashMap + val lowerMultiplicities = new HashMap + val upperMultiplicities = new HashMap + for (relation : problem.relations) { + lowerMultiplicities.put(relation, 0) + upperMultiplicities.put(relation, -1) + } + for (annotation : problem.annotations) { + switch (annotation) { + InverseRelationAssertion: { + inverseRelations.put(annotation.inverseA, annotation.inverseB) + inverseRelations.put(annotation.inverseB, annotation.inverseA) + } + LowerMultiplicityAssertion: + lowerMultiplicities.put(annotation.relation, annotation.lower) + UpperMultiplicityAssertion: + upperMultiplicities.put(annotation.relation, annotation.upper) + } + } + val multiplicityConstraintsBuilder = ImmutableList.builder() + for (relation : problem.relations) { + val containment = containmentRelations.contains(relation) + val lowerMultiplicity = lowerMultiplicities.get(relation) + val upperMultiplicity = upperMultiplicities.get(relation) + var container = false + var inverseUpperMultiplicity = -1 + val inverseRelation = inverseRelations.get(relation) + if (inverseRelation !== null) { + inverseUpperMultiplicity = upperMultiplicities.get(relation) + container = containmentRelations.contains(inverseRelation) + } + val constraint = new RelationMultiplicityConstraint(relation, containment, container, lowerMultiplicity, + upperMultiplicity, inverseUpperMultiplicity) + if (constraint.isActive) { + if (relation.parameters.size != 2) { + throw new IllegalArgumentException('''Relation «relation.name» has multiplicity or containment constraints, but it is not binary''') + } + multiplicityConstraintsBuilder.add(constraint) + } + } + new RelationConstraints(multiplicityConstraintsBuilder.build) + } +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagator.xtend index f0494214..3b442cd3 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagator.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagator.xtend @@ -10,11 +10,6 @@ import java.util.Map import java.util.Set import org.eclipse.xtend.lib.annotations.Accessors -enum ScopePropagatorStrategy { - BasicTypeHierarchy, - PolyhedralTypeHierarchy -} - class ScopePropagator { @Accessors(PROTECTED_GETTER) PartialInterpretation partialInterpretation Map type2Scope diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagatorStrategy.java b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagatorStrategy.java new file mode 100644 index 00000000..b1c5a658 --- /dev/null +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/ScopePropagatorStrategy.java @@ -0,0 +1,18 @@ +package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality; + +public enum ScopePropagatorStrategy { + BasicTypeHierarchy, + + PolyhedralTypeHierarchy, + + PolyhedralRelations { + @Override + public boolean requiresUpperBoundIndexing() { + return true; + } + }; + + public boolean requiresUpperBoundIndexing() { + return false; + } +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternGenerator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternGenerator.xtend index 24b3e870..1b0db90e 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternGenerator.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternGenerator.xtend @@ -1,7 +1,6 @@ package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns import hu.bme.mit.inf.dslreasoner.ecore2logic.ecore2logicannotations.InverseRelationAssertion -import hu.bme.mit.inf.dslreasoner.ecore2logic.ecore2logicannotations.LowerMultiplicityAssertion import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.BoolTypeReference import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.IntTypeReference import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.RealTypeReference @@ -17,6 +16,7 @@ import hu.bme.mit.inf.dslreasoner.viatra2logic.viatra2logicannotations.Transform import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.Modality import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.TypeAnalysisResult import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.TypeInferenceMethod +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.RelationConstraints import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialInterpretation import java.util.HashMap import java.util.Map @@ -26,22 +26,26 @@ import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery import org.eclipse.xtend.lib.annotations.Accessors import static extension hu.bme.mit.inf.dslreasoner.util.CollectionsUtil.* +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagatorStrategy class PatternGenerator { - @Accessors(PUBLIC_GETTER) val TypeIndexer typeIndexer //= new TypeIndexer(this) - @Accessors(PUBLIC_GETTER) val RelationDeclarationIndexer relationDeclarationIndexer = new RelationDeclarationIndexer(this) - @Accessors(PUBLIC_GETTER) val RelationDefinitionIndexer relationDefinitionIndexer = new RelationDefinitionIndexer(this) + @Accessors(PUBLIC_GETTER) val TypeIndexer typeIndexer // = new TypeIndexer(this) + @Accessors(PUBLIC_GETTER) val RelationDeclarationIndexer relationDeclarationIndexer = new RelationDeclarationIndexer( + this) + @Accessors(PUBLIC_GETTER) val RelationDefinitionIndexer relationDefinitionIndexer = new RelationDefinitionIndexer( + this) @Accessors(PUBLIC_GETTER) val ContainmentIndexer containmentIndexer = new ContainmentIndexer(this) @Accessors(PUBLIC_GETTER) val InvalidIndexer invalidIndexer = new InvalidIndexer(this) - @Accessors(PUBLIC_GETTER) val UnfinishedIndexer unfinishedIndexer = new UnfinishedIndexer(this) - @Accessors(PUBLIC_GETTER) val TypeRefinementGenerator typeRefinementGenerator //= new RefinementGenerator(this) - @Accessors(PUBLIC_GETTER) val RelationRefinementGenerator relationRefinementGenerator = new RelationRefinementGenerator(this) - - public new(TypeInferenceMethod typeInferenceMethod) { - if(typeInferenceMethod == TypeInferenceMethod.Generic) { + @Accessors(PUBLIC_GETTER) val UnfinishedIndexer unfinishedIndexer + @Accessors(PUBLIC_GETTER) val TypeRefinementGenerator typeRefinementGenerator // = new RefinementGenerator(this) + @Accessors(PUBLIC_GETTER) val RelationRefinementGenerator relationRefinementGenerator = new RelationRefinementGenerator( + this) + + new(TypeInferenceMethod typeInferenceMethod, ScopePropagatorStrategy scopePropagatorStrategy) { + if (typeInferenceMethod == TypeInferenceMethod.Generic) { this.typeIndexer = new GenericTypeIndexer(this) this.typeRefinementGenerator = new GenericTypeRefinementGenerator(this) - } else if(typeInferenceMethod == TypeInferenceMethod.PreliminaryAnalysis) { + } else if (typeInferenceMethod == TypeInferenceMethod.PreliminaryAnalysis) { this.typeIndexer = new TypeIndexerWithPreliminaryTypeAnalysis(this) this.typeRefinementGenerator = new TypeRefinementWithPreliminaryTypeAnalysis(this) } else { @@ -49,112 +53,100 @@ class PatternGenerator { this.typeRefinementGenerator = null throw new IllegalArgumentException('''Unknown type indexing technique : «typeInferenceMethod.name»''') } + this.unfinishedIndexer = new UnfinishedIndexer(this, scopePropagatorStrategy.requiresUpperBoundIndexing) } - - public def requiresTypeAnalysis() { + + def requiresTypeAnalysis() { typeIndexer.requiresTypeAnalysis || typeRefinementGenerator.requiresTypeAnalysis } - - public dispatch def CharSequence referRelation( - RelationDeclaration referred, - String sourceVariable, - String targetVariable, - Modality modality, - Map fqn2PQuery) - { - return this.relationDeclarationIndexer.referRelation(referred,sourceVariable,targetVariable,modality) + + dispatch def CharSequence referRelation(RelationDeclaration referred, String sourceVariable, String targetVariable, + Modality modality, Map fqn2PQuery) { + return this.relationDeclarationIndexer.referRelation(referred, sourceVariable, targetVariable, modality) } - public dispatch def CharSequence referRelation( - RelationDefinition referred, - String sourceVariable, - String targetVariable, - Modality modality, - Map fqn2PQuery) - { - val pattern = referred.annotations.filter(TransfomedViatraQuery).head.patternFullyQualifiedName.lookup(fqn2PQuery) - return this.relationDefinitionIndexer.referPattern(pattern,#[sourceVariable,targetVariable],modality,true,false) + + dispatch def CharSequence referRelation(RelationDefinition referred, String sourceVariable, String targetVariable, + Modality modality, Map fqn2PQuery) { + val pattern = referred.annotations.filter(TransfomedViatraQuery).head.patternFullyQualifiedName.lookup( + fqn2PQuery) + return this.relationDefinitionIndexer.referPattern(pattern, #[sourceVariable, targetVariable], modality, true, + false) } - - def public referRelationByName(EReference reference, - String sourceVariable, - String targetVariable, - Modality modality) - { - '''find «modality.name.toLowerCase»InRelation«canonizeName('''«reference.name» reference «reference.EContainingClass.name»''') - »(problem,interpretation,«sourceVariable»,«targetVariable»);''' + + def referRelationByName(EReference reference, String sourceVariable, String targetVariable, Modality modality) { + '''find «modality.name.toLowerCase»InRelation«canonizeName('''«reference.name» reference «reference.EContainingClass.name»''')»(problem,interpretation,«sourceVariable»,«targetVariable»);''' } - - def public CharSequence referAttributeByName(EAttribute attribute, - String sourceVariable, - String targetVariable, - Modality modality) - { - '''find «modality.name.toLowerCase»InRelation«canonizeName('''«attribute.name» attribute «attribute.EContainingClass.name»''') - »(problem,interpretation,«sourceVariable»,«targetVariable»);''' + + def CharSequence referAttributeByName(EAttribute attribute, String sourceVariable, String targetVariable, + Modality modality) { + '''find «modality.name.toLowerCase»InRelation«canonizeName('''«attribute.name» attribute «attribute.EContainingClass.name»''')»(problem,interpretation,«sourceVariable»,«targetVariable»);''' } - - public def canonizeName(String name) { + + def canonizeName(String name) { name.split(' ').join('_') } - - public def lowerMultiplicities(LogicProblem problem) { - problem.assertions.map[annotations].flatten.filter(LowerMultiplicityAssertion).filter[!it.relation.isDerived] - } - public def wfQueries(LogicProblem problem) { - problem.assertions.map[it.annotations] - .flatten - .filter(TransformedViatraWellformednessConstraint) - .map[it.query] + + def wfQueries(LogicProblem problem) { + problem.assertions.map[it.annotations].flatten.filter(TransformedViatraWellformednessConstraint).map[it.query] } - public def getContainments(LogicProblem p) { + + def getContainments(LogicProblem p) { return p.containmentHierarchies.head.containmentRelations } - public def getInverseRelations(LogicProblem p) { + + def getInverseRelations(LogicProblem p) { val inverseRelations = new HashMap - p.annotations.filter(InverseRelationAssertion).forEach[ - inverseRelations.put(it.inverseA,it.inverseB) - inverseRelations.put(it.inverseB,it.inverseA) + p.annotations.filter(InverseRelationAssertion).forEach [ + inverseRelations.put(it.inverseA, it.inverseB) + inverseRelations.put(it.inverseB, it.inverseA) ] return inverseRelations } - public def isRepresentative(Relation relation, Relation inverse) { - if(inverse == null) { + + def isRepresentative(Relation relation, Relation inverse) { + if (inverse === null) { return true } else { - relation.name.compareTo(inverse.name)<1 + relation.name.compareTo(inverse.name) < 1 } } - - public def isDerived(Relation relation) { + + def isDerived(Relation relation) { relation.annotations.exists[it instanceof DefinedByDerivedFeature] } - public def getDerivedDefinition(RelationDeclaration relation) { + + def getDerivedDefinition(RelationDeclaration relation) { relation.annotations.filter(DefinedByDerivedFeature).head.query } - + private def allTypeReferences(LogicProblem problem) { problem.eAllContents.filter(TypeReference).toIterable } + protected def hasBoolean(LogicProblem problem) { problem.allTypeReferences.exists[it instanceof BoolTypeReference] } + protected def hasInteger(LogicProblem problem) { problem.allTypeReferences.exists[it instanceof IntTypeReference] } + protected def hasReal(LogicProblem problem) { problem.allTypeReferences.exists[it instanceof RealTypeReference] } + protected def hasString(LogicProblem problem) { problem.allTypeReferences.exists[it instanceof StringTypeReference] } - - public def transformBaseProperties( + + def transformBaseProperties( LogicProblem problem, PartialInterpretation emptySolution, - Map fqn2PQuery, - TypeAnalysisResult typeAnalysisResult + Map fqn2PQuery, + TypeAnalysisResult typeAnalysisResult, + RelationConstraints constraints ) { - + return ''' import epackage "http://www.bme.hu/mit/inf/dslreasoner/viatrasolver/partialinterpretationlanguage" import epackage "http://www.bme.hu/mit/inf/dslreasoner/logic/model/problem" @@ -188,7 +180,7 @@ class PatternGenerator { private pattern elementCloseWorld(element:DefinedElement) { PartialInterpretation.openWorldElements(i,element); - PartialInterpretation.maxNewElements(i,0); + PartialInterpretation.maxNewElements(i,0); } or { Scope.targetTypeInterpretation(scope,interpretation); PartialTypeInterpratation.elements(interpretation,element); @@ -221,7 +213,7 @@ class PatternGenerator { ////////// // 1.1.1 primitive Type Indexers ////////// -««« pattern instanceofBoolean(problem:LogicProblem, interpretation:PartialInterpretation, element:DefinedElement) { + ««« pattern instanceofBoolean(problem:LogicProblem, interpretation:PartialInterpretation, element:DefinedElement) { ««« find interpretation(problem,interpretation); ««« PartialInterpretation.booleanelements(interpretation,element); ««« } @@ -279,7 +271,7 @@ class PatternGenerator { ////////// // 3.1 Unfinishedness Measured by Multiplicity ////////// - «unfinishedIndexer.generateUnfinishedMultiplicityQueries(problem,fqn2PQuery)» + «unfinishedIndexer.generateUnfinishedMultiplicityQueries(constraints.multiplicityConstraints,fqn2PQuery)» ////////// // 3.2 Unfinishedness Measured by WF Queries @@ -302,6 +294,6 @@ class PatternGenerator { // 4.3 Relation refinement ////////// «relationRefinementGenerator.generateRefineReference(problem)» - ''' + ''' } } diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternProvider.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternProvider.xtend index e87f52af..90f79810 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternProvider.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/PatternProvider.xtend @@ -10,6 +10,8 @@ import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.ModelGenerationStati import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.TypeAnalysis import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.TypeAnalysisResult import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.TypeInferenceMethod +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.RelationConstraints +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.RelationMultiplicityConstraint import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.util.ParseUtil import hu.bme.mit.inf.dslreasoner.viatrasolver.partialinterpretationlanguage.partialinterpretation.PartialInterpretation import hu.bme.mit.inf.dslreasoner.workspace.ReasonerWorkspace @@ -23,78 +25,96 @@ import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery import org.eclipse.xtend.lib.annotations.Data import static extension hu.bme.mit.inf.dslreasoner.util.CollectionsUtil.* +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.ScopePropagatorStrategy -@Data class GeneratedPatterns { - public Map>> invalidWFQueries - public Map>> unfinishedWFQueries - public Map>> unfinishedMulticiplicityQueries - public Map>> refineObjectQueries - public Map>> refineTypeQueries - public Map, IQuerySpecification>> refinerelationQueries +@Data +class GeneratedPatterns { + public Map>> invalidWFQueries + public Map>> unfinishedWFQueries + public Map multiplicityConstraintQueries + public Map>> unfinishedMulticiplicityQueries + public Map>> refineObjectQueries + public Map>> refineTypeQueries + public Map, IQuerySpecification>> refinerelationQueries public Map modalRelationQueries public Collection>> allQueries } -@Data class ModalPatternQueries { +@Data +class ModalPatternQueries { val IQuerySpecification> mayQuery val IQuerySpecification> mustQuery val IQuerySpecification> currentQuery } +@Data +class UnifinishedMultiplicityQueries { + val IQuerySpecification> unfinishedMultiplicityQuery + val IQuerySpecification> unrepairableMultiplicityQuery + val IQuerySpecification> remainingInverseMultiplicityQuery + val IQuerySpecification> remainingContentsQuery +} + class PatternProvider { - + val TypeAnalysis typeAnalysis = new TypeAnalysis - - public def generateQueries( - LogicProblem problem, - PartialInterpretation emptySolution, - ModelGenerationStatistics statistics, - Set existingQueries, - ReasonerWorkspace workspace, - TypeInferenceMethod typeInferenceMethod, - boolean writeToFile) - { + + def generateQueries(LogicProblem problem, PartialInterpretation emptySolution, ModelGenerationStatistics statistics, + Set existingQueries, ReasonerWorkspace workspace, TypeInferenceMethod typeInferenceMethod, + ScopePropagatorStrategy scopePropagatorStrategy, RelationConstraints relationConstraints, boolean writeToFile) { val fqn2Query = existingQueries.toMap[it.fullyQualifiedName] - val PatternGenerator patternGenerator = new PatternGenerator(typeInferenceMethod) - val typeAnalysisResult = if(patternGenerator.requiresTypeAnalysis) { - val startTime = System.nanoTime - val result = typeAnalysis.performTypeAnalysis(problem,emptySolution) - val typeAnalysisTime = System.nanoTime - startTime - statistics.PreliminaryTypeAnalisisTime = typeAnalysisTime - result - } else { - null - } - val baseIndexerFile = patternGenerator.transformBaseProperties(problem,emptySolution,fqn2Query,typeAnalysisResult) - if(writeToFile) { - workspace.writeText('''generated3valued.vql_deactivated''',baseIndexerFile) + val PatternGenerator patternGenerator = new PatternGenerator(typeInferenceMethod, scopePropagatorStrategy) + val typeAnalysisResult = if (patternGenerator.requiresTypeAnalysis) { + val startTime = System.nanoTime + val result = typeAnalysis.performTypeAnalysis(problem, emptySolution) + val typeAnalysisTime = System.nanoTime - startTime + statistics.PreliminaryTypeAnalisisTime = typeAnalysisTime + result + } else { + null + } + val baseIndexerFile = patternGenerator.transformBaseProperties(problem, emptySolution, fqn2Query, + typeAnalysisResult, relationConstraints) + if (writeToFile) { + workspace.writeText('''generated3valued.vql_deactivated''', baseIndexerFile) } val ParseUtil parseUtil = new ParseUtil val generatedQueries = parseUtil.parse(baseIndexerFile) - val runtimeQueries = calclulateRuntimeQueries(patternGenerator,problem,emptySolution,typeAnalysisResult,generatedQueries); + val runtimeQueries = calclulateRuntimeQueries(patternGenerator, problem, emptySolution, typeAnalysisResult, + relationConstraints, generatedQueries) return runtimeQueries } - + private def GeneratedPatterns calclulateRuntimeQueries( PatternGenerator patternGenerator, LogicProblem problem, PartialInterpretation emptySolution, TypeAnalysisResult typeAnalysisResult, + RelationConstraints relationConstraints, Map>> queries ) { - val Map>> - invalidWFQueries = patternGenerator.invalidIndexer.getInvalidateByWfQueryNames(problem).mapValues[it.lookup(queries)] - val Map>> - unfinishedWFQueries = patternGenerator.unfinishedIndexer.getUnfinishedWFQueryNames(problem).mapValues[it.lookup(queries)] - val Map>> - unfinishedMultiplicityQueries = patternGenerator.unfinishedIndexer.getUnfinishedMultiplicityQueries(problem).mapValues[it.lookup(queries)] - val Map>> - refineObjectsQueries = patternGenerator.typeRefinementGenerator.getRefineObjectQueryNames(problem,emptySolution,typeAnalysisResult).mapValues[it.lookup(queries)] - val Map>> - refineTypeQueries = patternGenerator.typeRefinementGenerator.getRefineTypeQueryNames(problem,emptySolution,typeAnalysisResult).mapValues[it.lookup(queries)] - val Map, IQuerySpecification>> - refineRelationQueries = patternGenerator.relationRefinementGenerator.getRefineRelationQueries(problem).mapValues[it.lookup(queries)] - val Map modalRelationQueries = problem.relations.filter(RelationDefinition).toMap([it], [ relationDefinition | + val invalidWFQueries = patternGenerator.invalidIndexer.getInvalidateByWfQueryNames(problem).mapValues [ + it.lookup(queries) + ] + val unfinishedWFQueries = patternGenerator.unfinishedIndexer.getUnfinishedWFQueryNames(problem).mapValues [ + it.lookup(queries) + ] + val multiplicityConstraintQueries = patternGenerator.unfinishedIndexer.getUnfinishedMultiplicityQueries( + relationConstraints.multiplicityConstraints).mapValues [ + new UnifinishedMultiplicityQueries(unfinishedMultiplicityQueryName?.lookup(queries), + unrepairableMultiplicityQueryName?.lookup(queries), + remainingInverseMultiplicityQueryName?.lookup(queries), remainingContentsQueryName?.lookup(queries)) + ] + val unfinishedMultiplicityQueries = multiplicityConstraintQueries.entrySet.filter [ + value.unfinishedMultiplicityQuery !== null + ].toMap([key.relation], [value.unfinishedMultiplicityQuery]) + val refineObjectsQueries = patternGenerator.typeRefinementGenerator. + getRefineObjectQueryNames(problem, emptySolution, typeAnalysisResult).mapValues[it.lookup(queries)] + val refineTypeQueries = patternGenerator.typeRefinementGenerator.getRefineTypeQueryNames(problem, emptySolution, + typeAnalysisResult).mapValues[it.lookup(queries)] + val refineRelationQueries = patternGenerator.relationRefinementGenerator.getRefineRelationQueries(problem). + mapValues[it.lookup(queries)] + val modalRelationQueries = problem.relations.filter(RelationDefinition).toMap([it], [ relationDefinition | val indexer = patternGenerator.relationDefinitionIndexer new ModalPatternQueries( indexer.relationDefinitionName(relationDefinition, Modality.MAY).lookup(queries), @@ -105,6 +125,7 @@ class PatternProvider { return new GeneratedPatterns( invalidWFQueries, unfinishedWFQueries, + multiplicityConstraintQueries, unfinishedMultiplicityQueries, refineObjectsQueries, refineTypeQueries, diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/RelationRefinementGenerator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/RelationRefinementGenerator.xtend index f9e9baea..fa73c861 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/RelationRefinementGenerator.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/RelationRefinementGenerator.xtend @@ -9,77 +9,71 @@ import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.ComplexTypeReference class RelationRefinementGenerator { PatternGenerator base; + public new(PatternGenerator base) { this.base = base } - - def CharSequence generateRefineReference(LogicProblem p) { - return ''' - «FOR relationRefinement: this.getRelationRefinements(p)» - pattern «relationRefinementQueryName(relationRefinement.key,relationRefinement.value)»( - problem:LogicProblem, interpretation:PartialInterpretation, - relationIterpretation:PartialRelationInterpretation«IF relationRefinement.value != null», oppositeInterpretation:PartialRelationInterpretation«ENDIF», - from: DefinedElement, to: DefinedElement) - { - find interpretation(problem,interpretation); - PartialInterpretation.partialrelationinterpretation(interpretation,relationIterpretation); - PartialRelationInterpretation.interpretationOf.name(relationIterpretation,"«relationRefinement.key.name»"); - «IF relationRefinement.value != null» - PartialInterpretation.partialrelationinterpretation(interpretation,oppositeInterpretation); - PartialRelationInterpretation.interpretationOf.name(oppositeInterpretation,"«relationRefinement.value.name»"); - «ENDIF» - find mustExist(problem, interpretation, from); - find mustExist(problem, interpretation, to); - «base.typeIndexer.referInstanceOfByReference(relationRefinement.key.parameters.get(0), Modality::MUST,"from")» - «base.typeIndexer.referInstanceOfByReference(relationRefinement.key.parameters.get(1), Modality::MUST,"to")» - «base.relationDeclarationIndexer.referRelation(relationRefinement.key,"from","to",Modality.MAY)» - neg «base.relationDeclarationIndexer.referRelation(relationRefinement.key,"from","to",Modality.MUST)» - } + + def CharSequence generateRefineReference(LogicProblem p) ''' + «FOR relationRefinement : this.getRelationRefinements(p)» + pattern «relationRefinementQueryName(relationRefinement.key,relationRefinement.value)»( + problem:LogicProblem, interpretation:PartialInterpretation, + relationIterpretation:PartialRelationInterpretation«IF relationRefinement.value !== null», oppositeInterpretation:PartialRelationInterpretation«ENDIF», + from: DefinedElement, to: DefinedElement) + { + find interpretation(problem,interpretation); + PartialInterpretation.partialrelationinterpretation(interpretation,relationIterpretation); + PartialRelationInterpretation.interpretationOf.name(relationIterpretation,"«relationRefinement.key.name»"); + «IF relationRefinement.value !== null» + PartialInterpretation.partialrelationinterpretation(interpretation,oppositeInterpretation); + PartialRelationInterpretation.interpretationOf.name(oppositeInterpretation,"«relationRefinement.value.name»"); + «ENDIF» + find mustExist(problem, interpretation, from); + find mustExist(problem, interpretation, to); + «base.typeIndexer.referInstanceOfByReference(relationRefinement.key.parameters.get(0), Modality::MUST,"from")» + «base.typeIndexer.referInstanceOfByReference(relationRefinement.key.parameters.get(1), Modality::MUST,"to")» + «base.relationDeclarationIndexer.referRelation(relationRefinement.key,"from","to",Modality.MAY)» + neg «base.relationDeclarationIndexer.referRelation(relationRefinement.key,"from","to",Modality.MUST)» + } «ENDFOR» - ''' - } - + ''' + def String relationRefinementQueryName(RelationDeclaration relation, Relation inverseRelation) { - '''«IF inverseRelation != null - »refineRelation_«base.canonizeName(relation.name)»_and_«base.canonizeName(inverseRelation.name)»« - ELSE - »refineRelation_«base.canonizeName(relation.name)»«ENDIF»''' + '''«IF inverseRelation !== null»refineRelation_«base.canonizeName(relation.name)»_and_«base.canonizeName(inverseRelation.name)»«ELSE»refineRelation_«base.canonizeName(relation.name)»«ENDIF»''' } - + def referRefinementQuery(RelationDeclaration relation, Relation inverseRelation, String relInterpretationName, - String inverseInterpretationName, String sourceName, String targetName) - '''find «this.relationRefinementQueryName(relation,inverseRelation)»(problem, interpretation, «relInterpretationName», «IF inverseRelation != null»inverseInterpretationName, «ENDIF»«sourceName», «targetName»);''' - + String inverseInterpretationName, String sourceName, + String targetName) '''find «this.relationRefinementQueryName(relation,inverseRelation)»(problem, interpretation, «relInterpretationName», «IF inverseRelation !== null»inverseInterpretationName, «ENDIF»«sourceName», «targetName»);''' + def getRefineRelationQueries(LogicProblem p) { // val containmentRelations = p.containmentHierarchies.map[containmentRelations].flatten.toSet // p.relations.filter(RelationDeclaration).filter[!containmentRelations.contains(it)].toInvertedMap['''refineRelation_«base.canonizeName(it.name)»'''] /* - val res = new LinkedHashMap - for(relation: getRelationRefinements(p)) { - if(inverseRelations.containsKey(relation)) { - val name = '''refineRelation_«base.canonizeName(relation.name)»_and_«base.canonizeName(inverseRelations.get(relation).name)»''' - res.put(relation -> inverseRelations.get(relation),name) - } else { - val name = '''refineRelation_«base.canonizeName(relation.name)»''' - res.put(relation -> null,name) - } - } - return res*/ - - getRelationRefinements(p).toInvertedMap[relationRefinementQueryName(it.key,it.value)] + * val res = new LinkedHashMap + * for(relation: getRelationRefinements(p)) { + * if(inverseRelations.containsKey(relation)) { + * val name = '''refineRelation_«base.canonizeName(relation.name)»_and_«base.canonizeName(inverseRelations.get(relation).name)»''' + * res.put(relation -> inverseRelations.get(relation),name) + * } else { + * val name = '''refineRelation_«base.canonizeName(relation.name)»''' + * res.put(relation -> null,name) + * } + * } + return res*/ + getRelationRefinements(p).toInvertedMap[relationRefinementQueryName(it.key, it.value)] } - def getRelationRefinements(LogicProblem p) { val inverses = base.getInverseRelations(p) val containments = base.getContainments(p) val list = new LinkedList - for(relation : p.relations.filter(RelationDeclaration)) { - if(!containments.contains(relation)) { - if(inverses.containsKey(relation)) { + for (relation : p.relations.filter(RelationDeclaration)) { + if (!containments.contains(relation)) { + if (inverses.containsKey(relation)) { val inverse = inverses.get(relation) - if(!containments.contains(inverse)) { - if(base.isRepresentative(relation,inverse)) { + if (!containments.contains(inverse)) { + if (base.isRepresentative(relation, inverse)) { list += (relation -> inverse) } } @@ -90,4 +84,4 @@ class RelationRefinementGenerator { } return list } -} \ No newline at end of file +} diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/TypeRefinementGenerator.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/TypeRefinementGenerator.xtend index 7e3fad91..ee7299cd 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/TypeRefinementGenerator.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/TypeRefinementGenerator.xtend @@ -86,8 +86,8 @@ abstract class TypeRefinementGenerator { } protected def String patternName(Relation containmentRelation, Relation inverseContainment, Type newType) { - if(containmentRelation != null) { - if(inverseContainment != null) { + if(containmentRelation !== null) { + if(inverseContainment !== null) { '''createObject_«base.canonizeName(newType.name)»_by_«base.canonizeName(containmentRelation.name)»_with_«base.canonizeName(inverseContainment.name)»''' } else { '''createObject_«base.canonizeName(newType.name)»_by_«base.canonizeName(containmentRelation.name)»''' diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/UnfinishedIndexer.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/UnfinishedIndexer.xtend index ad1c9033..286756a8 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/UnfinishedIndexer.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/patterns/UnfinishedIndexer.xtend @@ -1,85 +1,195 @@ package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns -import hu.bme.mit.inf.dslreasoner.ecore2logic.ecore2logicannotations.LowerMultiplicityAssertion import hu.bme.mit.inf.dslreasoner.logic.model.logicproblem.LogicProblem -import hu.bme.mit.inf.dslreasoner.viatra2logic.viatra2logicannotations.TransformedViatraWellformednessConstraint +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.Modality +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.RelationMultiplicityConstraint +import java.util.LinkedHashMap +import java.util.List import java.util.Map import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery +import org.eclipse.xtend.lib.annotations.Data import static extension hu.bme.mit.inf.dslreasoner.util.CollectionsUtil.* -import java.util.LinkedHashMap -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.Modality -import hu.bme.mit.inf.dslreasoner.logic.model.logiclanguage.ComplexTypeReference + +@Data +class UnifinishedMultiplicityQueryNames { + val String unfinishedMultiplicityQueryName + val String unrepairableMultiplicityQueryName + val String remainingInverseMultiplicityQueryName + val String remainingContentsQueryName +} class UnfinishedIndexer { val PatternGenerator base - - new(PatternGenerator patternGenerator) { + val boolean indexUpperMultiplicities + + new(PatternGenerator patternGenerator, boolean indexUpperMultiplicities) { this.base = patternGenerator + this.indexUpperMultiplicities = indexUpperMultiplicities } - - def generateUnfinishedWfQueries(LogicProblem problem, Map fqn2PQuery) { + + def generateUnfinishedWfQueries(LogicProblem problem, Map fqn2PQuery) { val wfQueries = base.wfQueries(problem) ''' - «FOR wfQuery: wfQueries» - pattern unfinishedBy_«base.canonizeName(wfQuery.target.name)»(problem:LogicProblem, interpretation:PartialInterpretation, - «FOR param : wfQuery.patternFullyQualifiedName.lookup(fqn2PQuery).parameters SEPARATOR ', '»var_«param.name»«ENDFOR») - { - «base.relationDefinitionIndexer.referPattern( + «FOR wfQuery : wfQueries» + pattern unfinishedBy_«base.canonizeName(wfQuery.target.name)»(problem:LogicProblem, interpretation:PartialInterpretation, + «FOR param : wfQuery.patternFullyQualifiedName.lookup(fqn2PQuery).parameters SEPARATOR ', '»var_«param.name»«ENDFOR») + { + «base.relationDefinitionIndexer.referPattern( wfQuery.patternFullyQualifiedName.lookup(fqn2PQuery), wfQuery.patternFullyQualifiedName.lookup(fqn2PQuery).parameters.map['''var_«it.name»'''], Modality.CURRENT, true,false)» - } - «ENDFOR» + } + «ENDFOR» ''' } + def getUnfinishedWFQueryNames(LogicProblem problem) { val wfQueries = base.wfQueries(problem) val map = new LinkedHashMap - for(wfQuery : wfQueries) { - map.put(wfQuery.target,'''unfinishedBy_«base.canonizeName(wfQuery.target.name)»''') + for (wfQuery : wfQueries) { + map.put(wfQuery.target, '''unfinishedBy_«base.canonizeName(wfQuery.target.name)»''') } return map } - def generateUnfinishedMultiplicityQueries(LogicProblem problem, Map fqn2PQuery) { - val lowerMultiplicities = base.lowerMultiplicities(problem) - return ''' - «FOR lowerMultiplicity : lowerMultiplicities» - pattern «unfinishedMultiplicityName(lowerMultiplicity)»(problem:LogicProblem, interpretation:PartialInterpretation, relationIterpretation:PartialRelationInterpretation, object:DefinedElement,missingMultiplicity) { - find interpretation(problem,interpretation); - PartialInterpretation.partialrelationinterpretation(interpretation,relationIterpretation); - PartialRelationInterpretation.interpretationOf.name(relationIterpretation,"«lowerMultiplicity.relation.name»"); - «base.typeIndexer.referInstanceOf(lowerMultiplicity.firstParamTypeOfRelation,Modality::MUST,"object")» - numberOfExistingReferences == count «base.referRelation(lowerMultiplicity.relation,"object","_",Modality.MUST,fqn2PQuery)» - check(numberOfExistingReferences < «lowerMultiplicity.lower»); - missingMultiplicity == eval(«lowerMultiplicity.lower»-numberOfExistingReferences); - } + + def generateUnfinishedMultiplicityQueries(List constraints, + Map fqn2PQuery) ''' + «FOR constraint : constraints» + «IF constraint.constrainsUnfinished» + private pattern «unfinishedMultiplicityName(constraint)»_helper(problem:LogicProblem, interpretation:PartialInterpretation, object:DefinedElement, missingMultiplicity:java Integer) { + find interpretation(problem,interpretation); + find mustExist(problem,interpretation,object); + «base.typeIndexer.referInstanceOf(constraint.sourceType,Modality::MUST,"object")» + numberOfExistingReferences == count «base.referRelation(constraint.relation,"object","_",Modality.MUST,fqn2PQuery)» + check(numberOfExistingReferences < «constraint.lowerBound»); + missingMultiplicity == eval(«constraint.lowerBound»-numberOfExistingReferences); + } + + pattern «unfinishedMultiplicityName(constraint)»(problem:LogicProblem, interpretation:PartialInterpretation, missingMultiplicity:java Integer) { + find interpretation(problem,interpretation); + missingMultiplicity == sum find «unfinishedMultiplicityName(constraint)»_helper(problem, interpretation, _, #_); + } + «ENDIF» + + «IF indexUpperMultiplicities» + «IF constraint.constrainsUnrepairable» + private pattern «repairMatchName(constraint)»(problem:LogicProblem, interpretation:PartialInterpretation, source:DefinedElement, target:DefinedElement) { + find interpretation(problem,interpretation); + find mustExist(problem,interpretation,source); + «base.typeIndexer.referInstanceOf(constraint.sourceType,Modality::MUST,"source")» + find mustExist(problem,interpretation,target); + «base.typeIndexer.referInstanceOf(constraint.targetType,Modality::MUST,"target")» + neg «base.referRelation(constraint.relation,"source","target",Modality.MUST,fqn2PQuery)» + «base.referRelation(constraint.relation,"source","target",Modality.MAY,fqn2PQuery)» + } + + private pattern «unrepairableMultiplicityName(constraint)»_helper(problem:LogicProblem, interpretation:PartialInterpretation, object:DefinedElement, unrepairableMultiplicity:java Integer) { + find interpretation(problem,interpretation); + find mustExist(problem,interpretation,object); + «base.typeIndexer.referInstanceOf(constraint.sourceType,Modality::MUST,"object")» + find «unfinishedMultiplicityName(constraint)»_helper(problem, interpretation, object, missingMultiplicity); + numerOfRepairMatches == count find «repairMatchName(constraint)»(problem, interpretation, object, _); + check(numerOfRepairMatches < missingMultiplicity); + unrepairableMultiplicity == eval(missingMultiplicity-numerOfRepairMatches); + } + + private pattern «unrepairableMultiplicityName(constraint)»(problem:LogicProblem, interpretation:PartialInterpretation, unrepairableMultiplicity:java Integer) { + find interpretation(problem,interpretation); + unrepairableMultiplicity == max find «unrepairableMultiplicityName(constraint)»_helper(problem, interpretation, _, #_); + } or { + find interpretation(problem,interpretation); + neg find «unrepairableMultiplicityName(constraint)»_helper(problem, interpretation, _, _); + unrepairableMultiplicity == 0; + } + «ENDIF» + + «IF constraint.constrainsRemainingInverse» + private pattern «remainingMultiplicityName(constraint)»_helper(problem:LogicProblem, interpretation:PartialInterpretation, object:DefinedElement, remainingMultiplicity:java Integer) { + find interpretation(problem,interpretation); + find mustExist(problem,interpretation,object); + «base.typeIndexer.referInstanceOf(constraint.targetType,Modality::MUST,"object")» + numberOfExistingReferences == count «base.referRelation(constraint.relation,"_","object",Modality.MUST,fqn2PQuery)» + check(numberOfExistingReferences < «constraint.inverseUpperBound»); + remainingMultiplicity == eval(«constraint.inverseUpperBound»-numberOfExistingReferences); + } + + pattern «remainingMultiplicityName(constraint)»(problem:LogicProblem, interpretation:PartialInterpretation, remainingMultiplicity:java Integer) { + find interpretation(problem,interpretation); + remainingMultiplicity == sum find «remainingMultiplicityName(constraint)»_helper(problem, interpretation, _, #_); + } + «ENDIF» + + «IF constraint.constrainsRemainingContents» + «IF constraint.upperBoundFinite» + private pattern «remainingContentsName(constraint)»_helper(problem:LogicProblem, interpretation:PartialInterpretation, object:DefinedElement, remainingMultiplicity:java Integer) { + find interpretation(problem,interpretation); + find mustExist(problem,interpretation,object); + «base.typeIndexer.referInstanceOf(constraint.sourceType,Modality::MUST,"object")» + numberOfExistingReferences == count «base.referRelation(constraint.relation,"object","_",Modality.MUST,fqn2PQuery)» + check(numberOfExistingReferences < «constraint.upperBound»); + remainingMultiplicity == eval(«constraint.upperBound»-numberOfExistingReferences); + } + + pattern «remainingContentsName(constraint)»(problem:LogicProblem, interpretation:PartialInterpretation, remainingMultiplicity:java Integer) { + find interpretation(problem,interpretation); + remainingMultiplicity == sum find «remainingContentsName(constraint)»_helper(problem, interpretation, _, #_); + } + «ELSE» + pattern «remainingContentsName(constraint)»_helper(problem:LogicProblem, interpretation:PartialInterpretation) { + find interpretation(problem,interpretation); + find mustExist(problem,interpretation,object); + «base.typeIndexer.referInstanceOf(constraint.sourceType,Modality::MUST,"object")» + } + + pattern «remainingContentsName(constraint)»(problem:LogicProblem, interpretation:PartialInterpretation, remainingMultiplicity:java Integer) { + find interpretation(problem,interpretation); + find «remainingContentsName(constraint)»_helper(problem, interpretation); + remainingMultiplicity == -1; + } or { + find interpretation(problem,interpretation); + neg find «remainingContentsName(constraint)»_helper(problem, interpretation); + remainingMultiplicity == 0; + } + «ENDIF» + «ENDIF» + «ENDIF» «ENDFOR» - ''' - } - def String unfinishedMultiplicityName(LowerMultiplicityAssertion lowerMultiplicityAssertion) - '''unfinishedLowerMultiplicity_«base.canonizeName(lowerMultiplicityAssertion.relation.name)»''' - - def public referUnfinishedMultiplicityQuery(LowerMultiplicityAssertion lowerMultiplicityAssertion) - '''find «unfinishedMultiplicityName(lowerMultiplicityAssertion)»(problem, interpretation ,object, missingMultiplicity);''' - - def getFirstParamTypeOfRelation(LowerMultiplicityAssertion lowerMultiplicityAssertion) { - val parameters = lowerMultiplicityAssertion.relation.parameters - if(parameters.size == 2) { - val firstParam = parameters.get(0) - if(firstParam instanceof ComplexTypeReference) { - return firstParam.referred - } - } - } - - def getUnfinishedMultiplicityQueries(LogicProblem problem) { - val lowerMultiplicities = base.lowerMultiplicities(problem) - val map = new LinkedHashMap - for(lowerMultiplicity : lowerMultiplicities) { - map.put(lowerMultiplicity.relation,unfinishedMultiplicityName(lowerMultiplicity)) - } - return map + ''' + + def String unfinishedMultiplicityName( + RelationMultiplicityConstraint constraint) '''unfinishedLowerMultiplicity_«base.canonizeName(constraint.relation.name)»''' + + def String unrepairableMultiplicityName( + RelationMultiplicityConstraint constraint) '''unrepairableLowerMultiplicity_«base.canonizeName(constraint.relation.name)»''' + + private def String repairMatchName( + RelationMultiplicityConstraint constraint) '''repair_«base.canonizeName(constraint.relation.name)»''' + + def String remainingMultiplicityName( + RelationMultiplicityConstraint constraint) '''remainingInverseUpperMultiplicity_«base.canonizeName(constraint.relation.name)»''' + + def String remainingContentsName( + RelationMultiplicityConstraint constraint) '''remainingContents_«base.canonizeName(constraint.relation.name)»''' + + def getUnfinishedMultiplicityQueries(List constraints) { + constraints.toInvertedMap [ constraint | + new UnifinishedMultiplicityQueryNames( + if(constraint.constrainsUnfinished) unfinishedMultiplicityName(constraint) else null, + if (indexUpperMultiplicities && constraint.constrainsUnrepairable) + unrepairableMultiplicityName(constraint) + else + null, + if (indexUpperMultiplicities && constraint.constrainsRemainingInverse) + remainingMultiplicityName(constraint) + else + null, + if (indexUpperMultiplicities && constraint.constrainsRemainingContents) + remainingContentsName(constraint) + else + null + ) + ] } } diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/GoalConstraintProvider.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/GoalConstraintProvider.xtend index e1be2742..b6fdbe06 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/GoalConstraintProvider.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/rules/GoalConstraintProvider.xtend @@ -1,6 +1,6 @@ package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.rules -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.MultiplicityGoalConstraintCalculator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.MultiplicityGoalConstraintCalculator import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.patterns.GeneratedPatterns import java.util.ArrayList diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasonerConfiguration.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasonerConfiguration.xtend index 7a3a2d67..3c9ef74c 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasonerConfiguration.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/ViatraReasonerConfiguration.xtend @@ -51,7 +51,7 @@ class ViatraReasonerConfiguration extends LogicSolverConfiguration { */ public var SearchSpaceConstraint searchSpaceConstraints = new SearchSpaceConstraint - public var ScopePropagatorStrategy scopePropagatorStrategy = ScopePropagatorStrategy.PolyhedralTypeHierarchy + public var ScopePropagatorStrategy scopePropagatorStrategy = ScopePropagatorStrategy.PolyhedralRelations public var List costObjectives = newArrayList } diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/dse/UnfinishedMultiplicityObjective.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/dse/UnfinishedMultiplicityObjective.xtend index 7d0a7884..9f0c642f 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/dse/UnfinishedMultiplicityObjective.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner/src/hu/bme/mit/inf/dslreasoner/viatrasolver/reasoner/dse/UnfinishedMultiplicityObjective.xtend @@ -1,10 +1,10 @@ package hu.bme.mit.inf.dslreasoner.viatrasolver.reasoner.dse -import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.MultiplicityGoalConstraintCalculator +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.MultiplicityGoalConstraintCalculator import java.util.Comparator import org.eclipse.viatra.dse.base.ThreadContext -import org.eclipse.viatra.dse.objectives.IObjective import org.eclipse.viatra.dse.objectives.Comparators +import org.eclipse.viatra.dse.objectives.IObjective class UnfinishedMultiplicityObjective implements IObjective { val MultiplicityGoalConstraintCalculator unfinishedMultiplicity; @@ -29,9 +29,9 @@ class UnfinishedMultiplicityObjective implements IObjective { override satisifiesHardObjective(Double fitness) { return fitness <=0.01 } override setComparator(Comparator comparator) { - throw new UnsupportedOperationException("TODO: auto-generated method stub") + throw new UnsupportedOperationException } override setLevel(int level) { - throw new UnsupportedOperationException("TODO: auto-generated method stub") + throw new UnsupportedOperationException } -} \ No newline at end of file +} diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend index 789018cb..15758985 100644 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend @@ -17,7 +17,7 @@ abstract class PolyhedronSolverTest { var PolyhedronSaturationOperator operator protected def PolyhedronSolver createSolver() - + @Before def void setUp() { solver = createSolver() @@ -183,7 +183,7 @@ abstract class PolyhedronSolverTest { assertEquals(PolyhedronSaturationResult.EMPTY, result) } - + @Test def void unboundedRelaxationWithNoIntegerSolutionTest() { val x = new Dimension("x", 0, 1) @@ -193,7 +193,29 @@ abstract class PolyhedronSolverTest { #[new LinearConstraint(#{x -> 2}, 1, 1)], #[x, y] )) - + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.EMPTY, result) + } + + @Test + def void emptyConstraintTest() { + val constraint = new LinearConstraint(emptyMap, 0, 1) + createSaturationOperator(new Polyhedron(#[], #[constraint], #[constraint])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, constraint.lowerBound) + assertEquals(0, constraint.upperBound) + } + + @Test + def void emptyConstraintUnsatisfiableTest() { + val constraint = new LinearConstraint(emptyMap, 1, 0) + createSaturationOperator(new Polyhedron(#[], #[constraint], #[constraint])) + val result = saturate() assertEquals(PolyhedronSaturationResult.EMPTY, result) -- cgit v1.2.3-54-g00ecf From 80077d1e7dc34767929b0709919793e740dbd45f Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 25 Jul 2019 20:08:26 +0200 Subject: Parse rational numbers in Z3PolyhedronSolver --- .../cardinality/Z3PolyhedronSolver.xtend | 47 +++++++- .../cardinality/CbcPolyhedronSolverTest.xtend | 17 +-- .../tests/cardinality/PolyhedronSolverTest.xtend | 130 +++++++++++++++------ .../tests/cardinality/Z3PolyhedronSolverTest.xtend | 11 +- 4 files changed, 160 insertions(+), 45 deletions(-) (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/Z3PolyhedronSolver.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/Z3PolyhedronSolver.xtend index c8759a46..23444956 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/Z3PolyhedronSolver.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/Z3PolyhedronSolver.xtend @@ -1,41 +1,54 @@ package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality +import com.microsoft.z3.AlgebraicNum import com.microsoft.z3.ArithExpr import com.microsoft.z3.Context import com.microsoft.z3.Expr import com.microsoft.z3.IntNum import com.microsoft.z3.Optimize +import com.microsoft.z3.RatNum import com.microsoft.z3.Status import com.microsoft.z3.Symbol +import java.math.BigDecimal +import java.math.MathContext +import java.math.RoundingMode import java.util.Map import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor class Z3PolyhedronSolver implements PolyhedronSolver { val boolean lpRelaxation + val double timeoutSeconds @FinalFieldsConstructor new() { } new() { - this(true) + this(false, -1) } override createSaturationOperator(Polyhedron polyhedron) { - new Z3SaturationOperator(polyhedron, lpRelaxation) + new Z3SaturationOperator(polyhedron, lpRelaxation, timeoutSeconds) } } class Z3SaturationOperator extends AbstractPolyhedronSaturationOperator { static val INFINITY_SYMBOL_NAME = "oo" static val MULT_SYMBOL_NAME = "*" + static val TIMEOUT_SYMBOL_NAME = "timeout" + static val INTEGER_PRECISION = new BigDecimal(Integer.MAX_VALUE).precision + static val ROUND_DOWN = new MathContext(INTEGER_PRECISION, RoundingMode.FLOOR) + static val ROUND_UP = new MathContext(INTEGER_PRECISION, RoundingMode.CEILING) + // The interval isolating the number is smaller than 1/10^precision. + static val ALGEBRAIC_NUMBER_ROUNDING = 0 extension val Context context val Symbol infinitySymbol val Symbol multSymbol val Map variables + val int timeoutMilliseconds - new(Polyhedron polyhedron, boolean lpRelaxation) { + new(Polyhedron polyhedron, boolean lpRelaxation, double timeoutSeconds) { super(polyhedron) context = new Context infinitySymbol = context.mkSymbol(INFINITY_SYMBOL_NAME) @@ -48,6 +61,7 @@ class Z3SaturationOperator extends AbstractPolyhedronSaturationOperator { mkIntConst(name) } ] + timeoutMilliseconds = Math.ceil(timeoutSeconds * 1000) as int } override doSaturate() { @@ -91,6 +105,10 @@ class Z3SaturationOperator extends AbstractPolyhedronSaturationOperator { val value = switch (resultExpr : handle.lower) { IntNum: resultExpr.getInt() + RatNum: + floor(resultExpr) + AlgebraicNum: + floor(resultExpr.toLower(ALGEBRAIC_NUMBER_ROUNDING)) default: if (isNegativeInfinity(resultExpr)) { null @@ -103,6 +121,12 @@ class Z3SaturationOperator extends AbstractPolyhedronSaturationOperator { status } + private def floor(RatNum ratNum) { + val numerator = new BigDecimal(ratNum.numerator.bigInteger) + val denominator = new BigDecimal(ratNum.denominator.bigInteger) + numerator.divide(denominator, ROUND_DOWN).setScale(0, RoundingMode.FLOOR).intValue + } + private def saturateUpperBound(ArithExpr expr, LinearBoundedExpression expressionToSaturate) { val optimize = prepareOptimize val handle = optimize.MkMaximize(expr) @@ -111,6 +135,10 @@ class Z3SaturationOperator extends AbstractPolyhedronSaturationOperator { val value = switch (resultExpr : handle.upper) { IntNum: resultExpr.getInt() + RatNum: + ceil(resultExpr) + AlgebraicNum: + ceil(resultExpr.toUpper(ALGEBRAIC_NUMBER_ROUNDING)) default: if (isPositiveInfinity(resultExpr)) { null @@ -123,6 +151,12 @@ class Z3SaturationOperator extends AbstractPolyhedronSaturationOperator { status } + private def ceil(RatNum ratNum) { + val numerator = new BigDecimal(ratNum.numerator.bigInteger) + val denominator = new BigDecimal(ratNum.denominator.bigInteger) + numerator.divide(denominator, ROUND_UP).setScale(0, RoundingMode.CEILING).intValue + } + private def isPositiveInfinity(Expr expr) { expr.app && expr.getFuncDecl.name == infinitySymbol } @@ -137,6 +171,13 @@ class Z3SaturationOperator extends AbstractPolyhedronSaturationOperator { private def prepareOptimize() { val optimize = mkOptimize() + if (timeoutMilliseconds >= 0) { + val params = mkParams() + // We cannot turn TIMEOUT_SYMBOL_NAME into a Symbol in the constructor, + // because there is no add(Symbol, int) overload. + params.add(TIMEOUT_SYMBOL_NAME, timeoutMilliseconds) + optimize.parameters = params + } assertConstraints(optimize) optimize } diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend index 3d911bfb..a51aa082 100644 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend @@ -8,24 +8,27 @@ import org.junit.Test import static org.junit.Assert.* -class CbcPolyhedronSolverTest extends PolyhedronSolverTest { - +class CbcPolyhedronSolverTest extends IntegerPolyhedronSolverTest { + override protected createSolver() { - new CbcPolyhedronSolver(10, false) + new CbcPolyhedronSolver(10, true) } - +} + +class CbcPolyhedronSolverTimeoutTest { + @Test def void timeoutTest() { - val solver = new CbcPolyhedronSolver(0, false) + val solver = new CbcPolyhedronSolver(0, true) val x = new Dimension("x", 0, 1) val polyhedron = new Polyhedron(#[x], #[], #[x]) val operator = solver.createSaturationOperator(polyhedron) try { val result = operator.saturate - + assertEquals(PolyhedronSaturationResult.UNKNOWN, result) } finally { operator.close() } - } + } } diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend index 15758985..1b2dcb00 100644 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend @@ -78,19 +78,6 @@ abstract class PolyhedronSolverTest { assertEquals(3, x.upperBound) } - @Test - def void singleDimensionConstraintIntegerTest() { - val x = new Dimension("x", null, null) - val constraint = new LinearConstraint(#{x -> 2}, 0, 3) - createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) - - val result = saturate() - - assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(0, x.lowerBound) - assertEquals(1, x.upperBound) - } - @Test def void singleDimensionUnboundedFromAboveTest() { val x = new Dimension("x", 0, null) @@ -164,6 +151,60 @@ abstract class PolyhedronSolverTest { saturate() } + @Test + def void emptyConstraintTest() { + val x = new Dimension("x", 0, 1) + val constraint = new LinearConstraint(emptyMap, 0, 1) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[constraint])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, constraint.lowerBound) + assertEquals(0, constraint.upperBound) + } + + @Test + def void emptyConstraintUnsatisfiableTest() { + val x = new Dimension("x", 0, 1) + val constraint = new LinearConstraint(emptyMap, 1, 0) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[constraint])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.EMPTY, result) + } + + protected def createSaturationOperator(Polyhedron polyhedron) { + destroyOperatorIfExists() + operator = solver.createSaturationOperator(polyhedron) + } + + protected def destroyOperatorIfExists() { + if (operator !== null) { + operator.close + } + } + + protected def saturate() { + operator.saturate + } +} + +abstract class IntegerPolyhedronSolverTest extends PolyhedronSolverTest { + @Test + def void singleDimensionConstraintNonIntegerTest() { + val x = new Dimension("x", null, null) + val constraint = new LinearConstraint(#{x -> 2}, 0, 3) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(1, x.upperBound) + } + @Test def void unsatisfiableMultipleInheritanceTest() { val x = new Dimension("x", 0, 1) @@ -198,41 +239,64 @@ abstract class PolyhedronSolverTest { assertEquals(PolyhedronSaturationResult.EMPTY, result) } +} +abstract class RelaxedPolyhedronSolverTest extends PolyhedronSolverTest { @Test - def void emptyConstraintTest() { - val constraint = new LinearConstraint(emptyMap, 0, 1) - createSaturationOperator(new Polyhedron(#[], #[constraint], #[constraint])) + def void singleDimensionConstraintNonIntegerTest() { + val x = new Dimension("x", null, null) + val constraint = new LinearConstraint(#{x -> 2}, 0, 3) + createSaturationOperator(new Polyhedron(#[x], #[constraint], #[x])) val result = saturate() assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(0, constraint.lowerBound) - assertEquals(0, constraint.upperBound) + assertEquals(0, x.lowerBound) + assertEquals(2, x.upperBound) } @Test - def void emptyConstraintUnsatisfiableTest() { - val constraint = new LinearConstraint(emptyMap, 1, 0) - createSaturationOperator(new Polyhedron(#[], #[constraint], #[constraint])) + def void unsatisfiableMultipleInheritanceTest() { + val x = new Dimension("x", 0, 1) + val y = new Dimension("y", 0, 1) + val z = new Dimension("z", 0, 1) + createSaturationOperator(new Polyhedron( + #[x, y, z], + #[ + new LinearConstraint(#{x -> 1, y -> 1}, 1, 1), + new LinearConstraint(#{x -> 1, z -> 1}, 1, 1), + new LinearConstraint(#{y -> 1, z -> 1}, 1, 1) + ], + #[x, y, z] + )) val result = saturate() - assertEquals(PolyhedronSaturationResult.EMPTY, result) + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(1, x.upperBound) + assertEquals(0, y.lowerBound) + assertEquals(1, y.upperBound) + assertEquals(0, z.lowerBound) + assertEquals(1, z.upperBound) } - private def createSaturationOperator(Polyhedron polyhedron) { - destroyOperatorIfExists() - operator = solver.createSaturationOperator(polyhedron) - } + @Test + def void unboundedRelaxationWithNoIntegerSolutionTest() { + val x = new Dimension("x", 0, 1) + val y = new Dimension("y", 0, null) + createSaturationOperator(new Polyhedron( + #[x, y], + #[new LinearConstraint(#{x -> 2}, 1, 1)], + #[x, y] + )) - private def destroyOperatorIfExists() { - if (operator !== null) { - operator.close - } - } + val result = saturate() - private def saturate() { - operator.saturate + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(0, x.lowerBound) + assertEquals(1, x.upperBound) + assertEquals(0, y.lowerBound) + assertEquals(null, y.upperBound) } } diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend index b6d9b3b2..49b916d3 100644 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/Z3PolyhedronSolverTest.xtend @@ -2,9 +2,16 @@ package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests.cardinality import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Z3PolyhedronSolver -class Z3PolyhedronSolverTest extends PolyhedronSolverTest { +class Z3PolyhedronSolverTest extends IntegerPolyhedronSolverTest { override protected createSolver() { - new Z3PolyhedronSolver(false) + new Z3PolyhedronSolver(false, 10) + } +} + +class RelaxedZ3PolyhedronSolverTest extends RelaxedPolyhedronSolverTest { + + override protected createSolver() { + new Z3PolyhedronSolver(true, 10) } } -- cgit v1.2.3-54-g00ecf From b4bf8d387e430600790f6b30d9e88ec785148cd7 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Mon, 29 Jul 2019 14:21:36 +0200 Subject: Make CbcPolyhedronSolver more robust --- .../cpp/viatracbc.cpp | 54 +++++--- .../cpp/viatracbc.hpp | 2 +- .../lib/libviatracbc.so | Bin 38248 -> 33944 bytes .../bme/mit/inf/dslreasoner/ilp/cbc/CbcSolver.java | 8 +- .../cardinality/CbcPolyhedronSolver.xtend | 154 +++++++++++++-------- .../cardinality/CbcPolyhedronSolverTest.xtend | 14 +- .../tests/cardinality/PolyhedronSolverTest.xtend | 107 ++++++++++++-- 7 files changed, 240 insertions(+), 99 deletions(-) (limited to 'Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit') diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.cpp b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.cpp index 49994244..ffd35759 100644 --- a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.cpp +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.cpp @@ -36,9 +36,10 @@ static const jint kCbcError = 5; static CoinModel CreateModel(JNIEnv *env, jdoubleArray columnLowerBoundsArray, jdoubleArray columnUpperBoundsArray, jintArray rowStartsArray, jintArray columnIndicesArray, jdoubleArray entriedArray, jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, - jdoubleArray objectiveArray); + jdoubleArray objectiveArray, jboolean lpRelaxation); static void CreateModelColumns(JNIEnv *env, jdoubleArray columnLowerBoundsArray, - jdoubleArray columnUpperBoundsArray, jdoubleArray objectiveArray, CoinModel &build); + jdoubleArray columnUpperBoundsArray, jdoubleArray objectiveArray, jboolean lpRelaxation, + CoinModel &build); static void CreateModelRows(JNIEnv *env, jintArray rowStartsArray, jintArray columnIndicesArray, jdoubleArray entriesArray, jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, CoinModel &build); @@ -83,11 +84,11 @@ jint Java_hu_bme_mit_inf_dslreasoner_ilp_cbc_CbcSolver_solveIlpProblem( JNIEnv *env, jclass klazz, jdoubleArray columnLowerBoundsArray, jdoubleArray columnUpperBoundsArray, jintArray rowStartsArray, jintArray columnIndicesArray, jdoubleArray entriesArray, jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, jdoubleArray objectiveArray, - jdoubleArray outputArray, jdouble timeoutSeconds, jboolean silent) { + jdoubleArray outputArray, jboolean lpRelaxation, jdouble timeoutSeconds, jboolean silent) { try { auto build = CreateModel(env, columnLowerBoundsArray, columnUpperBoundsArray, rowStartsArray, columnIndicesArray, entriesArray, rowLowerBoundsArray, rowUpperBoundsArray, - objectiveArray); + objectiveArray, lpRelaxation); double value; jint result = SolveModel(build, timeoutSeconds, silent, value); if (result == kCbcSolutionBounded) { @@ -106,16 +107,18 @@ jint Java_hu_bme_mit_inf_dslreasoner_ilp_cbc_CbcSolver_solveIlpProblem( CoinModel CreateModel(JNIEnv *env, jdoubleArray columnLowerBoundsArray, jdoubleArray columnUpperBoundsArray, jintArray rowStartsArray, jintArray columnIndicesArray, jdoubleArray entriesArray, jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, - jdoubleArray objectiveArray) { + jdoubleArray objectiveArray, jboolean lpRelaxation) { CoinModel build; - CreateModelColumns(env, columnLowerBoundsArray, columnUpperBoundsArray, objectiveArray, build); + CreateModelColumns(env, columnLowerBoundsArray, columnUpperBoundsArray, objectiveArray, + lpRelaxation, build); CreateModelRows(env, rowStartsArray, columnIndicesArray, entriesArray, rowLowerBoundsArray, rowUpperBoundsArray, build); return build; } void CreateModelColumns(JNIEnv *env, jdoubleArray columnLowerBoundsArray, - jdoubleArray columnUpperBoundsArray, jdoubleArray objectiveArray, CoinModel &build) { + jdoubleArray columnUpperBoundsArray, jdoubleArray objectiveArray, jboolean lpRelaxation, + CoinModel &build) { int numColumns = env->GetArrayLength(columnLowerBoundsArray); PinnedDoubleArray columnLowerBounds{env, columnLowerBoundsArray}; PinnedDoubleArray columnUpperBounds{env, columnUpperBoundsArray}; @@ -123,7 +126,9 @@ void CreateModelColumns(JNIEnv *env, jdoubleArray columnLowerBoundsArray, for (int i = 0; i < numColumns; i++) { build.setColumnBounds(i, columnLowerBounds[i], columnUpperBounds[i]); build.setObjective(i, objective[i]); - build.setInteger(i); + if (!lpRelaxation) { + build.setInteger(i); + } } } @@ -215,6 +220,9 @@ jint SolveModel(CoinModel &build, jdouble timeoutSeconds, jboolean silent, jdoub if (model.isInitialSolveProvenPrimalInfeasible()) { return kCbcUnsat; } + if (model.isInitialSolveProvenDualInfeasible()) { + return kCbcSolutionUnbounded; + } if (model.isInitialSolveAbandoned()) { return kCbcTimeout; } @@ -226,20 +234,26 @@ jint SolveModel(CoinModel &build, jdouble timeoutSeconds, jboolean silent, jdoub model.branchAndBound(); - if (model.isProvenInfeasible()) { - return kCbcUnsat; - } - if (model.isProvenDualInfeasible()) { - return kCbcSolutionUnbounded; - } - if (model.isProvenOptimal()) { - value = model.getMinimizationObjValue(); - return kCbcSolutionBounded; - } - if (model.maximumSecondsReached()) { + switch (model.status()) { + case 0: + if (model.isProvenInfeasible()) { + return kCbcUnsat; + } + if (model.isProvenDualInfeasible()) { + return kCbcSolutionUnbounded; + } + if (model.isProvenOptimal()) { + value = model.getMinimizationObjValue(); + return kCbcSolutionBounded; + } + throw std::runtime_error("CBC status is 0, but no solution is found"); + case 1: return kCbcTimeout; + case 2: + return kCbcAbandoned; + default: + throw std::runtime_error("Unknown CBC status"); } - return kCbcAbandoned; } void ThrowException(JNIEnv *env, const char *message) { diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.hpp b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.hpp index c65f71e3..12198c8b 100644 --- a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.hpp +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/cpp/viatracbc.hpp @@ -9,7 +9,7 @@ JNIEXPORT jint JNICALL Java_hu_bme_mit_inf_dslreasoner_ilp_cbc_CbcSolver_solveIl JNIEnv *env, jclass klazz, jdoubleArray columnLowerBoundsArray, jdoubleArray columnUpperBoundsArray, jintArray rowStartsArray, jintArray columnIndicesArray, jdoubleArray entriesArray, jdoubleArray rowLowerBoundsArray, jdoubleArray rowUpperBoundsArray, jdoubleArray objectiveArray, - jdoubleArray outputArray, jdouble timeoutSeconds, jboolean silent); + jdoubleArray outputArray, jboolean lpRelaxation, jdouble timeoutSeconds, jboolean silent); } diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/lib/libviatracbc.so b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/lib/libviatracbc.so index 21fd2ff2..4eae7de6 100755 Binary files a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/lib/libviatracbc.so and b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/lib/libviatracbc.so differ diff --git a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcSolver.java b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcSolver.java index 39b9d537..085d4448 100644 --- a/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcSolver.java +++ b/Solvers/ILP-Solver/hu.bme.mit.inf.dslreasoner.ilp.cbc/src/hu/bme/mit/inf/dslreasoner/ilp/cbc/CbcSolver.java @@ -15,14 +15,14 @@ public class CbcSolver { } public static CbcResult solve(double[] columnLowerBounds, double[] columnUpperBounds, int[] rowStarts, - int[] columnIndices, double[] entries, double[] rowLowerBounds, double[] rowUpperBounds, - double[] objective, double timeoutSeconds, boolean silent) { + int[] columnIndices, double[] entries, double[] rowLowerBounds, double[] rowUpperBounds, double[] objective, + boolean lpRelaxation, double timeoutSeconds, boolean silent) { loadNatives(); validate(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices, entries, rowLowerBounds, rowUpperBounds, objective); double[] output = new double[1]; int result = solveIlpProblem(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices, entries, - rowLowerBounds, rowUpperBounds, objective, output, timeoutSeconds, silent); + rowLowerBounds, rowUpperBounds, objective, output, lpRelaxation, timeoutSeconds, silent); if (result == CBC_SOLUTION_BOUNDED) { return new CbcResult.SolutionBounded(output[0]); } else if (result == CBC_SOLUTION_UNBOUNDED) { @@ -67,5 +67,5 @@ public class CbcSolver { private static native int solveIlpProblem(double[] columnLowerBounds, double[] columnUpperBounds, int[] rowStarts, int[] columnIndices, double[] entries, double[] rowLowerBounds, double[] rowUpperBounds, double[] objective, - double[] output, double timeoutSeconds, boolean silent); + double[] output, boolean lpRelaxation, double timeoutSeconds, boolean silent); } diff --git a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/CbcPolyhedronSolver.xtend b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/CbcPolyhedronSolver.xtend index 7753e68e..4bd46fbf 100644 --- a/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/CbcPolyhedronSolver.xtend +++ b/Solvers/VIATRA-Solver/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/cardinality/CbcPolyhedronSolver.xtend @@ -1,27 +1,32 @@ package hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality +import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import hu.bme.mit.inf.dslreasoner.ilp.cbc.CbcResult import hu.bme.mit.inf.dslreasoner.ilp.cbc.CbcSolver +import java.util.HashSet import java.util.List import java.util.Map +import java.util.Set import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor @FinalFieldsConstructor class CbcPolyhedronSolver implements PolyhedronSolver { + val boolean lpRelaxation val double timeoutSeconds val boolean silent new() { - this(10, true) + this(false, -1, true) } override createSaturationOperator(Polyhedron polyhedron) { - new CbcSaturationOperator(polyhedron, timeoutSeconds, silent) + new CbcSaturationOperator(polyhedron, lpRelaxation, timeoutSeconds, silent) } } class CbcSaturationOperator extends AbstractPolyhedronSaturationOperator { + val boolean lpRelaxation val double timeoutSeconds val boolean silent val double[] columnLowerBounds @@ -29,8 +34,9 @@ class CbcSaturationOperator extends AbstractPolyhedronSaturationOperator { val double[] objective val Map dimensionsToIndicesMap - new(Polyhedron polyhedron, double timeoutSeconds, boolean silent) { + new(Polyhedron polyhedron, boolean lpRelaxation, double timeoutSeconds, boolean silent) { super(polyhedron) + this.lpRelaxation = lpRelaxation this.timeoutSeconds = timeoutSeconds this.silent = silent val numDimensions = polyhedron.dimensions.size @@ -56,6 +62,12 @@ class CbcSaturationOperator extends AbstractPolyhedronSaturationOperator { rowStarts.set(numConstraints, numEntries) val columnIndices = newIntArrayOfSize(numEntries) val entries = newDoubleArrayOfSize(numEntries) + val unconstrainedDimensions = new HashSet + for (dimension : polyhedron.dimensions) { + if (dimension.lowerBound === null && dimension.upperBound === null) { + unconstrainedDimensions += dimension + } + } var int index = 0 for (var int i = 0; i < numConstraints; i++) { rowStarts.set(i, index) @@ -69,6 +81,7 @@ class CbcSaturationOperator extends AbstractPolyhedronSaturationOperator { val dimension = polyhedron.dimensions.get(j) val coefficient = constraint.coefficients.get(dimension) if (coefficient !== null && coefficient != 0) { + unconstrainedDimensions -= dimension columnIndices.set(index, j) entries.set(index, coefficient) index++ @@ -79,71 +92,94 @@ class CbcSaturationOperator extends AbstractPolyhedronSaturationOperator { throw new AssertionError("Last entry does not equal the number of entries in the constraint matrix") } for (expressionToSaturate : polyhedron.expressionsToSaturate) { - for (var int j = 0; j < numDimensions; j++) { - objective.set(j, 0) + val result = saturate(expressionToSaturate, rowStarts, columnIndices, entries, rowLowerBounds, + rowUpperBounds, unconstrainedDimensions, constraints) + if (result != PolyhedronSaturationResult.SATURATED) { + return result } - switch (expressionToSaturate) { - Dimension: { - val j = getIndex(expressionToSaturate) - objective.set(j, 1) + } + PolyhedronSaturationResult.SATURATED + } + + protected def saturate(LinearBoundedExpression expressionToSaturate, int[] rowStarts, int[] columnIndices, + double[] entries, double[] rowLowerBounds, double[] rowUpperBounds, Set unconstrainedDimensions, + ImmutableList constraints) { + val numDimensions = objective.size + for (var int j = 0; j < numDimensions; j++) { + objective.set(j, 0) + } + switch (expressionToSaturate) { + Dimension: { + // CBC will return nonsensical results or call free() with invalid arguments if + // it is passed a fully unconstrained (-Inf lower and +Int upper bound, no inequalities) variable + // in the objective function. + if (unconstrainedDimensions.contains(expressionToSaturate)) { + return PolyhedronSaturationResult.SATURATED } - LinearConstraint: { - for (pair : expressionToSaturate.coefficients.entrySet) { - val j = getIndex(pair.key) - objective.set(j, pair.value) + val j = getIndex(expressionToSaturate) + objective.set(j, 1) + } + LinearConstraint: { + for (pair : expressionToSaturate.coefficients.entrySet) { + val dimension = pair.key + // We also have to check for unconstrained dimensions here to avoid crashing. + if (unconstrainedDimensions.contains(dimension)) { + expressionToSaturate.lowerBound = null + expressionToSaturate.upperBound = null + return PolyhedronSaturationResult.SATURATED } + val j = getIndex(dimension) + objective.set(j, pair.value) } - default: - throw new IllegalArgumentException("Unknown expression: " + expressionToSaturate) } - val minimizationResult = CbcSolver.solve(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices, - entries, rowLowerBounds, rowUpperBounds, objective, timeoutSeconds, silent) - switch (minimizationResult) { - CbcResult.SolutionBounded: { - val value = Math.floor(minimizationResult.value) - expressionToSaturate.lowerBound = value as int - setBound(expressionToSaturate, constraints, value, columnLowerBounds, rowLowerBounds) - } - case CbcResult.SOLUTION_UNBOUNDED: { - expressionToSaturate.lowerBound = null - setBound(expressionToSaturate, constraints, Double.NEGATIVE_INFINITY, columnLowerBounds, - rowLowerBounds) - } - case CbcResult.UNSAT: - return PolyhedronSaturationResult.EMPTY - case CbcResult.ABANDONED, - case CbcResult.TIMEOUT: - return PolyhedronSaturationResult.UNKNOWN - default: - throw new RuntimeException("Unknown CbcResult: " + minimizationResult) + default: + throw new IllegalArgumentException("Unknown expression: " + expressionToSaturate) + } + val minimizationResult = CbcSolver.solve(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices, + entries, rowLowerBounds, rowUpperBounds, objective, lpRelaxation, timeoutSeconds, silent) + switch (minimizationResult) { + CbcResult.SolutionBounded: { + val value = Math.floor(minimizationResult.value) + expressionToSaturate.lowerBound = value as int + setBound(expressionToSaturate, constraints, value, columnLowerBounds, rowLowerBounds) } - for (var int j = 0; j < numDimensions; j++) { - val objectiveCoefficient = objective.get(j) - objective.set(j, -objectiveCoefficient) + case CbcResult.SOLUTION_UNBOUNDED: { + expressionToSaturate.lowerBound = null + setBound(expressionToSaturate, constraints, Double.NEGATIVE_INFINITY, columnLowerBounds, rowLowerBounds) } - val maximizationResult = CbcSolver.solve(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices, - entries, rowLowerBounds, rowUpperBounds, objective, timeoutSeconds, silent) - switch (maximizationResult) { - CbcResult.SolutionBounded: { - val value = Math.ceil(-maximizationResult.value) - expressionToSaturate.upperBound = value as int - setBound(expressionToSaturate, constraints, value, columnUpperBounds, rowUpperBounds) - } - case CbcResult.SOLUTION_UNBOUNDED: { - expressionToSaturate.upperBound = null - setBound(expressionToSaturate, constraints, Double.POSITIVE_INFINITY, columnUpperBounds, - rowUpperBounds) - } - case CbcResult.UNSAT: - throw new RuntimeException("Minimization was SAT, but maximization is UNSAT") - case CbcResult.ABANDONED, - case CbcResult.TIMEOUT: - return PolyhedronSaturationResult.UNKNOWN - default: - throw new RuntimeException("Unknown CbcResult: " + maximizationResult) + case CbcResult.UNSAT: + return PolyhedronSaturationResult.EMPTY + case CbcResult.ABANDONED, + case CbcResult.TIMEOUT: + return PolyhedronSaturationResult.UNKNOWN + default: + throw new RuntimeException("Unknown CbcResult: " + minimizationResult) + } + for (var int j = 0; j < numDimensions; j++) { + val objectiveCoefficient = objective.get(j) + objective.set(j, -objectiveCoefficient) + } + val maximizationResult = CbcSolver.solve(columnLowerBounds, columnUpperBounds, rowStarts, columnIndices, + entries, rowLowerBounds, rowUpperBounds, objective, lpRelaxation, timeoutSeconds, silent) + switch (maximizationResult) { + CbcResult.SolutionBounded: { + val value = Math.ceil(-maximizationResult.value) + expressionToSaturate.upperBound = value as int + setBound(expressionToSaturate, constraints, value, columnUpperBounds, rowUpperBounds) } + case CbcResult.SOLUTION_UNBOUNDED: { + expressionToSaturate.upperBound = null + setBound(expressionToSaturate, constraints, Double.POSITIVE_INFINITY, columnUpperBounds, rowUpperBounds) + } + case CbcResult.UNSAT: + throw new RuntimeException("Minimization was SAT, but maximization is UNSAT") + case CbcResult.ABANDONED, + case CbcResult.TIMEOUT: + return PolyhedronSaturationResult.UNKNOWN + default: + throw new RuntimeException("Unknown CbcResult: " + maximizationResult) } - PolyhedronSaturationResult.SATURATED + return PolyhedronSaturationResult.SATURATED } private def toDouble(Integer nullableInt, double defaultValue) { diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend index a51aa082..b22e2a20 100644 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/CbcPolyhedronSolverTest.xtend @@ -7,11 +7,19 @@ import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.Polyhedr import org.junit.Test import static org.junit.Assert.* +import hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.cardinality.LinearConstraint class CbcPolyhedronSolverTest extends IntegerPolyhedronSolverTest { override protected createSolver() { - new CbcPolyhedronSolver(10, true) + new CbcPolyhedronSolver(false, 10, true) + } +} + +class RelaxedCbcPolyhedronSolverTest extends RelaxedPolyhedronSolverTest { + + override protected createSolver() { + new CbcPolyhedronSolver(true, 10, true) } } @@ -19,9 +27,9 @@ class CbcPolyhedronSolverTimeoutTest { @Test def void timeoutTest() { - val solver = new CbcPolyhedronSolver(0, true) + val solver = new CbcPolyhedronSolver(false, 0, true) val x = new Dimension("x", 0, 1) - val polyhedron = new Polyhedron(#[x], #[], #[x]) + val polyhedron = new Polyhedron(#[x], #[new LinearConstraint(#{x -> 1}, null, 0)], #[x]) val operator = solver.createSaturationOperator(polyhedron) try { val result = operator.saturate diff --git a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend index 1b2dcb00..47534618 100644 --- a/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend +++ b/Tests/hu.bme.mit.inf.dslreasoner.viatrasolver.logic2viatra.tests/src/hu/bme/mit/inf/dslreasoner/viatrasolver/logic2viatra/tests/cardinality/PolyhedronSolverTest.xtend @@ -80,26 +80,52 @@ abstract class PolyhedronSolverTest { @Test def void singleDimensionUnboundedFromAboveTest() { - val x = new Dimension("x", 0, null) + val x = new Dimension("x", -2, null) createSaturationOperator(new Polyhedron(#[x], #[], #[x])) val result = saturate() assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(0, x.lowerBound) + assertEquals(-2, x.lowerBound) assertEquals(null, x.upperBound) } @Test def void singleDimensionUnboundedFromBelowTest() { - val x = new Dimension("x", null, 0) + val x = new Dimension("x", null, 2) createSaturationOperator(new Polyhedron(#[x], #[], #[x])) val result = saturate() assertEquals(PolyhedronSaturationResult.SATURATED, result) assertEquals(null, x.lowerBound) - assertEquals(0, x.upperBound) + assertEquals(2, x.upperBound) + } + + @Test + def void singleDimensionUnboundedTest() { + val x = new Dimension("x", null, null) + createSaturationOperator(new Polyhedron(#[x], #[], #[x])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(null, x.lowerBound) + assertEquals(null, x.upperBound) + } + + @Test + def void singleDimensionUnboundedObjectiveTest() { + val x = new Dimension("x", null, null) + val y = new Dimension("y", 0, 1) + val objective = new LinearConstraint(#{x -> 1, y -> 1}, null, null) + createSaturationOperator(new Polyhedron(#[x, y], #[], #[objective])) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(null, objective.lowerBound) + assertEquals(null, objective.upperBound) } @Test @@ -174,6 +200,25 @@ abstract class PolyhedronSolverTest { assertEquals(PolyhedronSaturationResult.EMPTY, result) } + + @Test + def void unboundedRelaxationWithIntegerSolutionTest() { + val x = new Dimension("x", 1, 3) + val y = new Dimension("y", null, null) + createSaturationOperator(new Polyhedron( + #[x, y], + #[new LinearConstraint(#{x -> 2}, 2, 6)], + #[x, y] + )) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(1, x.lowerBound) + assertEquals(3, x.upperBound) + assertEquals(null, y.lowerBound) + assertEquals(null, y.upperBound) + } protected def createSaturationOperator(Polyhedron polyhedron) { destroyOperatorIfExists() @@ -228,7 +273,7 @@ abstract class IntegerPolyhedronSolverTest extends PolyhedronSolverTest { @Test def void unboundedRelaxationWithNoIntegerSolutionTest() { val x = new Dimension("x", 0, 1) - val y = new Dimension("y", 0, null) + val y = new Dimension("y", null, null) createSaturationOperator(new Polyhedron( #[x, y], #[new LinearConstraint(#{x -> 2}, 1, 1)], @@ -282,21 +327,59 @@ abstract class RelaxedPolyhedronSolverTest extends PolyhedronSolverTest { } @Test - def void unboundedRelaxationWithNoIntegerSolutionTest() { - val x = new Dimension("x", 0, 1) - val y = new Dimension("y", 0, null) + def void unboundedRelaxationWithNoIntegerSolutionUnconstrainedVariableTest() { + val x = new Dimension("x", 1, 2) + val y = new Dimension("y", null, null) createSaturationOperator(new Polyhedron( #[x, y], - #[new LinearConstraint(#{x -> 2}, 1, 1)], + #[new LinearConstraint(#{x -> 2}, 3, 3)], #[x, y] )) val result = saturate() assertEquals(PolyhedronSaturationResult.SATURATED, result) - assertEquals(0, x.lowerBound) - assertEquals(1, x.upperBound) - assertEquals(0, y.lowerBound) + assertEquals(1, x.lowerBound) + assertEquals(2, x.upperBound) + assertEquals(null, y.lowerBound) assertEquals(null, y.upperBound) } + + @Test + def void unboundedRelaxationWithNoIntegerSolutionConstrainedVariableTest() { + val x = new Dimension("x", 1, 2) + val y = new Dimension("y", null, null) + createSaturationOperator(new Polyhedron( + #[x, y], + #[new LinearConstraint(#{x -> 2}, 3, 3), new LinearConstraint(#{y -> 1}, null, 1)], + #[x, y] + )) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(1, x.lowerBound) + assertEquals(2, x.upperBound) + assertEquals(null, y.lowerBound) + assertEquals(1, y.upperBound) + } + + @Test + def void unboundedRelaxationWithNoIntegerSolutionBoundedVariableTest() { + val x = new Dimension("x", 1, 2) + val y = new Dimension("y", null, 1) + createSaturationOperator(new Polyhedron( + #[x, y], + #[new LinearConstraint(#{x -> 2}, 3, 3)], + #[x, y] + )) + + val result = saturate() + + assertEquals(PolyhedronSaturationResult.SATURATED, result) + assertEquals(1, x.lowerBound) + assertEquals(2, x.upperBound) + assertEquals(null, y.lowerBound) + assertEquals(1, y.upperBound) + } } -- cgit v1.2.3-54-g00ecf