diff --git a/library/leon/math/package.scala b/library/leon/math/package.scala
index 036d35771..213dcd278 100644
--- a/library/leon/math/package.scala
+++ b/library/leon/math/package.scala
@@ -2,6 +2,7 @@
package leon
import leon.annotation._
+import leon.collection._
package object math {
@@ -16,7 +17,308 @@ package object math {
@library
def max(i1: BigInt, i2: BigInt) = if (i1 >= i2) i1 else i2
+
+ @library
+ def abs(i: BigInt) = if(i < BigInt(0)) -i else i
+
+ @library
+ @annotation.isabelle.noBody()
+ // Computes the vectorial product of two lists.
+ def vectorialProduct(a: List[BigInt], b: List[BigInt]): BigInt = {
+ require(a.length == b.length)
+ a match {
+ case Nil() => 0
+ case Cons(ael, aq) => ael * b.head + vectorialProduct(aq, b.tail)
+ }
+ }
+
+ @library
+ @annotation.isabelle.noBody()
+ def isGCDable(l: List[BigInt]): Boolean = l match {
+ case Cons(x, b) if x == BigInt(0) => isGCDable(b)
+ case Cons(x, b) => true
+ case Nil() => false
+ }
+
+ /** Computes the GCD between numbers */
+ @library
+ @annotation.isabelle.noBody()
+ def gcdlist(l:List[BigInt]):BigInt = {
+ require(l.length > 0 && isGCDable(l))
+ l match {
+ case Cons(a, Nil()) => if(a < 0) -a else a
+ case Cons(z, q) if z == BigInt(0) => gcdlist(q)
+ case Cons(a, Cons(b, q)) => gcdlist(gcd(a,b)::q)
+ }
+ }
+
+ /** Computes the LCM between numbers */
+ @library
+ @annotation.isabelle.noBody()
+ def lcmlist(l:List[BigInt]):BigInt = {
+ require(l.length > 0 && isGCDable(l))
+ l match {
+ case Cons(a, Nil()) => if(a < 0) -a else a
+ case Cons(z, q) if z == BigInt(0) => lcmlist(q)
+ case Cons(a, Cons(b, q)) => lcmlist(lcm(a,b)::q)
+ }
+ }
+
+ /** Computes the GCD between two numbers */
+ @library
+ @annotation.isabelle.noBody()
+ def gcd(x:BigInt, y:BigInt): BigInt = {
+ require(x != 0 || y != 0)
+ if (x==BigInt(0)) abs(y)
+ else if (x<0) gcd(-x, y)
+ else if (y<0) gcd(x, -y)
+ else gcd(y%x, x)
+ } ensuring { (res: BigInt) =>
+ x % res == 0 && y % res == 0 && res > 0
+ }
+
+ /** Computes the LCM between two numbers */
+ @library
+ @annotation.isabelle.noBody()
+ def lcm(x: BigInt, y: BigInt): BigInt = {
+ require(x != 0 || y != 0)
+ abs(x * y) / gcd(x, y)
+ } ensuring { (res: BigInt) =>
+ res % x == 0 && res % y == 0 && res > 0
+ }
+
+ /** Computes the GCD between two numbers. Axponential speed-up.*/
+ /*@library
+ def binaryGcd(a:BigInt, b:BigInt):BigInt = {
+ var g = BigInt(1)
+ var (u, v) = (a, b)
+ while(u%2 == 0 && v%2 == 0) {
+ u = u/2
+ v = v/2
+ g = 2*g
+ }
+ while(u != 0) {
+ if(u % 2 == 0) u = u/2
+ else if(v % 2 == 0) v = v/2
+ else {
+ val t = abs(u-v)/2
+ if(u >= v) u = t else v = t
+ }
+ }
+ g*v
+ }*/
+
+ /*@library
+ def bezout(e: BigInt, a : List[BigInt]):List[BigInt] = {
+ require(a.length > 0 && isGCDable(a))
+ bezoutMM(a, e / gcdlist(a))
+ }*/
+
+ @library
+ @annotation.isabelle.noBody()
+ def bezoutWithBase(e: BigInt, a: List[BigInt]): (List[List[BigInt]]) = {
+ require(isNonZero(a) && a.nonEmpty)
+ bezoutWithBaseMMFunc(e, a)
+ }
+
+ /** Finds (x1, x2, k) such that x1.a + x2.b + gcd(a,b) = 0 and k = gcd(a ,b) */
+ /*@library
+ def extendedEuclid(a_in: BigInt, b_in: BigInt):(BigInt, BigInt, BigInt) = {
+ var (x, lastx) = (BigInt(0), BigInt(1))
+ var (y, lasty) = (BigInt(1), BigInt(0))
+ var (a, b) = (a_in, b_in)
+ var (quotient, temp) = (BigInt(0), BigInt(0))
+ while(b != 0) {
+ val amodb = (abs(b) + a%b)%b
+ quotient = (a - amodb)/b
+ a = b
+ b = amodb
+ temp = x
+ x = lastx-quotient*x
+ lastx = temp
+ temp = y
+ y = lasty-quotient*y
+ lasty = temp
+ }
+ if(a < 0)
+ (lastx, lasty, -a)
+ else
+ (-lastx, -lasty, a)
+ }*/
+
+ // Finds coefficients x such that k*gcd(a_in) + x.a_in = 0
+ /*@library
+ def bezoutMM(a_in: List[BigInt], k: BigInt):List[BigInt] = {
+ var a = a_in
+ var a_in_gcds = a_in.foldRight(List[BigInt]()){
+ case (i, Nil()) => List(i)
+ case (i, a::q) => gcd(a, i)::a::q
+ }
+ var result:List[BigInt] = Nil()
+ var last_coef = BigInt(-1)
+ while(a.nonEmpty) {
+ a match {
+ case Cons(x, Nil()) if x == BigInt(0) =>
+ result = Cons(BigInt(0), result)
+ a = Nil()
+ case Cons(el, Nil()) =>
+ // Solution is -el/abs(el)
+ result = (k*(-last_coef * (-el/abs(el))))::result
+ a = Nil()
+ case Cons(el1, Cons(el2, Nil())) =>
+ val (u, v, _) = extendedEuclid(el1, el2)
+ result = (-v*k*last_coef)::(-u*k*last_coef)::result
+ a = Nil()
+ case (el1::q) =>
+ val el2 = a_in_gcds.tail.head
+ val (u, v, _) = extendedEuclid(el1, el2)
+ result = (-u*k*last_coef)::result
+ last_coef = -v*last_coef
+ a = q
+ a_in_gcds = a_in_gcds.tail
+ }
+ }
+ result.reverse
+ }*/
+
+ // Finds coefficients x such that gcd(a_in) + x.a_in = 0
+ /*@library
+ def bezoutMM(a_in: List[BigInt]):List[BigInt] = bezoutMM(a_in, BigInt(1))*/
+ /*@library
+ def bezoutWithBaseMM(e: BigInt, a: List[BigInt]): (List[List[BigInt]]) = {
+ var coefs = a
+ var coefs_gcd = coefs.foldRight(List[BigInt]()){
+ case (i, Nil()) => List(abs(i))
+ case (i, Cons(a, q)) => gcd(a, i)::a::q
+ }
+ var n = a.length
+ var result = List(bezoutMM(a, e/coefs_gcd.head)) // The gcd of all coefs divides e.
+ var i = BigInt(1)
+ var zeros:List[BigInt] = Nil()
+ while(i <= n-BigInt(1)) {
+ val kii = coefs_gcd.tail.head / coefs_gcd.head
+ val kijs = bezoutMM(coefs.tail, coefs.head/coefs_gcd.head)
+ result = Cons((zeros ++ Cons(kii, kijs)), result)
+ coefs = coefs.tail
+ coefs_gcd = coefs_gcd.tail
+ zeros = Cons(BigInt(0), zeros)
+ i += 1
+ }
+ result.reverse
+ }*/
+
+ @library
+ @annotation.isabelle.noBody()
+ def extendedEuclidFunc(a_in: BigInt, b_in: BigInt):(BigInt, BigInt, BigInt) = {
+ require(a_in != 0 || b_in != 0)
+ val (x, lastx) = (BigInt(0), BigInt(1))
+ val (y, lasty) = (BigInt(1), BigInt(0))
+ val (a, b) = (a_in, b_in)
+ def aux(a: BigInt, b: BigInt, x: BigInt, y: BigInt, lastx: BigInt, lasty: BigInt): (BigInt, BigInt, BigInt) = {
+ if (b == 0) {
+ if(a < 0)
+ (lastx, lasty, -a)
+ else
+ (-lastx, -lasty, a)
+ } else {
+ val amodb = (abs(b) + a%b)%b
+ val quotient = (a - amodb)/b
+ aux(b, amodb, lastx-quotient*x, lasty-quotient*y, x, y)
+ }
+ }
+ aux(a, b, x, y, lastx, lasty)
+ }/* ensuring { res =>
+ val (x1, x2, k) = res
+ x1 * a_in + x2 * b_in + k == 0 && k == gcd(a_in, b_in)
+ }*/
+
+ @library
+ @annotation.isabelle.noBody()
+ def isPositive(l: List[BigInt]): Boolean = l match {
+ case Cons(a, b) => a > 0 && isPositive(b)
+ case Nil() => true
+ }
+ @library
+ @annotation.isabelle.noBody()
+ def isNonZero(l: List[BigInt]): Boolean = l match {
+ case Cons(a, b) => a != 0 && isNonZero(b)
+ case Nil() => true
+ }
@library
- def abs(n: BigInt) = if(n < 0) -n else n
+ @annotation.isabelle.noBody()
+ def foldRightGcds(es: List[BigInt]): List[BigInt] = {
+ require(isNonZero(es))
+ ((es match {
+ case Cons(i, aq) => foldRightGcds(aq) match {
+ case Cons(a, q) => gcd(a, i)::a::q
+ case Nil() => List(abs(i))
+ }
+ case Nil() => Nil()
+ }): List[BigInt])} ensuring {
+ (res: List[BigInt]) =>
+ isPositive(res) && res.length == es.length
+ }
+
+ @library
+ @annotation.isabelle.noBody()
+ def bezoutMMFunc(a_in: List[BigInt], k: BigInt):List[BigInt] = {
+ require(isNonZero(a_in) && a_in.length > 0)
+ val a = a_in
+
+ val a_in_gcds = foldRightGcds(a_in)
+ val result:List[BigInt] = Nil()
+ val last_coef = BigInt(-1)
+ def aux(a: List[BigInt], a_in_gcds: List[BigInt], result: List[BigInt], last_coef: BigInt): List[BigInt] = {
+ require(isNonZero(a) && a_in_gcds == foldRightGcds(a))
+ if(a.isEmpty) result.reverse
+ else {
+ a match {
+ /*case Cons(x, Nil()) if x == BigInt(0) =>
+ aux(Nil(), a_in_gcds, Cons(BigInt(0), result), last_coef)*/
+ case Cons(el, Nil()) =>
+ // Solution is -el/abs(el)
+ aux(Nil(), a_in_gcds, (k*(-last_coef * (-el/abs(el))))::result, last_coef)
+ case Cons(el1, Cons(el2, Nil())) =>
+ val (u, v, _) = extendedEuclidFunc(el1, el2)
+ aux(Nil(), a_in_gcds, (-v*k*last_coef)::(-u*k*last_coef)::result, last_coef)
+ case (el1::q) =>
+ val el2 = a_in_gcds.tail.head
+ val (u, v, _) = extendedEuclidFunc(el1, el2)
+ aux(q, a_in_gcds.tail, (-u*k*last_coef)::result, -v*last_coef)
+ }
+ }
+ } ensuring {
+ (res: List[BigInt]) => true
+ }
+ aux(a_in, a_in_gcds, result, last_coef)
+ }/* ensuring {
+ (x: List[BigInt]) =>
+ vectorialProduct(x, a_in) + k*gcdlist(a_in) == 0
+ }*/
+
+ @library
+ @annotation.isabelle.noBody()
+ def bezoutWithBaseMMFunc(e: BigInt, a: List[BigInt]): (List[List[BigInt]]) = {
+ require(isNonZero(a) && a.nonEmpty)
+ val coefs = a
+ val coefs_gcd = foldRightGcds(coefs)
+ val n = a.length
+ val result = List(bezoutMMFunc(a, e/coefs_gcd.head)) // The gcd of all coefs divides e.
+ val i = BigInt(1)
+ val zeros:List[BigInt] = Nil()
+ def aux(i: BigInt, result: List[List[BigInt]], coefs: List[BigInt], coefs_gcd: List[BigInt], zeros: List[BigInt])
+ : List[List[BigInt]] = {
+ require(i <= n && coefs.length + i == a.length + BigInt(1) && isNonZero(coefs) && isPositive(coefs_gcd) && coefs_gcd == foldRightGcds(coefs) && (i == n || (coefs_gcd.length >= 2 && coefs.length >= 1)))
+ if (i > n-BigInt(1)) {
+ result.reverse
+ } else {
+ val kii = coefs_gcd.tail.head / coefs_gcd.head
+ val kijs = bezoutMMFunc(coefs.tail, coefs.head/coefs_gcd.head)
+ aux(i + 1, Cons((zeros ++ Cons(kii, kijs)), result), coefs.tail, coefs_gcd.tail, Cons(BigInt(0), zeros))
+ }
+ }
+ aux(i, result, coefs, coefs_gcd, zeros)
+ }
}
+
diff --git a/src/main/scala/leon/purescala/Constructors.scala b/src/main/scala/leon/purescala/Constructors.scala
index 7b34157d3..d914d7861 100644
--- a/src/main/scala/leon/purescala/Constructors.scala
+++ b/src/main/scala/leon/purescala/Constructors.scala
@@ -117,7 +117,7 @@ object Constructors {
*/
def functionInvocation(fd : FunDef, args : Seq[Expr]) = {
- require(fd.params.length == args.length, "Invoking function with incorrect number of arguments")
+ require(fd.params.length == args.length, s"Invoking function ${fd.id.name} with incorrect number of arguments. Expected ${fd.params.length}, got $args.")
val formalType = tupleTypeWrap(fd.params map { _.getType })
val actualType = tupleTypeWrap(args map { _.getType })
@@ -125,7 +125,7 @@ object Constructors {
canBeSupertypeOf(formalType, actualType) match {
case Some(tmap) =>
FunctionInvocation(fd.typed(fd.tparams map { tpd => tmap.getOrElse(tpd.tp, tpd.tp) }), args)
- case None => throw LeonFatalError(s"$args:$actualType cannot be a subtype of $formalType!")
+ case None => throw LeonFatalError(s"$args:$actualType cannot be a subtype of $formalType!. Indeed " + ExprOps.explainTyping(tupleWrap(args)))
}
}
diff --git a/src/main/scala/leon/purescala/Extractors.scala b/src/main/scala/leon/purescala/Extractors.scala
index 36b0887dc..7a02a36d5 100644
--- a/src/main/scala/leon/purescala/Extractors.scala
+++ b/src/main/scala/leon/purescala/Extractors.scala
@@ -308,6 +308,7 @@ object Extractors {
object TopLevelAnds { // expr1 AND (expr2 AND (expr3 AND ..)) => List(expr1, expr2, expr3)
def unapply(e: Expr): Option[Seq[Expr]] = e match {
case Let(i, e, TopLevelAnds(bs)) => Some(bs map (let(i,e,_)))
+ case LetTuple(is, e, TopLevelAnds(bs)) => Some(bs map (letTuple(is, e, _)))
case And(exprs) =>
Some(exprs.flatMap(unapply).flatten)
case e =>
diff --git a/src/main/scala/leon/purescala/PrettyPrinter.scala b/src/main/scala/leon/purescala/PrettyPrinter.scala
index b60a3d006..195564fe7 100644
--- a/src/main/scala/leon/purescala/PrettyPrinter.scala
+++ b/src/main/scala/leon/purescala/PrettyPrinter.scala
@@ -256,8 +256,11 @@ class PrettyPrinter(opts: PrinterOptions,
optP { p"$a $op $b" }
case FcallMethodInvocation(rec, fd, id, tps, args) if (fd.annotations.contains("library") || !(opts.printRunnableCode)) =>
-
- p"$rec.$id${nary(tps, ", ", "[", "]")}"
+ p"$rec"
+ if(id.name != "apply") {
+ p".$id"
+ }
+ p"${nary(tps, ", ", "[", "]")}"
if (fd.isRealFunction) {
// The non-present arguments are synthetic function invocations
diff --git a/src/main/scala/leon/synthesis/Rules.scala b/src/main/scala/leon/synthesis/Rules.scala
index 2f463a038..03f9feaf1 100644
--- a/src/main/scala/leon/synthesis/Rules.scala
+++ b/src/main/scala/leon/synthesis/Rules.scala
@@ -48,6 +48,7 @@ object Rules {
//HOFDecomp,
//ExampleGuidedTermExploration,
//BottomUpETE,
+ IntegerEquality,
IfSplit,
InputSplit,
UnusedInput,
diff --git a/src/main/scala/leon/synthesis/comfusy/APAAbstractions.scala b/src/main/scala/leon/synthesis/comfusy/APAAbstractions.scala
new file mode 100644
index 000000000..9d755b50f
--- /dev/null
+++ b/src/main/scala/leon/synthesis/comfusy/APAAbstractions.scala
@@ -0,0 +1,372 @@
+package leon.synthesis.comfusy
+
+/*****************************
+ * Expression abstractions *
+ *****************************/
+
+// dummy
+object APAAbstractions
+
+/** This object provides methods for creating sign abstractions
+ * from integer arithmetic expressions of sign abstractions.
+ * Sign abstraction are typically applied to input terms.
+ *
+ * @author Mikaël Mayer
+ */
+object SignAbstraction {
+
+ /** Creates a sign abstraction from the sum of two sign abstractions.
+ */
+ def addSign(a: SignAbstraction, b: SignAbstraction):ASign = {
+ ASign((a.can_be_positive || b.can_be_positive), (a.can_be_zero && b.can_be_zero) || (a.can_be_positive && b.can_be_negative) || (b.can_be_positive && a.can_be_negative), (a.can_be_negative || b.can_be_negative))
+ }
+
+ /** Creates a sign abstraction from the sum of any number of sign abstractions.
+ */
+ def addSign(l: List[SignAbstraction]):ASign = {
+ l.foldLeft(ASign(false, true, false))(addSign(_, _))
+ }
+
+ /** Creates a sign abstraction from the product of two sign abstractions.
+ */
+ def multSign(a: SignAbstraction, b: SignAbstraction):ASign = {
+ val result = ASign((a.can_be_positive && b.can_be_positive) || (a.can_be_negative && b.can_be_negative), (a.can_be_zero || b.can_be_zero), (a.can_be_positive && b.can_be_negative) || (a.can_be_negative && b.can_be_positive))
+ result
+ }
+
+ /** Creates a sign abstraction from the product of any number of sign abstractions.
+ */
+ def multSign(l: List[SignAbstraction]):ASign = {
+ l.foldLeft(ASign(true, false, false))(multSign(_, _))
+ }
+
+ /** Creates a sign abstraction from the division of sign abstractions.
+ * Raises an error if the divisor can be zero.
+ */
+ def divSign(a: SignAbstraction, b: SignAbstraction):ASign = {
+ if(b.can_be_zero)
+ throw new Exception("Error : "+b+" can be zero")
+ ASign((a.can_be_positive && b.can_be_positive) || (a.can_be_negative && b.can_be_negative), (a.can_be_positive || a.can_be_negative || a.can_be_zero) && (b.can_be_positive || b.can_be_negative || b.can_be_zero), (a.can_be_positive && b.can_be_negative) || (a.can_be_negative && b.can_be_positive))
+ }
+
+ /** Creates a sign abstraction from the opposite of a sign abstraction.
+ */
+ def oppSign(a: SignAbstraction):ASign = {
+ ASign(a.can_be_negative, a.can_be_zero, a.can_be_positive)
+ }
+
+ /** Creates a sign abstraction from the absolute value of a sign abstraction.
+ */
+ def absSign(a: SignAbstraction):ASign = {
+ ASign(a.can_be_negative || a.can_be_positive, a.can_be_zero, false)
+ }
+
+ /** Creates a sign abstraction from a number.
+ */
+ def number(i: Int):ASign = {
+ ASign(i > 0, i == 0, i < 0)
+ }
+
+ /** Creates a sign abstraction from a linear combination of sign abstractions.
+ * i + l1*s1 + l2*s2 + l3*s3 ... + ln * sn
+ *
+ * @param i The constant coefficient of the linear combination
+ * @param l The list of pairs (l_i, s_i) where l_i is an integer and s_i a
+ * sign abstraction.
+ */
+ def linearCombinationSign(i: Int, l: List[(Int, SignAbstraction)]):ASign = {
+ val l_sign = l map { case (i, sa) => multSign(number(i), sa)}
+ addSign(number(i)::l_sign)
+ }
+ def mergeSign(a: SignAbstraction, b: SignAbstraction):ASign = {
+ ASign(a.can_be_positive && b.can_be_positive, a.can_be_zero && b.can_be_zero, a.can_be_negative && b.can_be_negative)
+ }
+}
+
+/** Class SignAbstraction
represents a sign abstraction (>0, =0 and <0).
+ * Any class that extends it should implement the method normalClone()
+ * returning a clone from itself.
+ * The following methods are available:
+ * - Methods assume* (assumeZero, assumePositive...) to clone the expression with a more specialized abstraction
+ * - Methods is* (isPositive, isNegativeZero) which check the sign abstraction capacities.
+ * - The method ropagateSign can be overriden to propagate sign properties to sub-expressions.
+ *
+ * @author Mikaël Mayer
+ */
+trait SignAbstraction {
+
+ //@{ Private section
+ /** Private variables containing the abstraction. */
+ private var private_pos: Boolean = true
+ private var private_nul: Boolean = true
+ private var private_neg: Boolean = true
+
+ /** Clones the expression with a new abstraction. */
+ private def cloneWithSign(a: SignAbstraction):this.type = {
+ this.cloneWithSign(a.can_be_positive, a.can_be_zero, a.can_be_negative)
+ }
+
+ /** Clones the expression with a new abstraction where the signs are given. */
+ private def cloneWithSign(pos_ : Boolean, nul_ : Boolean, neg_ : Boolean):this.type = {
+ val result = normalClone().asInstanceOf[SignAbstraction]
+ result.setSign(pos_, nul_, neg_)
+ result.asInstanceOf[this.type]
+ }
+ //@}
+
+ //@{ Protected section
+ /** A direct method to set up the sign.
+ * Used by subclasses methods only.*/
+ protected def setSign(pos_ :Boolean, nul_ :Boolean, neg_ :Boolean):Unit = {
+ private_pos = pos_
+ private_nul = nul_
+ private_neg = neg_
+ }
+ /** A direct method to set up the sign according to an integer.
+ * Used by subclasses methods only. */
+ protected def setSign(i: Int):Unit = {
+ setSign(i > 0, i == 0, i < 0)
+ }
+
+ /** A direct method to set up the sign according to an existing abstraction.
+ * Used by subclasses methods only. */
+ protected def setSign(i: SignAbstraction):Unit = {
+ setSign(i.can_be_positive, i.can_be_zero, i.can_be_negative)
+ }
+
+ /** Returns a clone of the expression updated with a better or new knowledge about the sign. */
+ protected def propagateSign_internal(i: SignAbstraction):this.type = {
+ cloneWithSign(can_be_positive && i.can_be_positive, can_be_zero && i.can_be_zero, can_be_negative && i.can_be_negative)
+ }
+ //@}
+
+ //@{ Sign check methods
+ /** Returns true if the expression can be positive. */
+ def can_be_positive:Boolean = private_pos
+
+ /** Returns true if the expression can be zero. */
+ def can_be_zero:Boolean = private_nul
+
+ /** Returns true if the expression can be negative. */
+ def can_be_negative:Boolean = private_neg
+
+ /** Returns true if the expression is defined and positive. */
+ def isPositive() = can_be_positive && !can_be_negative && !can_be_zero
+
+ /** Returns true if the expression is defined and negative. */
+ def isNegative() = !can_be_positive && can_be_negative && !can_be_zero
+
+ /** Returns true if the expression is defined and (positive or zero). */
+ def isPositiveZero() = (can_be_positive || can_be_zero) && !can_be_negative
+
+ /** Returns true if the expression cannot be positive nor zero. */
+ def isNotPositiveZero() = !can_be_positive && !can_be_zero
+
+ /** Returns true if the expression is defined and (negative or zero). */
+ def isNegativeZero() = (can_be_negative || can_be_zero) && !can_be_positive
+
+ /** Returns true if the expression cannot be negative nor zero. */
+ def isNotNegativeZero() = !can_be_negative && !can_be_zero
+
+ /** Returns true if the expression cannot be positive. */
+ def isNotPositive() = !can_be_positive
+
+ /** Returns true if the expression cannot be negative. */
+ def isNotNegative() = !can_be_negative
+
+ /** Returns true if the expression is defined an is zero. */
+ def isZero() = can_be_zero && !can_be_positive && !can_be_negative
+
+ /** Returns true if the expression cannot be zero. */
+ def isNotZero() = !can_be_zero
+
+ /** Returns true if the expression is not defined. */
+ def isNotDefined() = !can_be_positive && !can_be_negative && !can_be_zero
+ //@}
+
+ //@{ Assuming sign methods
+ /** Assumes the sign of a integer. */
+ def assumeSign(i: Int):this.type = {
+ propagateSign(ASign(i > 0, i == 0, i < 0))
+ }
+
+ /** Assumes a sign == 0. */
+ def assumeZero():this.type = {
+ assumeSign(0)
+ }
+
+ /** Assumes a sign > 0 */
+ def assumePositive():this.type = {
+ assumeSign(1)
+ }
+
+ /** Assumes a sign < 0 */
+ def assumeNegative():this.type = {
+ assumeSign(-1)
+ }
+
+ /** Assumes a sign >= 0 */
+ def assumePositiveZero():this.type = {
+ propagateSign(ASign(true, true, false))
+ }
+
+ /** Assumes a sign <= 0 */
+ def assumeNegativeZero():this.type = {
+ propagateSign(ASign(false, true, true))
+ }
+
+ /** Assumes a sign != 0 */
+ def assumeNotZero():this.type = {
+ propagateSign(ASign(true, false, true))
+ }
+
+ /** Assumes a sign from a sign abstraction */
+ def assumeSign(i: SignAbstraction):this.type = {
+ propagateSign(ASign(i.can_be_positive, i.can_be_zero, i.can_be_negative))
+ }
+
+ /** Assumes a potential zero sign from a sign abstraction
+ * This is used products : a has the same zero-sign as a*b. */
+ def assumeNotzerobility(i: SignAbstraction):this.type = {
+ propagateSign(ASign(true, i.can_be_zero, true))
+ }
+ //@}
+
+ //@{ Methods to specialize
+ /** A cloning method to implement in order to use this class. */
+ def normalClone():this.type
+
+ /** A method which processes the sign propagation of an expression.
+ * Can be overriden to propagate the sign within sub-elements of the expression. */
+ def propagateSign(i: SignAbstraction):this.type = {
+ propagateSign_internal(i)
+ }
+ //@}
+}
+
+
+/** The simplest class to implement a sign abstraction. */
+case class ASign(pos_ : Boolean, nul_ : Boolean, neg_ : Boolean) extends SignAbstraction {
+ setSign(pos_, nul_, neg_)
+ def normalClone():this.type = ASign(pos_, nul_, neg_).asInstanceOf[this.type]
+}
+
+/** The simplest class to implement a positive or zero sign abstraction. */
+case class PositiveZeroSign() extends SignAbstraction {
+ setSign(true, true, false)
+ def normalClone():this.type = PositiveZeroSign().asInstanceOf[this.type]
+}
+
+/** Class CoefficientAbstraction
represents an all-zero coefficients abstraction.
+ * The coefficients of a linear combination c_0 + c_1*y_1 + ... c_n*y_n are (c_1, ..., c_n)
+ * where the c_i are input terms and the y_i are output variables.
+ * Any class that extends CoefficientAbstraction
should implement the method normalClone()
+ * returning a clone from itself.
+ * The following methods are available:
+ * - Methods assume* (assumeAllCoefficientsAreZero, ...) to clone the expression with a more specialized abstraction
+ * - Checks Methods (allCoefficientsAreZero ...) which check the coefficient abstraction capacities.
+ *
+ * @author Mikaël Mayer
+ */
+trait CoefficientAbstraction {
+
+ //@{ Private section
+ /** Private variables containing the abstraction. */
+ private var p_allCoefficientsCanBeZero: Boolean = true
+ private var p_oneCoefficientsCanBeNonZero: Boolean = true
+
+ private def cloneWithCoefficientAbstraction(c: CoefficientAbstraction):this.type = {
+ this.cloneWithCoefficientAbstraction(c.p_allCoefficientsCanBeZero, c.p_oneCoefficientsCanBeNonZero)
+ }
+ private def cloneWithCoefficientAbstraction(allCoefficientsCanBeZero_ : Boolean, oneCoefficientsCanBeNonZero_ : Boolean):this.type = {
+ val result = normalClone().asInstanceOf[CoefficientAbstraction]
+ result.setCoefficients(allCoefficientsCanBeZero_, oneCoefficientsCanBeNonZero_)
+ result.asInstanceOf[this.type]
+ }
+ //@}
+
+ //@{ Protected section
+ /** A direct method to set up coefficient abstraction.
+ * Used by subclasses methods only. */
+ protected def setNotAllCoefficientsAreZero() = {
+ setCoefficients(false, true)
+ }
+
+ /** A direct method to set up coefficient abstraction.
+ * Used by subclasses methods only. */
+ protected def setNoCoefficients() = {
+ setCoefficients(true, true)
+ }
+
+ /** A direct method to set up coefficient abstraction.
+ * Used by subclasses methods only. */
+ protected def setCoefficients(a: Boolean, n: Boolean) = {
+ p_allCoefficientsCanBeZero = a
+ p_oneCoefficientsCanBeNonZero = n
+ }
+
+ /** A method to clone the expression with the knowledge of another coefficient abstraction. */
+ protected def propagateCoefficientAbstraction(o : CoefficientAbstraction):this.type = {
+ cloneWithCoefficientAbstraction(allCoefficientsCanBeZero && o.allCoefficientsCanBeZero,
+ oneCoefficientsCanBeNonZero && o.oneCoefficientsCanBeNonZero)
+ }
+ //@}
+
+ //@{ Coefficient check methods
+ /** Returns true if all the coefficients are zero. */
+ def allCoefficientsAreZero = p_allCoefficientsCanBeZero && !p_oneCoefficientsCanBeNonZero
+
+ /** Returns true if not all the coefficients are zero. */
+ def notAllCoefficientsAreZero = p_oneCoefficientsCanBeNonZero && !p_allCoefficientsCanBeZero
+
+ /** Returns true if all the coefficients are zero. */
+ def allCoefficientsCanBeZero = p_allCoefficientsCanBeZero
+
+ /** Returns true if not all the coefficients are zero. */
+ def oneCoefficientsCanBeNonZero = p_oneCoefficientsCanBeNonZero
+ //@}
+
+ //@{ Assuming coefficients methods
+ /** Assumes that all coefficients are zero. */
+ def assumeAllCoefficientsAreZero : this.type = {
+ propagateCoefficientAbstraction(ACoef(true, false))
+ }
+
+ /** Assumes that not all coefficients are zero. */
+ def assumeNotAllCoefficientsAreZero : this.type = {
+ cloneWithCoefficientAbstraction(ACoef(false, true))
+ }
+
+ /** Assumes a coefficient abstraction of a sum. */
+ def assumeSumCoefficientAbstraction(a: CoefficientAbstraction, o: CoefficientAbstraction):this.type = {
+ if(a.allCoefficientsAreZero && o.allCoefficientsAreZero) {
+ assumeAllCoefficientsAreZero
+ } else if(a.allCoefficientsAreZero) {
+ propagateCoefficientAbstraction(o)
+ } else if(o.allCoefficientsAreZero) {
+ propagateCoefficientAbstraction(a)
+ } else this
+ }
+
+ /** Assumes a coefficient abstraction of a multiplication by an integer. */
+ def assumeMultCoefficientAbstraction(o: CoefficientAbstraction, i: Int):this.type = {
+ if(i==0) {
+ assumeAllCoefficientsAreZero
+ } else {
+ propagateCoefficientAbstraction(o)
+ }
+ }
+ //@}
+
+
+ //@{ Methods to specialize
+ /** A cloning method to implement in order to use this class. */
+ def normalClone():this.type
+ //@}
+}
+
+/** The simplest class to implement a coefficient abstraction. */
+case class ACoef(a: Boolean, n: Boolean) extends CoefficientAbstraction {
+ setCoefficients(a, n)
+ def normalClone():this.type = ACoef(a, n).asInstanceOf[this.type]
+}
\ No newline at end of file
diff --git a/src/main/scala/leon/synthesis/comfusy/APACondition.scala b/src/main/scala/leon/synthesis/comfusy/APACondition.scala
new file mode 100644
index 000000000..1bc4995f5
--- /dev/null
+++ b/src/main/scala/leon/synthesis/comfusy/APACondition.scala
@@ -0,0 +1,287 @@
+package leon.synthesis.comfusy
+
+/** An abstract precondition or specification representation.
+ * Such preconditions are commonly associated to a program which correctly executes if the condition is true.
+ *
+ * @author Mikaël Mayer
+ */
+sealed abstract class APAAbstractCondition {
+
+ /** Returns a string representing the condition in current rendering mode.
+ * See APASynthesis.rendering_mode to change it. */
+ def toCommonString : String
+
+ /** Returns the boolean value of this condition under a certain variable mapping. */
+ def execute(l: Map[InputVar, Int]): Boolean
+
+ /** Returns the list of input variables contained in this conditions. */
+ def input_variables: List[InputVar]
+}
+
+
+/** This object provides optimization and default values for conditions.
+ *
+ * @author Mikaël Mayer
+ */
+object APACondition {
+
+ /** Returns a typical false precondition. */
+ def False: APACondition = APACondition(Nil, APAFalse(), APAEmptySplitCondition())
+
+ /** Creates a new APACondition after optimizing the arguments
+ *
+ * @param input_assignment The list of input assignments to change if necessary.
+ * @param global_condition A simple formula part of the condition.
+ * @param pf An advanced formula part of the condition (bounded quantifiers, disjunction of other conditions...)
+ * @return An optimized APACondition.
+ */
+ def optimized(input_assignment: List[InputAssignment], global_condition: APAFormula, pf : APASplitCondition):APACondition = {
+ val final_input_variables = (global_condition.input_variables ++ pf.input_variables).distinct
+ val (useful_input_assignments, _) = input_assignment.foldRight((Nil:List[InputAssignment], final_input_variables:List[InputVar])) {
+ case (assignment@SingleInputAssignment(x, t), (useful_input_assignments, useful_input_variables)) =>
+ if(useful_input_variables contains x) { // Then it's useful
+ (assignment::useful_input_assignments, t.input_variables ++ useful_input_variables)
+ } else { // Then this variable is useless
+ (useful_input_assignments, useful_input_variables)
+ }
+ case (assignment@BezoutInputAssignment(xl, tl), (useful_input_assignments, useful_input_variables)) =>
+ if((xl.flatten : List[InputVar]) exists (useful_input_variables contains _)) { // Then it's useful
+ (assignment::useful_input_assignments, (tl flatMap (_.input_variables)) ++ useful_input_variables)
+ } else { // Then this variable is useless
+ (useful_input_assignments, useful_input_variables)
+ }
+ }
+ if(global_condition == APAFalse()) return APACondition.False
+ val result = if(pf.input_variables == Nil) {
+ pf.execute(Map()) match {
+ case false => APACondition.False
+ case true => APACondition(useful_input_assignments, global_condition.simplified, APAEmptySplitCondition())
+ }
+ } else {
+ global_condition.simplified match {
+ case t@APAFalse() => APACondition(useful_input_assignments, t, APAEmptySplitCondition())
+ case t => APACondition(useful_input_assignments, t, pf)
+ }
+ }
+ result
+ }
+}
+
+/** Class representing a program precondition.
+ *
+ * @param input_assignment The list of input assignments to change if necessary.
+ * @param global_condition A simple formula part of the condition.
+ * @param pf An advanced formula part of the condition (bounded quantifiers, disjunction of other conditions...)
+ * @author Mikaël Mayer
+ */
+case class APACondition(input_assignment: List[InputAssignment], global_condition: APAFormula, pf : APASplitCondition) extends APAAbstractCondition {
+
+ /** Adds an assumption after the existing assumptions and before the advanced formula. */
+ def &&(more_cond: APAFormula):APACondition = APACondition(input_assignment, global_condition && more_cond, pf)
+
+ /** Adds an assumption before the existing assumptions.
+ * Used e.g. in the case a condition a % b == 0 if b != 0 is checked before.
+ */
+ def assumeBefore(more_cond: APAFormula):APACondition = APACondition(input_assignment, more_cond && global_condition, pf)
+
+ /** Returns if the condition is trivially true. */
+ def isTrivial(): Boolean = global_condition == APATrue() && pf == APAEmptySplitCondition()
+
+ /** Returns the list of the input variables appearing anywhere in the precondition */
+ def input_variables = ((global_condition.input_variables ++ pf.input_variables) filterNot ((input_assignment flatMap (_.input_variables))).contains).distinct
+
+ /** Returns a parsable string representing the body of the condition, without variable assignment,
+ * under the current rendering mode. */
+ protected def conditionBodyToCommonString(rm: RenderingMode) = (global_condition, pf) match {
+ case (_, APAEmptySplitCondition()) => global_condition.toString
+ case (APATrue(), _) => pf.toString
+ case _ => "("+global_condition.toString+") "+rm.and_symbol+" ("+pf.toString+")"
+ }
+
+ /** Returns a parsable string representing the complete condition */
+ def conditionToCommonString = APASynthesis.rendering_mode match {
+ case RenderingScala => conditionToScalaString
+ case RenderingPython => conditionToPythonString
+ }
+
+ /** Returns a parsable scala string representing the complete condition */
+ def conditionToScalaString = input_assignment match {
+ case Nil => conditionBodyToCommonString(APASynthesis.rendering_mode)
+ case _ => "{"+(input_assignment map (_.toCommonString("")) reduceLeft (_+";"+_)) + ";"+conditionBodyToCommonString(APASynthesis.rendering_mode)+"}"
+ }
+
+ /** Returns a parsable python string representing the complete condition */
+ def conditionToPythonString = {
+ def conditionToPythonString_aux(input_assignment: List[InputAssignment]):String = input_assignment match {
+ case Nil => conditionBodyToCommonString(APASynthesis.rendering_mode)
+ case (assignment::q) =>
+ "(lambda "+assignment.varToPythonString+": "+conditionToPythonString_aux(q)+")("+assignment.valToPythonString+")"
+ }
+ conditionToPythonString_aux(input_assignment)
+ }
+
+ /** toString alias */
+ def toCommonString = conditionToCommonString
+
+ /** toString alias */
+ override def toString = toCommonString
+
+ def execute(l: Map[InputVar, Int]): Boolean = {
+ var input_value_map = l
+ input_assignment foreach {
+ case SingleInputAssignment(v, t) => val input_value_assignment = APAAbstractProgram.toAPAInputAssignment(input_value_map)
+ t.replaceList(input_value_assignment) match {
+ case APAInputCombination(i, Nil) => input_value_map += (v -> i)
+ case t => throw new Exception("Was not able to reduce term "+t+" to integer under the mapping "+input_value_map)
+ }
+ case BezoutInputAssignment(vl, tl) => val input_value_assignment = APAAbstractProgram.toAPAInputAssignment(input_value_map)
+ val bezout_coefs:List[Int] = tl map (_.replaceList(input_value_assignment)) map {
+ case APAInputCombination(i, Nil) => i
+ case t => throw new Exception("Was not able to reduce term "+t+" to integer under the mapping "+input_value_map)
+ }
+ // Double zip and add all assignments to variables
+ (vl zip Common.bezoutWithBase(1, bezout_coefs)) map { case (l1, l2) => l1 zip l2 } foreach { _ foreach { input_value_map += _ } }
+ }
+ global_condition.replaceListInput(APAAbstractProgram.toAPAInputAssignment(input_value_map)) match {
+ case APATrue() => pf.execute(input_value_map)
+ case APAFalse() => false
+ case t =>
+ throw new Exception ("Could not find the truth value of "+this+"\n under the mapping "+input_value_map+".\n Got the result: "+t)
+ }
+ }
+}
+
+/** Advanced conditions.
+ * @author Mikaël Mayer
+ */
+sealed abstract class APASplitCondition extends APAAbstractCondition {
+
+ /** Returns a string representing the condition. */
+ override def toString = toStringGeneral(APASynthesis.rendering_mode)
+
+ /** Method to override. Return astring representing the solution under the given RenderingMode */
+ protected def toStringGeneral(rm: RenderingMode):String
+
+ /** Alias for toString */
+ def toCommonString:String = toStringGeneral(APASynthesis.rendering_mode)
+}
+
+/** Disjunction of all conditions.
+ * c1 || c2 || c3 ...
+ * It is named CaseSplit because the condition is derived from a sequence of if-then-else statements/
+ * if(c1) ... else if(c2) ... else if(c3) ...
+ *
+ * @author Mikaël Mayer
+ */
+case class APACaseSplitCondition(csl: List[APAAbstractCondition]) extends APASplitCondition {
+
+ /** Returns the list of the input variables contained in this disjunction */
+ def input_variables = (csl flatMap (_.input_variables)).distinct
+
+ /** Returns a boolean indicating if the disjunction is true. */
+ def execute(l: Map[InputVar, Int]): Boolean = csl exists (_.execute(l))
+
+ /** Returns a string representing this disjunction. */
+ protected def toStringGeneral(rm: RenderingMode):String = csl map { t => ("("+(t.toCommonString)+")") } reduceLeft (_ + " "+rm.or_symbol+" " + _)
+
+ /** Returns a condition made from this disjunction. */
+ def toCondition: APACondition = APACondition(Nil, APATrue(), this)
+}
+
+/** Empty advanced condition
+ * Equivalent to a true precondition.
+ *
+ * @author Mikaël Mayer
+ */
+case class APAEmptySplitCondition() extends APASplitCondition {
+
+ /** Returns the empty set of input variables. */
+ def input_variables = Nil
+
+ /** Returns true, the truth value of this expression */
+ def execute(l: Map[InputVar, Int]): Boolean = true
+
+ /** Returns an empty string, which represents an empty condition. */
+ protected def toStringGeneral(rm: RenderingMode) = ""
+}
+
+/** Bounded quantifier representation.
+ * The variable vector will iterate over [lower_range, upper range]^n where n is the number of variables.
+ * The provided global condition is implicitly existentially quantified over this bounded variable vector.
+ * To sum up, the condition is true if global_condition holds for one tuple in the hypercube
+ *
+ * @param vl A list of input variables (v1...vn)
+ * @param lower_range The lower range for each variable.
+ * @param upper_range The upper range for each variable.
+ * @param global_condition The implicitly existentially quantified over the variables.
+ * @author Mikaël Mayer
+ */
+case class APAForCondition(vl: List[InputVar], lower_range: APAInputTerm, upper_range: APAInputTerm, global_condition: APACondition) extends APASplitCondition {
+
+ /** Returns the list of input variables in this condition, not part of the existentially quantified variables. */
+ def input_variables = (global_condition.input_variables) filterNot (vl.contains)
+
+ /** Returns a string representing the bounded quantified condition in a programming language. */
+ protected def toStringGeneral(rm: RenderingMode):String = {
+ rm match {
+ case RenderingScala => toScalaString
+ case RenderingPython => toPythonString
+ }
+ }
+
+ /** Returns a string containing the tuple of the existential variable's names */
+ def varsToString(vl : List[InputVar]) : String = vl match {
+ case Nil => ""
+ case (v::Nil) => v.name
+ case (v::q) => "("+v.name+","+varsToString(q)+")"
+ }
+
+ /** Returns a python string representing the condition */
+ def toPythonString:String = {
+ val basic_range = "xrange("+lower_range+", 1 + "+upper_range+")"
+ val list_ranges = "("+varsToString(vl)+" "+ (vl map { case i => "for "+i.name+" in "+basic_range}).reduceLeft(_ + " " + _) + ")"
+ val exists_construct = "lambda a, "+varsToString(vl)+": a or "+ global_condition.toCommonString
+ val condition = "reduce("+exists_construct+", "+list_ranges+", False)"
+ condition
+ }
+
+ /** Returns a scala string representing the condition */
+ def toScalaString:String = {
+ val basic_range = "("+lower_range+" to "+upper_range+")"
+ var ranges = basic_range
+ vl.tail foreach {k =>
+ ranges = ranges + " flatMap { t => "+basic_range+" map { i => (i, t) } } "
+ }
+
+ val expanded_vars = varsToString(vl)
+ val find_string = ranges + " exists { case "+expanded_vars+" => "
+ val cond_string = global_condition.toCommonString
+ val match_string = " }"
+ (find_string::cond_string::match_string::Nil).reduceLeft((_ + _))
+ }
+
+ /** Returns a boolean indicating if the condition is true under a given mapping */
+ def execute(l: Map[InputVar, Int]): Boolean = {
+ if(global_condition == APACondition.False) return false
+ val lr:Int = lower_range.replaceList(APAAbstractProgram.toAPAInputAssignment(l)) match {
+ case APAInputCombination(k, Nil) => k
+ case t => throw new Exception("Was not able to reduce term "+t+" to integer under the mapping "+l)
+ }
+ val ur:Int = upper_range.replaceList(APAAbstractProgram.toAPAInputAssignment(l)) match {
+ case APAInputCombination(k, Nil) => k
+ case t =>
+ throw new Exception("Was not able to reduce term "+t+" to integer under the mapping "+l)
+ }
+ val basic_range = (lr to ur)
+ def possible_assignments(vl: List[InputVar], i: Int, current_assignment: List[(InputVar, Int)]) : Stream[List[(InputVar, Int)]] = vl match {
+ case Nil => Stream(current_assignment)
+ case (v::q) if i > ur => Stream()
+ case (v::q) => possible_assignments(q, lr, (v, i)::current_assignment) append possible_assignments(vl, i+1, current_assignment)
+ }
+ val assignments = possible_assignments(vl, lr, Nil)
+
+ assignments exists { assignments =>
+ global_condition.execute(l ++ assignments)
+ }
+ }
+}
diff --git a/src/main/scala/leon/synthesis/comfusy/APAInputAssignments.scala b/src/main/scala/leon/synthesis/comfusy/APAInputAssignments.scala
new file mode 100644
index 000000000..c4a7e510e
--- /dev/null
+++ b/src/main/scala/leon/synthesis/comfusy/APAInputAssignments.scala
@@ -0,0 +1,197 @@
+package leon.synthesis.comfusy
+
+//dummy
+object APAInputAssignments
+
+/** This object offers global methods methods to deal with input assignments.
+ */
+object InputAssignment {
+ //Combines input sentences
+ def listToCommonString(input_assignment:List[InputAssignment], indent:String):String = {
+ val prog_input = input_assignment map (_.toCommonString(indent)) match {
+ case Nil => ""
+ case l => l reduceLeft {(t1, t2) => (t1 + "\n" + t2)}
+ }
+ prog_input
+ }
+}
+
+/** An input assignment is a way to assign some expressions to input variables
+ * like in "val a = b/2+c", where a, b and c are input variables.
+ */
+sealed abstract class InputAssignment {
+
+ /** Returns a list of input variables contained in the expression of this input assignment */
+ def input_variables: List[InputVar]
+
+ /** Extracts a non-exhaustive list of simple assignments of InputTerms to InputVars. */
+ def extract:List[(InputVar, APAInputTerm)]
+
+ /** Returns a string representing this assignment under the current rendering mode. */
+ def toCommonString(indent: String):String = APASynthesis.rendering_mode match {
+ case RenderingScala => toScalaString(indent)
+ case RenderingPython => toPythonString(indent)
+ }
+
+ /** Returns a scala string representing the variables on the left of val ... = ...
*/
+ def varToScalaString = this match {
+ case SingleInputAssignment(i, t) => i.name
+ case BezoutInputAssignment(vl, tl) => "List(" + (vl map { l => "List(" + (l map (_.name) reduceLeft (_+","+_)) + ")"} reduceLeft (_+","+_)) + ")"
+ }
+
+ /** Returns a scala string representing the value on the right of val ... = ...
*/
+ def valToScalaString = this match {
+ case SingleInputAssignment(i, t) => t
+ case BezoutInputAssignment(vl, tl) => "Common.bezoutWithBase(1, "+(tl map (_.toString) reduceLeft (_+", "+_))+")"
+ }
+
+ /** Returns a python string representing the variables on the left of val ... = ...
*/
+ def varToPythonString = this match {
+ case SingleInputAssignment(i, t) => i.name
+ case BezoutInputAssignment(vl, tl) => "(" + (vl map { l => "(" + (l map (_.name) reduceLeft (_+","+_)) + ")"} reduceLeft (_+","+_)) + ")"
+ }
+
+ /** Returns a python string representing the value on the right of val ... = ...
*/
+ def valToPythonString = this match {
+ case SingleInputAssignment(i, t) => t
+ case BezoutInputAssignment(vl, tl) => "bezoutWithBase(1, "+(tl map (_.toString) reduceLeft (_+", "+_))+")"
+ }
+
+ /** Returns the whole assignment as a scala string */
+ def toScalaString(indent: String): String = {
+ indent+"val "+ varToScalaString + " = " + valToScalaString
+ }
+
+ /** Returns the whole assignment as a python string */
+ def toPythonString(indent: String): String = {
+ indent+ varToPythonString + " = " + valToPythonString
+ }
+
+ /** Returns the assignment were all input variables have been replaced by corresponding input terms. */
+ def replaceList(l : List[(InputVar, APAInputTerm)]):List[InputAssignment]
+
+ /** Returns the assignment were the sign abstraction s is applied to each occurence of t1 */
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):InputAssignment
+}
+
+// A simple assignment corresponding to val v = t
+case class SingleInputAssignment(v: InputVar, t: APAInputTerm) extends InputAssignment {
+ def input_variables = List(v)
+ def extract = List((v, t))
+ def replaceList(l : List[(InputVar, APAInputTerm)]) = List(SingleInputAssignment(v, t.replaceList(l)))
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction) = SingleInputAssignment(v, t.assumeSignInputTerm(t1, s))
+}
+// A complex Bézout assignemnt corresponding to val (v1::v2::Nil)::(v3::v4::Nil)::Nil = Common.bezoutWithBase(1, t1, t2)
+case class BezoutInputAssignment(v: List[List[InputVar]], t: List[APAInputTerm]) extends InputAssignment {
+ def input_variables = v.flatten : List[InputVar]
+ def extract = Nil
+ def replaceList(l: List[(InputVar, APAInputTerm)]) = BezoutInputAssignment(v, t map (_.replaceList(l))).simplified
+
+ /** Returns a simplified version of the assignment as a list of input assignments. */
+ /** Simplification occurs if some coefficients are equal to 1 or -1, or in other simple cases. */
+ def simplified: List[InputAssignment] = {
+ t map (_.simplified) match {
+ case t if t forall {
+ case APAInputCombination(i, Nil) => true
+ case _ => false
+ } =>
+ val bezout_coefs:List[Int] = t map {
+ case APAInputCombination(i, Nil) => i
+ case t => throw new Exception("Theoretically unreachable section : "+t+" should be an integer")
+ }
+ // Double zip and add all assignments to variables
+ val assignments: List[(InputVar, APAInputTerm)] = (
+ (v zip Common.bezoutWithBase(1, bezout_coefs)) map {
+ case (l1, l2) => l1 zip (
+ l2 map {
+ case i => APAInputCombination(i)
+ }
+ )
+ }
+ ).flatten
+ val assignment_converted = assignments.map({ case (v, t) => SingleInputAssignment(v, t)})
+ assignment_converted
+ case a::Nil => // This corresponds to equations of the type 1+a*v = 0. If there is a solution, it is exactly -a (a has to be equal to 1 or -1)
+ val List(List(iv)) = v
+ List(SingleInputAssignment(iv, -a))
+ case a::b::Nil => // This corresponds to equations of the type 1+a*u+b*v = 0
+ // There is an optimization if either a or b has an absolute value of 1.
+ (a, b) match {
+ case (APAInputCombination(i, Nil), b) if Math.abs(i) == 1 =>
+ // case 1 + i*u + a*v == 0
+ val index_b = 2
+ var map_index_term = Map[Int, APAInputTerm]() + (index_b -> b)
+ val new_ints = Common.bezoutWithBase(1, i, index_b)
+ val assignments = convertAssignments(v, new_ints, map_index_term)
+ val assignment_converted = assignments.map({ case (v, t) => SingleInputAssignment(v, t)})
+ assignment_converted
+ case (a, APAInputCombination(j, Nil)) if Math.abs(j) == 1 =>
+ val index_a = 2
+ var map_index_term = Map[Int, APAInputTerm]() + (index_a -> a)
+ val new_ints = Common.bezoutWithBase(1, index_a, j)
+ val assignments = convertAssignments(v, new_ints, map_index_term)
+ val assignment_converted = assignments.map({ case (v, t) => SingleInputAssignment(v, t)})
+ assignment_converted
+ case _ => List(BezoutInputAssignment(v, t))
+ }
+ case t =>
+ val t_indexed = t.zipWithIndex
+ t_indexed find {
+ case (APAInputCombination(i, Nil), index) if Math.abs(i) == 1 => true
+ case _ => false
+ } match {
+ case Some((APAInputCombination(one_coefficient, Nil), index)) =>
+ // Corresponds to something trivial like 1 + a*x + b*y + z + c*w = 0
+ // The corresponding assignment is x = y1, y = y2, z = -1-a*x-b*y-c*w and w = y3
+ // (1 )T (0, 0, -1, 0) (a)
+ // (ya) . (1, 0, -a, 0) . (b) + 1 == 0
+ // (yb) (0, 1, -b, 0) (1)
+ // (yc) (0, 0, -c, 1) (c)
+
+ // To find the solution, encode a = 10, b = 20, c=30, and in the found solution, replace -10 by -a, etc.
+ var map_index_term = Map[Int, APAInputTerm]()
+
+ val to_solve_bezout_on = t_indexed map { case (term, i) =>
+ if(i == index) {
+ one_coefficient
+ } else {
+ val final_index = 10*i+10
+ map_index_term += (final_index -> term)
+ final_index
+ }
+ }
+ val new_ints = Common.bezoutWithBase(1, to_solve_bezout_on)
+ val assignments = convertAssignments(v, new_ints, map_index_term)
+ val assignment_converted = assignments.map({ case (v, t) => SingleInputAssignment(v, t)})
+ assignment_converted
+
+ case _ => // Essentially None
+ List(BezoutInputAssignment(v, t))
+ }
+ }
+ }
+
+ /** Converts an integer Bézout solution to a InputTerm solution, where specific integers */
+ /** given in map_index_terms are replaced with some input terms. */
+ def convertAssignments(v: List[List[InputVar]],
+ solved_for_ints: List[List[Int]],
+ map_index_term: Map[Int, APAInputTerm]): List[(InputVar, APAInputTerm)] = {
+ (
+ (v zip solved_for_ints) map {
+ case (l1, l2) => l1 zip (
+ l2 map {
+ case index if map_index_term contains index =>
+ map_index_term(index)
+ case index if map_index_term contains (-index) =>
+ -map_index_term(index)
+ case i =>
+ APAInputCombination(i)
+ }
+ )
+ }
+ ).flatten
+ }
+
+ /** Propagate a sign assumption. Does nothing for Bézout assignment. */
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction) = this
+}
diff --git a/src/main/scala/leon/synthesis/comfusy/APAInputSyntaxTree.scala b/src/main/scala/leon/synthesis/comfusy/APAInputSyntaxTree.scala
new file mode 100644
index 000000000..cfdea5f37
--- /dev/null
+++ b/src/main/scala/leon/synthesis/comfusy/APAInputSyntaxTree.scala
@@ -0,0 +1,721 @@
+package leon.synthesis.comfusy
+
+import scala.annotation.tailrec
+import scala.collection.mutable.ListBuffer
+
+// dummy
+object APAInputSyntaxTree
+
+/** Provides several methods to deal with input terms.
+ *
+ * @author Mikaël Mayer
+ */
+object APAInputTerm {
+ def partitionInteger(l: List[APAInputTerm]): (List[Int], List[APAInputTerm]) = l match {
+ case Nil => (Nil, Nil)
+ case (APAInputCombination(n, Nil)::q) =>
+ val (a, b) = partitionInteger(q)
+ (n::a, b)
+ case (p::q) =>
+ val (a, b) = partitionInteger(q)
+ (a, p::b)
+ }
+}
+
+/*****************
+ * Input terms *
+ *****************/
+
+/** Trait expressing that an expression can be converted to an InputTerm
+ * It is useful to deal with Input variables, which are not directly InputTerms
+ * in order not to overload the pattern matching.
+ *
+ * @author Mikaël Mayer
+ */
+trait ConvertibleToInputTerm {
+ implicit def toInputTerm():APAInputTerm
+}
+
+/** A class defining a general input term, that is, containing only input variables and integers.
+ * A sign abstraction is provided for each term.
+ *
+ * @author Mikaël Mayer
+ */
+sealed abstract class APAInputTerm extends SignAbstraction {
+
+ /** Returns the same expression, but simplified. */
+ def simplified:APAInputTerm // OptimizeMe : Store when it's already simplified in order not to compute two times the same thing
+
+ /** @return The list of Input variables that this expression contains. */
+ def input_variables: List[InputVar]
+
+ //@{ Operators
+ /** @param that A combination eventually containing output variables. */
+ /** @return The sum of this input term and the provided APACombination. */
+ def +(that : APACombination):APACombination = that + APACombination(this)
+
+ /** @return The difference of this input term and the provided APACombination. */
+ def -(that : APACombination):APACombination = -that + APACombination(this)
+
+ /** @return The product of this input term and the provided APACombination. */
+ def *(that : APACombination):APACombination = that * this
+
+ /** @return The sum of this input term and the provided input term. */
+ def +(that : APAInputTerm): APAInputTerm = APAInputAddition(this, that).simplified
+
+ /** @return The division of this input term by the provided input term. */
+ def /(that : APAInputTerm): APAInputTerm = APAInputDivision(this, that).simplified
+
+ /** @return The product of this input term by the provided input term. */
+ def *(that : APAInputTerm): APAInputTerm = APAInputMultiplication(this, that).simplified
+
+ /** @return The difference between this input term and the provided one. */
+ def -(that : APAInputTerm): APAInputTerm = (this, that) match {
+ case (t1: APAInputCombination, t2: APAInputCombination) => t1 - t2
+ case _ => this+(that*APAInputCombination(-1))
+ }
+
+ /** @return The opposite of this input term. */
+ def unary_-(): APAInputTerm = APAInputCombination(0, Nil) - this
+ //@}
+
+ /** @return This input term where all occurences of y have been replaced by t. */
+ def replace(y: InputVar, t: APAInputTerm):APAInputTerm
+
+ /** @return This input term where all occurences of y have been replaced by t. */
+ def replaceList(lxt : List[(InputVar, APAInputTerm)]): APAInputTerm = {
+ lxt.foldLeft(this){ case (result, (x, t)) => result.replace(x, t) }
+ }
+
+ /** @return This input term where the sign abstraction s is applied to all occurences of t1 */
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAInputTerm = {
+ (this, t1, -t1) match {
+ case (t0:APAInputCombination, t1:APAInputCombination, _) if t0 == t1 =>
+ val result = t1.assumeSign(s).propagateSign(this)
+ result
+ case (t0:APAInputCombination, _, mt1:APAInputCombination) if t0 == mt1 =>
+ val result = (-t1.assumeSign(s)).propagateSign(this)
+ result
+ case (t0:APAInputCombination, t1:APAInputCombination, mt1:APAInputCombination) =>
+ this
+ case _ =>
+ this
+ }
+ }
+
+ /** @return The integer that this input term represents if it exists, else throws an exception. */
+ def toInt: Int = this match {
+ case APAInputCombination(i, Nil) => i
+ case _ =>
+ throw new Exception(this + " cannot be converted to an integer")
+ }
+
+ /** Converts this input term to a string */
+ override def toString = toGeneralString
+
+ /** Converts this input term to a string in the current rendering mode */
+ /** See APASynthesis.rendering_mode. */
+ def toGeneralString: String = APASynthesis.rendering_mode match {
+ case rm@RenderingPython => toPythonString(rm)
+ case rm@RenderingScala => toScalaString(rm)
+ }
+
+ /** Converts this input term to a Python string. */
+ /** @rm should be a RenderingPython() */
+ protected def toPythonString(rm: RenderingMode): String = this match {
+ case APAInputLCM(l) => rm.lcm_symbol+"(["+(l map (_.toCommonString(rm)) reduceLeft (_ + "," + _)) +"])"
+ case APAInputGCD(l) => rm.gcd_symbol+"(["+(l map (_.toCommonString(rm)) reduceLeft (_ + "," + _)) +"])"
+ case _ => toCommonString(rm)
+ }
+
+ /** Converts this input term to a Scala string. */
+ /** @rm should be a RenderingScala() */
+ protected def toScalaString(rm: RenderingMode): String = this match {
+ case APAInputLCM(l) => rm.lcm_symbol+"(List("+(l map (_.toCommonString(rm)) reduceLeft (_ + "," + _)) +"))"
+ case APAInputGCD(l) => rm.gcd_symbol+"(List("+(l map (_.toCommonString(rm)) reduceLeft (_ + "," + _)) +"))"
+ case _ => toCommonString(rm)
+ }
+
+ /** Converts this input term to a common string in the provided rendering mode. */
+ /** rm should be equal to APASynthesis.rendering_mode */
+ def toCommonString(rm:RenderingMode):String = this match {
+ case APAInputMultiplication(Nil) => "1"
+ case APAInputMultiplication(a::Nil) => a.toCommonString(rm)
+ case APAInputMultiplication(l) => l map {
+ case el =>
+ val s = el.toCommonString(rm)
+ if(((s indexOf '-') >= 0) || ((s indexOf '+') >= 0) || ((s indexOf '/') >= 0)) "("+s+")" else s
+ } reduceLeft (_ + "*" + _)
+ case APAInputDivision(Nil, ld) => "1/("+APAInputMultiplication(ld).toCommonString(rm)+")"
+ case APAInputDivision(ln, Nil) => APAInputMultiplication(ln).toCommonString(rm)
+ case APAInputDivision(ln, ld) =>
+ val num = APAInputMultiplication(ln).toCommonString(rm)
+ val den = APAInputMultiplication(ld).toCommonString(rm)
+ val num_string = (if((num indexOf '+') >= 0 || (num indexOf '-') >= 0 || (num indexOf '+') >= 0) "("+num+")" else num )
+ val den_string = (if((den indexOf '+') >= 0 || (den indexOf '-') >= 0 || (den indexOf '+') >= 0) "("+den+")" else den )
+ num_string +"/"+den_string
+ case APAInputAddition(l) => l map {
+ case el =>
+ val s = el.toCommonString(rm)
+ if((s indexOf '-') >= 0) "("+s+")" else s
+ } reduceLeft (_ + " + " + _)
+ case APAInputAbs(e) => rm.abs_symbol + "("+e.toCommonString(rm)+")"
+ case APAInputLCM(Nil) =>
+ throw new Exception("What is this lcm that has not been simplified ??")
+ case APAInputLCM(l) => rm.lcm_symbol+"(List("+(l map (_.toCommonString(rm)) reduceLeft (_ + "," + _)) +"))"
+ case APAInputGCD(l) => rm.gcd_symbol+"(List("+(l map (_.toCommonString(rm)) reduceLeft (_ + "," + _)) +"))"
+ case t:APAInputCombination => t.toNiceString
+ /*case APAInputMod(operand, divisor) =>
+ val num = operand.toCommonString(rm)
+ val den = operand.toCommonString(rm)
+ val num_string = (if((num indexOf '+') >= 0 || (num indexOf '-') >= 0 || (num indexOf '+') >= 0) "("+num+")" else num )
+ val den_string = (if((den indexOf '+') >= 0 || (den indexOf '-') >= 0 || (den indexOf '+') >= 0) "("+den+")" else den )
+ val final_den_string = if(divisor.isPositive) den_string else (rm.abs_symbol+"("+den_string+")")
+ rm.mod_function(num_string, final_den_string)*/
+ //case _ => super.toString
+ }
+}
+
+/** Definition of an input variable. */
+case class InputVar(name: String) extends SignAbstraction with ConvertibleToInputTerm with APAVariable {
+
+ /** Clones the variable without the sign abstraction */
+ def normalClone():this.type = InputVar(name).asInstanceOf[this.type]
+
+ /** Return an InputTerm containing the variable. */
+ def toInputTerm():APAInputCombination = {
+ if(isZero) return APAInputCombination(0)
+ APAInputCombination(this)
+ }
+ // Syntactic sugar
+ //def +(pac : APACombination) = pac+APACombination(this)
+ //def +(v : InputVar) = APACombination(v)+APACombination(this)
+ //def +(v : OutputVar) = APACombination(v)+APACombination(this)
+ //def *(i : Int) = APAInputCombination(0, (i, this)::Nil)
+ //def *(that: APAInputTerm) = APAInputMultiplication(APAInputCombination(this), that)
+}
+
+/** Object to provide more constructors for APAInputCombination.
+ */
+object APAInputCombination {
+ def apply(i: Int):APAInputCombination = APAInputCombination(i, Nil)
+ def apply(i: InputVar):APAInputCombination = APAInputCombination(0, (1, i)::Nil).propagateSign(i)
+}
+
+/** A linear combination of input variables, with a constant coefficient.
+ */
+case class APAInputCombination(coefficient: Int, input_linear: List[(Int, InputVar)]) extends APAInputTerm {
+ setSign(SignAbstraction.linearCombinationSign(coefficient, input_linear))
+
+ /** Clones the expression without the sign abstraction. */
+ def normalClone():this.type = APAInputCombination(coefficient, input_linear).asInstanceOf[this.type]
+
+ /** Returns the list of input variables that this expression contains. */
+ def input_variables: List[InputVar] = input_linear map (_._2)
+
+ /** Returns true if the variable i1 is has a name lexicographically less than the variable i2. */
+ def by_InputVar_name(i1:(Int, InputVar), i2:(Int, InputVar)) : Boolean = (i1, i2) match {
+ case ((_, InputVar(name1)), (_, InputVar(name2))) => name1 < name2
+ }
+
+ /** Adds the coefficiented variable i to the list of existing coefficiented regrouped_vars. */
+ /** Assumes that if the variable appears exist, it appears first. */
+ def fold_Inputvar_name(i:(Int, InputVar), regrouped_vars:List[(Int, InputVar)]):List[(Int, InputVar)] = (i, regrouped_vars) match {
+ case (i, Nil) => i::Nil
+ case ((coef1, InputVar(name1)), (coef2, InputVar(name2))::q) if name1 == name2 => (coef1 + coef2, InputVar(name1))::q
+ case (i, q) => i::q
+ }
+
+ /** Intercepts the sign propagation, and if there is a unique variable, propagates the sign abstraction to it. */
+ override def propagateSign(s: SignAbstraction):this.type = { //Intercepts the sign propagation
+ val result = (coefficient, input_linear) match {
+ case (0, (i, v)::Nil) =>
+ val new_v = v.assumeSign(SignAbstraction.multSign(SignAbstraction.mergeSign(this, s), SignAbstraction.number(i)))
+ APAInputCombination(0, (i, new_v)::Nil)
+ case _ => this
+ }
+ result.propagateSign_internal(s).asInstanceOf[this.type]
+ }
+
+ /** Returns a simplified version of this input combination.
+ * Guarantees that
+ * - The variables are alphabetically sorted,
+ * - There are no null coefficients
+ */
+ def simplified: APAInputCombination = {
+ if(isZero) return APAInputCombination(0)
+ val input_linear2 = (input_linear sortWith by_InputVar_name ).foldRight[List[(Int, InputVar)]](Nil){ case (a, b) => fold_Inputvar_name(a, b)}
+ val input_linear3: List[(Int, InputVar)] = input_linear2 match {
+ case (i, v)::Nil => (i, v.assumeSign(SignAbstraction.multSign(this, SignAbstraction.number(i))))::Nil
+ case _ => input_linear2
+ }
+ APAInputCombination(coefficient, input_linear3 filterNot { case (i, v) => i == 0 || v.isZero}).propagateSign(this)
+ }
+
+ /** Returns the list of the coefficients in front of the input variables + the constant coefficient. */
+ def coefficient_list = (coefficient :: ((input_linear map (_._1)) ))
+
+ /** Returns true if there is a non-null coefficient in the combination. */
+ def has_gcd_coefs: Boolean = coefficient_list exists (_ != 0)
+
+ /** Returns the gcd of all coefficients. has_gcd_coefs
is assumed. */
+ def gcd_coefs = Common.gcdlist(coefficient_list)
+
+ /** Returns the first sign present.
+ * If this sign is negative in equations like -a+b == 0,
+ * it is used to gain a character and produce a-b == 0
+ */
+ def first_sign_present = coefficient_list find (_ != 0) match {
+ case Some(i) => if(i > 0) 1 else -1
+ case None => 1
+ }
+
+ /** Returns the division of this combination by an integer. */
+ /** Needs the coefficients to be divisible by i. */
+ def /(i : Int): APAInputCombination = {
+ APAInputCombination(coefficient / i, input_linear map {t => (t._1 / i, t._2)}).assumeSign(SignAbstraction.multSign(this, SignAbstraction.number(i)))
+ }
+
+ /** Returns true if this combination can be safely divisible by i. */
+ def safelyDivisibleBy(i : Int): Boolean = {
+ coefficient % i == 0 && (input_linear forall { case (k, v) => k % i == 0})
+ }
+
+ /** Returns the division of an input combination by another. */
+ /** The result is not necessarily a input combination */
+ def /(that : APAInputCombination): APAInputTerm = {
+ APAInputDivision(this, that).simplified
+ }
+
+ /** Returns the multiplication of this input combination by an integer. */
+ def *(i : Int): APAInputCombination = {
+ APAInputCombination(coefficient * i, input_linear map {t => (t._1 * i, t._2)}).assumeSign(SignAbstraction.multSign(this, SignAbstraction.number(i)))
+ }
+
+ /** Returns the multiplication of this input combination by another input combination. */
+ /** The result is not necessarily a input combination */
+ def *(that : APAInputCombination): APAInputTerm = {
+ APAInputMultiplication(this, that).simplified
+ }
+
+ /** Returns the sum of two input combinations. */
+ def +(pac : APAInputCombination): APAInputCombination = pac match {
+ case APAInputCombination(c, i) =>
+ APAInputCombination(coefficient + c, input_linear ++ i).simplified.assumeSign(SignAbstraction.addSign(this, pac))
+ }
+
+ /** Returns the difference between two input combinations. */
+ def -(that : APAInputCombination): APAInputCombination = this + (that * (-1))
+
+ /** Returns the sum of this input combination with a coefficiented variable. */
+ def +(kv : (Int, InputVar)): APAInputCombination = this + APAInputCombination(0, kv::Nil)
+
+ /** Returns the sum of this input combination with an integer. */
+ def +(k : Int): APAInputCombination = this + APAInputCombination(k, Nil)
+
+ /** Returns the difference of this input combination with a coefficiented variable. */
+ def -(kv : (Int, InputVar)): APAInputCombination = this - APAInputCombination(0, kv::Nil)
+
+ /** Returns the difference of this input combination with an integer. */
+ def -(k : Int): APAInputCombination = this + APAInputCombination(-k, Nil)
+
+ /** Returns the opposite of this input combination. */
+ override def unary_-(): APAInputCombination = (APAInputCombination(0, Nil) - this).propagateSign(SignAbstraction.oppSign(this))
+
+ /** Returns the linear expression where the input variable y has been replaced by the expression t. */
+ def replace(y: InputVar, t: APAInputTerm):APAInputTerm = {
+ val (input_linear_with_y, input_linear_without_y) = input_linear partition (_._2 == y)
+ val pac_without_y = APAInputCombination(coefficient, input_linear_without_y)
+ val total_y_coefficient = (input_linear_with_y map (_._1)).foldLeft(0)(_+_)
+ val result = t match {
+ case t:APAInputCombination =>
+ pac_without_y + (t*total_y_coefficient)
+ case _ =>
+ APAInputAddition(pac_without_y, APAInputMultiplication(APAInputCombination(total_y_coefficient), t))
+ }
+ result.propagateSign(this)
+ }
+
+ /** Returns this expression where the fact that t1 has sign s has been taken into account. */
+ override def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction) = {
+ t1 match {
+ case t@APAInputCombination(coefficient2, Nil) => this // Would be strange to arrive there.
+ case t@APAInputCombination(coefficient2, (i, v)::l) => // i is not null,
+ input_linear find (_._2 == v) match {
+ case Some((i2, v2)) =>
+ val t_assumed = t.assumeSign(s)
+ val resultWithoutT = (this*i-t_assumed*i2)
+ val resultAddingT =(t_assumed*i2)
+ val resultMultipliedByI = resultWithoutT+resultAddingT
+ val result = resultMultipliedByI/i
+ result
+ case None => this // This variable is not there, so we cannot conclude anything.
+ }
+ case _ => this
+
+ }
+ }
+
+ /** Returns a string representing this linear combination. */
+ override def toString = toNiceString // Comment this to keep the abstract syntax tree representation
+
+ /** Returns a nice string representing an usual linear combination, e.g. like 5+3a. */
+ def toNiceString:String = {
+ def inputElementToString(kv : (Int, InputVar)) = kv._1 match {
+ case 1 => kv._2.name
+ case -1 => "-" + kv._2.name
+ case k => k + "*" + kv._2.name
+ }
+ def makePlus(l: List[String]):String = l match {
+ case Nil => ""
+ case a::p => val s = makePlus(p)
+ if(s=="") a
+ else if(a=="") s
+ else if(s.charAt(0) == '-')
+ a + s
+ else
+ a + "+" + s
+ }
+ var c_string = if(coefficient == 0) "" else coefficient.toString
+ var i_string = input_linear match {
+ case Nil => ""
+ case a::l => l.foldLeft(inputElementToString(a)) { (s, t2) =>
+ val t2s = inputElementToString(t2)
+ s + (if(t2s.charAt(0) =='-') t2s else "+" + t2s)}
+ }
+ val s = makePlus(c_string::i_string::Nil)
+ if(s == "") "0" else s
+ }
+}
+
+/** Object providing methods to create a division of input terms, with some optimizations.
+ */
+object APAInputDivision {
+
+ /** Returns a simplified division of input terms. */
+ def apply(a: APAInputTerm, b: APAInputTerm): APAInputTerm = APAInputDivision(a::Nil, b::Nil).simplified
+
+ /** Returns a division where common occurrences between n and d have been removed (simplification). */
+ def simplifyNumDenom(n: List[APAInputTerm], d:List[APAInputTerm]):APAInputTerm = {
+ @tailrec def aux(n: List[APAInputTerm], d: List[APAInputTerm], collectedN: ListBuffer[APAInputTerm], collectedD: ListBuffer[APAInputTerm]): APAInputTerm = {
+ (n, d) match {
+ case (Nil, Nil) =>
+ (collectedN.toList, collectedD.toList) match {
+ case (Nil, Nil) => APAInputCombination(1, Nil)
+ case (n::Nil, Nil) => n
+ case (n, Nil) => APAInputMultiplication(n)
+ case (n, d) => APAInputDivision(n, d)
+ }
+ case (n1::np, d1::dp) =>
+ val i = d.indexOf(n1)
+ if (i > -1) {
+ aux(np, d.take(i) ++ d.drop(i+1), collectedN, collectedD)
+ } else {
+ val j = n.indexOf(d1)
+ if( j > -1) {
+ aux(n.take(j) ++ n.drop(j+1), dp, collectedN, collectedD)
+ } else {
+ aux(np, dp, collectedN += n1, collectedD += d1)
+ }
+ }
+ case (n, Nil) =>
+ aux(Nil, Nil, collectedN ++= n, collectedD)
+ case (Nil, d) =>
+ aux(Nil, Nil, collectedN, collectedD ++= d)
+ }
+ }
+ aux(n, d, ListBuffer(), ListBuffer())
+ }
+}
+
+/** Class representing a integer division between input terms.
+ * It should be guaranteed that the denominator divides the numerator.
+ */
+case class APAInputDivision(numerator: List[APAInputTerm], denominator : List[APAInputTerm]) extends APAInputTerm {
+ setSign(SignAbstraction.divSign(SignAbstraction.multSign(numerator), SignAbstraction.multSign(denominator)))
+
+ /** Returns a clone of this expression without the sign abstraction. */
+ def normalClone():this.type = APAInputDivision(numerator, denominator).asInstanceOf[this.type]
+
+ /** Returns a simplified version of this division. */
+ def simplified:APAInputTerm = {
+ if(isZero) return APAInputCombination(0)
+ val result = ((APAInputMultiplication(numerator).simplified, APAInputMultiplication(denominator).simplified) match {
+ case (n, APAInputCombination(1, Nil)) => n
+ case (n, d) if n == d => APAInputCombination(1, Nil)
+ case (nm@APAInputMultiplication(n), dm@APAInputMultiplication(d)) => APAInputDivision.simplifyNumDenom(n, d)
+ case (nm, dm@APAInputMultiplication(d)) => APAInputDivision.simplifyNumDenom(nm::Nil, d)
+ case (nm@APAInputMultiplication(n), dm) => APAInputDivision.simplifyNumDenom(n, dm::Nil)
+ case (nc@APAInputCombination(c, Nil), dc@APAInputCombination(i, Nil)) => APAInputCombination(c/i)
+ case (nc@APAInputCombination(c, l), dc@APAInputCombination(i, Nil)) if nc.safelyDivisibleBy(i) => nc/i
+ case (nc@APAInputCombination(c, l), dc@APAInputCombination(i, Nil)) => APAInputDivision(nc::Nil, dc::Nil)
+ case (n, d) => APAInputDivision(n::Nil, d::Nil)
+ })
+ result.propagateSign(this)
+ }
+
+ /** Returns the division where the variable y has been replaced by the input term t. */
+ def replace(y: InputVar, t: APAInputTerm):APAInputTerm = {
+ APAInputDivision(numerator map (_.replace(y, t)), denominator map (_.replace(y, t))).simplified.propagateSign(this)
+ }
+
+ /** Returns the list of input variables that this division contains. */
+ def input_variables: List[InputVar] = {
+ ((numerator flatMap (_.input_variables)) ++ (denominator flatMap (_.input_variables))).distinct
+ }
+}
+
+/** Object providing a method to create multiplications of input terms.
+ */
+object APAInputMultiplication {
+ def apply(a: APAInputTerm*):APAInputTerm = APAInputMultiplication(a.toList).simplified
+}
+
+/** Class representing a multiplication between input terms. */
+case class APAInputMultiplication(operands: List[APAInputTerm]) extends APAInputTerm {
+ //assert(operands.length > 1) // Else it does not make sense, it should have been simplified.
+ setSign(SignAbstraction.multSign(operands))
+
+ /** Returns a clone of this multiplication without the sign abstraction. */
+ def normalClone():this.type = APAInputMultiplication(operands).asInstanceOf[this.type]
+
+ /** Returns a simplified equal version of this multiplication. */
+ def simplified:APAInputTerm = {
+ if(isZero) return APAInputCombination(0)
+ val result = operands flatMap (_.simplified match {
+ case APAInputMultiplication(l) => l map (_.assumeNotzerobility(this))
+ case APAInputCombination(1, Nil) => Nil
+ case t => List(t.assumeNotzerobility(this))
+ }) match {
+ case Nil => APAInputCombination(1, Nil)
+ case a::Nil => a
+ case l =>
+ APAInputTerm.partitionInteger(l) match {
+ case (Nil, l) =>
+ APAInputMultiplication(l)
+ case (integers, not_input_combinations) =>
+ ((integers reduceLeft (_ * _)), not_input_combinations) match {
+ case (0, _) => APAInputCombination(0)
+ case (a, Nil) => APAInputCombination(a)
+ case (a, (t:APAInputCombination)::q) => APAInputMultiplication((t*a)::q)
+ case (a, _) => val s = APAInputCombination(a)::not_input_combinations
+ APAInputMultiplication(s)
+ }
+ }
+ }
+ result.propagateSign(this)
+ }
+
+ /** Returns the same multiplication where the non-zerobility of the applied sign abstraction. */
+ /** is propagated to all sub-children. */
+ override def propagateSign(s: SignAbstraction):this.type = { //Intercepts the sign propagation
+ if(s.isNotZero) {
+ val new_operands = operands map (_.assumeNotzerobility(s))
+ APAInputMultiplication(new_operands).propagateSign_internal(s).asInstanceOf[this.type]
+ } else {
+ APAInputMultiplication(operands).propagateSign_internal(s).asInstanceOf[this.type]
+ }
+ }
+
+ /** Returns an expression where all occurences of the variable y have been replaced by t. */
+ def replace(y: InputVar, t: APAInputTerm):APAInputTerm = {
+ APAInputMultiplication(operands map (_.replace(y, t))).propagateSign(this).simplified
+ }
+
+ /** Returns the list of input variables contained in this multiplication. */
+ def input_variables: List[InputVar] = {
+ (operands flatMap (_.input_variables)).distinct
+ }
+}
+
+/** Object providing a method to create additions of input terms and others.
+ */
+object APAInputAddition {
+
+ /** Returns an addition of the given input terms. */
+ def apply(a: APAInputTerm*):APAInputTerm = APAInputAddition(a.toList).simplified
+
+ /** Separate the input terms in l between input combinations and general input terms. */
+ /** Used to group input combinations together */
+ def partitionInputCombination(l: List[APAInputTerm]): (List[APAInputCombination], List[APAInputTerm]) = l match {
+ case Nil => (Nil, Nil)
+ case ((t@APAInputCombination(_, _))::q) =>
+ val (a, b) = partitionInputCombination(q)
+ (t::a, b)
+ case (p::q) =>
+ val (a, b) = partitionInputCombination(q)
+ (a, p::b)
+ }
+}
+
+/** Class representing an addition of given input terms.
+ * Additions differs from linear combinations, because they can store general additions
+ * like a*b+c+b^2+1
+ */
+case class APAInputAddition(l: List[APAInputTerm]) extends APAInputTerm {
+ setSign(SignAbstraction.addSign(l))
+
+ /** Returns a clone of this addition without the top-level abstraction (strange ?). */
+ def normalClone():this.type = APAInputAddition(l).asInstanceOf[this.type]
+
+ /** Returns a simplified version of this addition. */
+ def simplified:APAInputTerm = {
+ if(isZero) return APAInputCombination(0)
+ val result = l flatMap (_.simplified match {
+ case APAInputAddition(l) => l
+ case APAInputCombination(0, Nil) => Nil
+ case t => List(t)
+ }) match {
+ case Nil => APAInputCombination(0, Nil)
+ case a::Nil => a
+ case l =>
+ APAInputAddition.partitionInputCombination(l) match {
+ case (Nil, l) =>
+ APAInputAddition(l)
+ case (input_combinations, not_input_combinations) =>
+ ((input_combinations reduceLeft (_ + _)), not_input_combinations) match {
+ case (a, Nil) => a
+ case (a, _) => val s = a::not_input_combinations
+ APAInputAddition(s)
+ }
+
+ }
+ }
+ result.propagateSign(this)
+ }
+
+ /** Returns an expression where all occurences of the variable y have been replaced by t. */
+ def replace(y: InputVar, t: APAInputTerm):APAInputTerm = {
+ APAInputAddition(l map (_.replace(y, t))).propagateSign(this).simplified
+ }
+
+ /** Returns the list of input variables contained in this addition. */
+ def input_variables: List[InputVar] = {
+ (l flatMap (_.input_variables)).distinct
+ }
+}
+
+/** Class representing an absolute value of an input term.
+ */
+case class APAInputAbs(arg: APAInputTerm) extends APAInputTerm {
+ setSign(SignAbstraction.absSign(arg))
+
+ /** Returns a clone of this absolute value without the abstraction. */
+ def normalClone():this.type = APAInputAbs(arg).asInstanceOf[this.type]
+
+ /** Returns a simplified version of this absolute value. */
+ def simplified:APAInputTerm = {
+ if(isZero) return APAInputCombination(0)
+ val result = arg.simplified match {
+ case t if t.isPositiveZero => t
+ case APAInputCombination(i, Nil) => APAInputCombination(Math.abs(i), Nil)
+ case t =>
+ APAInputAbs(t)
+ }
+ result.propagateSign(this)
+ }
+
+ /** Returns an expression where all occurences of the variable y have been replaced by t. */
+ def replace(y: InputVar, t: APAInputTerm):APAInputTerm = {
+ val result = APAInputAbs(arg.replace(y, t)).simplified
+ result.propagateSign(this)
+ }
+
+ /** Returns the list of input variables contained in this absolute value. */
+ def input_variables: List[InputVar] = {
+ arg.input_variables
+ }
+}
+
+/** Class representing the gcd of a list of input terms.
+ * The list of input terms should be guaranteed not to be all zero at the same time.
+ */
+case class APAInputGCD(l: List[APAInputTerm]) extends APAInputTerm {
+ setSign(1)
+
+ /** Returns a clone of this gcd without the abstraction. */
+ def normalClone():this.type = APAInputGCD(l).asInstanceOf[this.type]
+
+ /** Returns a simplified version of this gcd. */
+ def simplified:APAInputTerm = {
+ if(isZero) return APAInputCombination(0)
+ val (integers, non_integers) = APAInputTerm.partitionInteger(l map (_.simplified))
+ val result = (Common.gcdlistComplete(integers), non_integers) match {
+ case (Some(1), _) => APAInputCombination(1, Nil)
+ case (None, k::Nil) => APAInputAbs(k).simplified
+ case (None, Nil) =>
+ throw new Error("GCD is not defined on an empty set")
+ case (None, l) => APAInputGCD(l)
+ case (Some(n), Nil) => APAInputAbs(APAInputCombination(n, Nil)).simplified
+ case (Some(n), l) => APAInputGCD(APAInputCombination(n, Nil)::l)
+ }
+ result.propagateSign(this)
+ }
+
+ /** Returns an expression where all occurences of the variable y have been replaced by t. */
+ def replace(y: InputVar, t: APAInputTerm):APAInputTerm = {
+ APAInputGCD(l map (_.replace(y, t))).simplified.propagateSign(this)
+ }
+
+ /** Returns the list of input variables contained in this gcd. */
+ def input_variables: List[InputVar] = {
+ (l flatMap (_.input_variables)).distinct
+ }
+}
+
+/** Class representing the lcm of a list of input terms.
+ */
+case class APAInputLCM(l: List[APAInputTerm]) extends APAInputTerm {
+ setSign(1)
+
+ /** Returns a clone of this lcm without the abstraction. */
+ def normalClone():this.type = APAInputLCM(l).asInstanceOf[this.type]
+
+ /** Returns a simplified version of this lcm. */
+ def simplified:APAInputTerm = {
+ if(isZero) return APAInputCombination(0)
+ val (integers, non_integers) = APAInputTerm.partitionInteger(l map (_.simplified))
+ val result = (Common.lcmlist(integers), non_integers) match {
+ case (1, Nil) => APAInputCombination(1)
+ case (1, k::Nil) => APAInputAbs(k).simplified
+ case (1, k1::k2::l) if k1 == k2 => APAInputLCM(k2::l).simplified
+ case (1, l) => APAInputLCM(l)
+ case (n, Nil) => APAInputAbs(APAInputCombination(n, Nil)).simplified
+ case (n, l) => APAInputLCM(APAInputCombination(n, Nil)::l)
+ }
+ result.propagateSign(this)
+ }
+
+ /** Returns an expression where all occurences of the variable y have been replaced by t. */
+ def replace(y: InputVar, t: APAInputTerm):APAInputTerm = {
+ APAInputLCM(l map (_.replace(y, t))).simplified.propagateSign(this)
+ }
+
+ /** Returns the list of input variables contained in this lcm. */
+ def input_variables: List[InputVar] = {
+ (l flatMap (_.input_variables)).distinct
+ }
+}
+/*
+case class APAInputMod(operand: APAInputTerm, divisor: APAInputTerm) extends APAInputTerm {
+ setSign(true, true, false) // >= 0
+ if(divisor.can_be_zero) throw new Exception("Error : "+divisor+" can be zero in expression "+this)
+
+ def normalClone():this.type = APAInputMod(operand, divisor).asInstanceOf[this.type]
+ def simplified:APAInputTerm = {
+ if(isZero) return APAInputCombination(0)
+ val result = (operand.simplified, divisor.simplified) match {
+ case (APAInputCombination(0, Nil), _) => APAInputCombination(0, Nil)
+ case (_, APAInputCombination(1, Nil)) => APAInputCombination(0, Nil)
+ case (APAInputCombination(i, Nil), APAInputCombination(j, Nil)) if j != 0 => APAInputCombination(Common.smod(i, j), Nil)
+ case (o, d) => APAInputMod(o, d)
+ }
+ result.propagateSign(this)
+ }
+ def replace(y: InputVar, t: APAInputTerm):APAInputTerm = {
+ APAInputMod(operand.replace(y, t), divisor.replace(y, t)).simplified.propagateSign(this)
+ }
+ def input_variables: List[InputVar] = {
+ (operand.input_variables ++ divisor.input_variables).distinct
+ }
+}*/
diff --git a/src/main/scala/leon/synthesis/comfusy/APAProgram.scala b/src/main/scala/leon/synthesis/comfusy/APAProgram.scala
new file mode 100644
index 000000000..05118df55
--- /dev/null
+++ b/src/main/scala/leon/synthesis/comfusy/APAProgram.scala
@@ -0,0 +1,650 @@
+package leon.synthesis.comfusy
+
+
+/** Object providing methods to deal with generated programs.
+ *
+ * @author Mikaël Mayer
+ */
+object APAAbstractProgram {
+
+ /** Converts a map of (input variable, integer) to its corresponding input assignment list. */
+ def toAPAInputAssignment(imap : Map[InputVar, Int]):List[(InputVar, APAInputCombination)] =
+ imap.toList map {case (v, i) => (v, APAInputCombination(i, Nil))}
+
+ /** Converts a map of (output variable, integer) to its corresponding output assignment list. */
+ def toAPAOutputAssignment(imap : Map[OutputVar, Int]):List[(OutputVar, APACombination)] =
+ imap.toList map {case (v, i) => (v, APACombination(APAInputCombination(i, Nil), Nil))}
+
+ /** Returns the combination of the two sentences, adding a "\n" if needed */
+ def combineSentences(s1: String, s2: String):String = (if(s1.endsWith("\n") || s1 == "") s1 else s1 + "\n") + s2
+
+ /** Returns a list of useful consistent input and output assignments,
+ * given existing assignments and needs.
+ *
+ * @param input_assignments The input assignments of the program.
+ * @param output_assignment The output assignments of the program.
+ * @param assignments_to_propagate_input A list of simple input assignments which can be propagated at anytime.
+ * @param assignments_to_propagate_output A list of simple output assignments which can be propagated at anytime.
+ * @param interesting_input_variables A list of input variables that are needed for further computation.
+ * @param interesting_output_variables A list of output variables that are needed for the final program
+ */
+ def propagation_delete_temp(
+ input_assignments : List[InputAssignment],
+ output_assignments : List[(OutputVar, APATerm)],
+ assignments_to_propagate_input : List[(InputVar, APAInputCombination)],
+ assignments_to_propagate_output : List[(OutputVar, APACombination)],
+ interesting_input_variables : List[InputVar], //Variables appearing in a split for example.
+ interesting_output_variables : List[OutputVar])
+ : (List[InputAssignment], List[(OutputVar, APATerm)]) =
+ recursive_propagation_delete_temp(input_assignments,
+ output_assignments,
+ assignments_to_propagate_input,
+ assignments_to_propagate_output,
+ interesting_input_variables,
+ interesting_output_variables, Nil, Nil)
+ /** Tail-recursive version of propagation_delete_temp
+ * Returns a list of useful consistent input and output assignments,
+ * given existing assignments and needs.
+ *
+ * @param input_assignments The input assignments of the program.
+ * @param output_assignment The output assignments of the program.
+ * @param assignments_to_propagate_input A list of simple input assignments which can be propagated at anytime.
+ * @param assignments_to_propagate_output A list of simple output assignments which can be propagated at anytime.
+ * @param interesting_input_variables A list of input variables that are needed for further computation.
+ * @param interesting_output_variables A list of output variables that are needed for the final program
+ * @param input_assignments_new The current list of final input assignments
+ * @param input_assignments_new The current list of final output assignments
+ */
+ def recursive_propagation_delete_temp(
+ input_assignments : List[InputAssignment],
+ output_assignments : List[(OutputVar, APATerm)],
+ assignments_to_propagate_input : List[(InputVar, APAInputCombination)],
+ assignments_to_propagate_output : List[(OutputVar, APACombination)],
+ interesting_input_variables : List[InputVar],
+ interesting_output_variables : List[OutputVar],
+ input_assignments_new : List[InputAssignment],
+ output_assignments_new : List[(OutputVar, APATerm)]
+ )
+ : (List[InputAssignment], List[(OutputVar, APATerm)]) = {
+ // First: input assignments and then output assignments
+ input_assignments match {
+ case Nil =>
+ output_assignments match {
+ case Nil => (input_assignments_new.reverse, output_assignments_new.reverse)
+ case (v, pac@APACombination(_, _))::q =>
+ pac.replaceListInput(assignments_to_propagate_input).replaceList(assignments_to_propagate_output) match {
+ case t@APACombination(_, _) =>
+ if(interesting_output_variables contains v) { // yes ! let's keep this variable
+ recursive_propagation_delete_temp(Nil, q,
+ assignments_to_propagate_input,
+ assignments_to_propagate_output ++ ((v,t)::Nil),
+ interesting_input_variables,
+ interesting_output_variables,
+ input_assignments_new,
+ (v, t)::output_assignments_new)
+ } else { // Not interesting to keep this variable. Just replace its content.
+ recursive_propagation_delete_temp(Nil, q,
+ assignments_to_propagate_input,
+ assignments_to_propagate_output ++ ((v,t)::Nil),
+ interesting_input_variables,
+ interesting_output_variables,
+ input_assignments_new, output_assignments_new)
+ }
+ // The term is not affine anymore, so we keep it without replacing.
+ case t => recursive_propagation_delete_temp(Nil, q,
+ assignments_to_propagate_input,
+ assignments_to_propagate_output,
+ interesting_input_variables,
+ interesting_output_variables,
+ input_assignments_new,
+ (v, t)::output_assignments_new)
+ }
+ // The term is not affine, so we keep it without replacing.
+ case (v, t)::q =>
+ recursive_propagation_delete_temp(Nil, q,
+ assignments_to_propagate_input,
+ assignments_to_propagate_output,
+ interesting_input_variables,
+ interesting_output_variables,
+ input_assignments_new,
+ (v, t)::output_assignments_new)
+ }
+ // On input_assignments : they can be useful if case disjunctions, so we always keep them.
+ // OptimizeMe : If not used in case disjunction, remove input variables.
+ case (ass@SingleInputAssignment(v, pac@APAInputCombination(_, _)))::q =>
+ val keep_assigment = interesting_input_variables contains v
+ pac.replaceList(assignments_to_propagate_input) match {
+ case t@APAInputCombination(_, _) => // We propagate everything.
+ recursive_propagation_delete_temp(q, output_assignments,
+ assignments_to_propagate_input ++ ((v,t)::Nil),
+ assignments_to_propagate_output,
+ interesting_input_variables,
+ interesting_output_variables,
+ (if(keep_assigment) (ass::input_assignments_new) else input_assignments_new),
+ output_assignments_new)
+ // Should not happen
+ case t => throw new Error("Honestly, I don't see how an input combination" + pac + "could be reduced to something else like" + t + ", but it happened.")
+ }
+ case assignment::q =>
+ assignment.replaceList(assignments_to_propagate_input) match {
+ case Nil => throw new Error("In theory it cannot come up to this point")
+ case new_assignment::Nil =>
+ recursive_propagation_delete_temp(q, output_assignments,
+ assignments_to_propagate_input,
+ assignments_to_propagate_output,
+ interesting_input_variables,
+ interesting_output_variables,
+ new_assignment::input_assignments_new,
+ output_assignments_new)
+ case l =>
+ recursive_propagation_delete_temp(l ++ q, output_assignments,
+ assignments_to_propagate_input,
+ assignments_to_propagate_output,
+ interesting_input_variables,
+ interesting_output_variables,
+ input_assignments_new,
+ output_assignments_new)
+ }
+ }
+
+ }
+}
+
+/** Abstract class representing program common properties.
+ */
+sealed abstract class APAAbstractProgram {
+
+ /** Converts this program to a string which can be visualized.*/
+ def toCommonString(indent: String): String
+
+ /** Executes the program under the provided environment.
+ * Returns a list of integer mappings. */
+ def execute(l: Map[InputVar, Int]): Map[OutputVar, Int]
+
+ /** Returns the list of input variables needed by the program. */
+ def input_variables: List[InputVar]
+}
+
+/** Abstract class representing a particular class of programs, the middle part.
+ * It helps to describe conjunction or disjunctions of programs.
+ * One difference with a APAProgram
is that it does not store the needed output variables,
+ * and thus needs an enclosing APAProgram
to work.
+ */
+abstract class APASplit extends APAAbstractProgram
+
+/** Program equivalent to assert(false)
+ * A program containing an APAFalseSplit
has a false precondition. */
+case class APAFalseSplit() extends APASplit {
+ def toCommonString(indent: String) = ""
+ def execute(l: Map[InputVar, Int]) = Map[OutputVar, Int]()
+ def input_variables = Nil
+}
+
+/** Program equivalent to NOOP. */
+case class APAEmptySplit() extends APASplit {
+ def toCommonString(indent: String) = ""
+ def execute(l: Map[InputVar, Int]) = Map[OutputVar, Int]()
+ def input_variables = Nil
+}
+
+/** Object providing several methods used by the class APACaseSplit
*/
+object APACaseSplit {
+
+ /** Returns a string containing the variable definition.
+ * For Scala, it corresponds to "val (x, y, z) = " */
+ def variable_define(indent: String, tuple_variables: String):String = {
+ APASynthesis.rendering_mode match {
+ case RenderingPython =>
+ indent // All variables will be described inside the program.
+ case RenderingScala =>
+ indent + "val "+ tuple_variables +" = "
+ }
+ }
+
+ /** Returns an optimized version of a given case split. */
+ def optimized(programs: List[(APACondition, APAProgram)]): APACaseSplit = {
+ val new_programs = programs filterNot {
+ case (cond, prog) =>
+ cond.global_condition == APAFalse() || (cond.pf match {
+ case t:APAForCondition => t.global_condition == APAFalse()
+ case _ => false
+ })
+ }
+ val reduced_programs = new_programs find {
+ case (cond, prog) => cond.isTrivial()
+ } match {
+ case Some(a) => a::Nil // Returns some trivial solution if it exists.
+ case _ => new_programs
+ }
+ APACaseSplit(reduced_programs)
+ }
+}
+
+/** Program used to represent a disjunction of programs under their conditions.
+ *
+ * if(condition1) program1
+ * else if(condition2) program2
+ * else if...
+ * else throw new Exception("No solution")
+ */
+case class APACaseSplit(programs: List[(APACondition, APAProgram)]) extends APASplit {
+
+ /** Returns a list containing the input variables presents in all sub-programs. */
+ def input_variables = (programs flatMap (_._2.input_variables)).distinct
+
+ /** Returns an indented string describing the program. */
+ override def toString = toCommonString(" ")
+
+ /** Returns the program equivalent to this case split. */
+ def toProgram: APAProgram = programs match {
+ case Nil => APAProgram.empty
+ case (c, p)::Nil => p
+ case (c, p)::q =>
+ APAProgram(p.input_variables, Nil, this, Nil, p.output_variables)
+ }
+
+ /** Returns a string representing this case split in the language provided in APASynthesis.rendering_mode
. */
+ def toCommonString(indent: String) = {
+ APASynthesis.rendering_mode match {
+ case RenderingScala => toScalaString(indent)
+ case RenderingPython => toPythonString(indent)
+ }
+ }
+
+ /** Returns a string representing this case split in python.
+ * APASynthesis.rendering_mode should have been set to RenderingPython. */
+ def toPythonString(indent: String) = {
+ val indent2 = indent + " "
+ (programs match {
+ case Nil => ""
+ case ((cond, pap)::Nil) =>
+ pap.innerCommonContent(indent)
+ case ((cond, pap)::_::q) =>
+ val error_result = pap.errorResultCommon(RenderingPython)
+ val prefix = APACaseSplit.variable_define(indent, pap.resultCommonContent(""))
+ prefix +
+ ((programs map {
+ case (cond, pap) =>
+ val prog_if = "if "+(cond.toCommonString)+":"
+ val prog_ifthen = pap.innerCommonContent(indent2)
+ //val prog_ifthenresult = pap.resultCommonContent(indent2)
+ val prog_ifend = indent
+ (prog_if::prog_ifthen::prog_ifend::Nil).reduceLeft(APAAbstractProgram.combineSentences(_, _))
+ })++(("se:\n"+indent2+error_result)::Nil)).reduceLeft( _ + "el" + _)
+ })
+ }
+
+ /** Returns a string representing this case split in Scala.
+ * APASynthesis.rendering_mode should have been set to RenderingScala. */
+ def toScalaString(indent: String) = {
+ val indent2 = indent + " "
+ (programs match {
+ case Nil => ""
+ case ((cond, pap)::Nil) =>
+ pap.innerCommonContent(indent)
+ case ((cond, pap)::_::q) =>
+ val error_result = pap.errorResultCommon(RenderingScala)
+ val prefix = APACaseSplit.variable_define(indent, pap.resultCommonContent(""))
+ prefix +
+ ((programs map {
+ case (cond, pap) =>
+ val prog_if = "if("+(cond.conditionToScalaString)+") {"
+ val prog_ifthen = pap.innerCommonContent(indent2)
+ val prog_ifthenresult = pap.resultCommonContent(indent2)
+ val prog_ifend = indent + "}"
+ (prog_if::prog_ifthen::prog_ifthenresult::prog_ifend::Nil).reduceLeft(APAAbstractProgram.combineSentences(_, _))
+ })++(("{ "+error_result+" }")::Nil)).reduceLeft( _ + " else " + _)
+ })
+ }
+
+ /** Executes this case split under the provided environment. */
+ def execute(l: Map[InputVar, Int]): Map[OutputVar, Int] = {
+ programs foreach {
+ case (cond, prog) =>
+ if(cond.execute(l)) return prog.execute(l)
+ }
+ Map[OutputVar, Int]()
+ }
+}
+
+/** Program used to represent a pair (condition, program) which should execute the program whose condition is true for
+ * a particular value of the input variables.
+ *
+ * for((v1, ..., vL) in [lower_range, upper_range]^L) {
+ * if(condition) {
+ * program
+ * exitloop
+ * }
+ * }
+ * @param vl The list of input variables which are bound by the for-loop (bound input variabless).
+ * @param lower_range The lower range for each of the variables in vl.
+ * @param upper_range The upper range for each of the variables in vl.
+ * @param condition The condition to test.
+ * @param program The program to execute if the condition is ok.
+ */
+case class APAForSplit(vl: List[InputVar], lower_range: APAInputTerm, upper_range: APAInputTerm, condition: APACondition, program: APAProgram) extends APASplit {
+
+ /** Converts this program to an indented string. */
+ override def toString = toCommonString(" ")
+
+ /** Returns a list of the input variables contained in the program, without the bound variables in the for loop. */
+ def input_variables = (program.input_variables) filterNot (vl.contains)
+
+ /** Returns an string containing the program, depending on the rendering mode APASynthesis.rendering_mode
. */
+ def toCommonString(indent: String) = {
+ APASynthesis.rendering_mode match {
+ case RenderingScala => toScalaString(indent)
+ case RenderingPython => toPythonString(indent)
+ }
+ }
+
+ /** Converts the bound variables to a tuple string. */
+ def varsToString(vl : List[InputVar]) : String = vl match {
+ case Nil => ""
+ case (v::Nil) => v.name
+ case (v::q) => "("+v.name+","+varsToString(q)+")"
+ }
+
+ /** Returns a string containing the program in python.
+ * APASynthesis.rendering_mode
should be set to RenderingPython. */
+ def toPythonString(indent: String) = {
+ val condition_variable = "_condition_"
+
+ val indent2 = indent + " "
+ val basic_range = "xrange("+lower_range+", 1 + "+upper_range+")"
+ val vs = vl match {
+ case v::Nil => "("+varsToString(vl)+",)"
+ case _ => varsToString(vl)
+ }
+ val list_ranges = "("+vs+" "+ (vl map { case i => "for "+i.name+" in "+basic_range}).reduceLeft(_ + " " + _) + ")"
+ val exists_construct = "lambda a, "+vs+": a or ("+ condition.toCommonString+" and "+vs+")"
+ val finding_term = "reduce("+exists_construct+", "+list_ranges+", False)"
+ val line_condition = indent + condition_variable+" = "+finding_term
+ val line_if = indent + "if "+condition_variable+":"
+ val line_assign = indent + " " + vs + " = "+condition_variable
+ val prog_string = program.innerCommonContent(indent2)
+ val line_else = indent + "else:"
+ val line_else_prog = indent2 + program.errorResultCommon(RenderingPython)
+ (line_condition::line_if::line_assign::prog_string::line_else::line_else_prog::Nil).reduceLeft(APAAbstractProgram.combineSentences(_, _))
+ }
+
+ /** Returns a string containing the program in Scala.
+ * APASynthesis.rendering_mode
should be set to RenderingScala. */
+ def toScalaString(indent: String) = {
+ val indent2 = indent + " "
+ val basic_range = "(("+lower_range+") to ("+upper_range+"))"
+ var ranges = basic_range
+ vl.tail foreach {k =>
+ ranges = ranges + " flatMap { t => "+basic_range+" map { i => (i, t) } } "
+ }
+ val expanded_vars = varsToString(vl)
+ val find_string = indent + "val " + program.resultCommonContent("") + " = " + ranges + " find { case "+expanded_vars+" => "
+ val cond_string = indent2 + condition.toCommonString
+ val match_string = indent+"} match {"
+ val case_string = indent2+"case Some("+expanded_vars+") => "
+ val prog_string = program.innerCommonContent(indent2)
+ val result_string = program.resultCommonContent(indent2)
+ val error_result = program.errorResultCommon(RenderingScala)
+ val end_string = indent2+"case None => "+error_result+"\n"+indent+"}"
+ (find_string::cond_string::match_string::case_string::prog_string::result_string::end_string::Nil).reduceLeft(APAAbstractProgram.combineSentences(_, _))
+ }
+
+ /** Returns the result of this program under the provided environment. */
+ def execute(l: Map[InputVar, Int]): Map[OutputVar, Int] = {
+ val lr:Int = lower_range.replaceList(APAAbstractProgram.toAPAInputAssignment(l)) match {
+ case APAInputCombination(k, Nil) => k
+ case t => throw new Exception("Was not able to reduce term "+t+" to integer under the mapping "+l)
+ }
+ val ur:Int = upper_range.replaceList(APAAbstractProgram.toAPAInputAssignment(l)) match {
+ case APAInputCombination(k, Nil) => k
+ case t => throw new Exception("Was not able to reduce term "+t+" to integer under the mapping "+l)
+ }
+ val basic_range = (lr to ur)
+ def possible_assignments(vl: List[InputVar], i: Int, current_assignment: List[(InputVar, Int)]) : Stream[List[(InputVar, Int)]] = vl match {
+ case Nil => Stream(current_assignment)
+ case (v::q) if i > ur => Stream()
+ case (v::q) => possible_assignments(q, lr, (v, i)::current_assignment) append possible_assignments(vl, i+1, current_assignment)
+ }
+ val assignments = possible_assignments(vl, lr, Nil)
+
+ assignments find { assignments =>
+ condition.execute(l ++ assignments)
+ } match {
+ case Some(assignments) =>
+ program.execute(l ++ assignments)
+ case None => //throw new Error("Could not find a satisfying "+vl+" in ["+lower_range+","+upper_range+"] for "+condition.toScalaString)
+ program.execute(l ++ (vl map { case v => (v -> 0)}))
+ }
+ }
+}
+
+/** Object providing methods to deal with program optimizations. */
+object APAProgram {
+
+ /** The empty program. */
+ def empty:APAProgram = APAProgram(Nil, Nil, APAEmptySplit(), Nil, Nil)
+
+ /** Returns an optimized version of the program described by these arguments.
+ *
+ * @param input_variables The input variables this program needs to run.
+ * @param input_assignment The input assignments defining new input variables this program needs to run.
+ * @param case_splits An eventual split among conditions.
+ * @param output_assignment The output assignements defining the output variables this program needs to compute.
+ * @param output_variables The output variables this program needs to compute.
+ */
+ def optimized(input_variables: List[InputVar],
+ input_assignment: List[InputAssignment],
+ case_splits: APASplit,
+ output_assignment: List[(OutputVar, APATerm)],
+ output_variables: List[OutputVar]):APAProgram = {
+ val final_output_variables = output_variables
+ val interesting_input_variables = (case_splits.input_variables ++ (output_assignment map (_._2) flatMap (_.input_variables))).distinct
+ // Let's propagate assignments that are temporary
+ val (reduced_input_assignments, reduced_output_assignments) = APAAbstractProgram.propagation_delete_temp(input_assignment, output_assignment, Nil, Nil, interesting_input_variables, output_variables)
+ APAProgram(input_variables, reduced_input_assignments, case_splits, reduced_output_assignments, output_variables)
+ }
+
+ /** Merges several conditions/programs together, which the help of case splits if needed.
+ *
+ * @param input_variables The general list of input variables all these programs need.
+ * @param output_variables The general list of output variables all these programs need.
+ * @param programs_conditions A list of pairs (conditions, programs) which needs to be merged.
+ */
+ def merge_disjunction(input_variables : List[InputVar],
+ output_variables: List[OutputVar],
+ programs_conditions: List[(APACondition, APAProgram)]): (APACondition, APAProgram) = {
+ //Adds the global precondition the disjunction fo the case split conditions.
+ val programs_conditions_filtered = programs_conditions filterNot (_._1.global_condition == APAFalse())
+ programs_conditions_filtered find { case (c, p) => c.isTrivial() } match {
+ case Some(complete_program) => complete_program
+ case None =>
+ programs_conditions_filtered match {
+ case Nil => (APACondition.False, APAProgram.empty)
+ case a::Nil => a
+ case _ =>
+ val splitted_conditions:List[APACondition] = programs_conditions_filtered map (_._1)
+ val splitted_formulas:List[APAFormula] = splitted_conditions map (_.global_condition)
+ val final_precondition = APACaseSplitCondition(splitted_conditions).toCondition
+ val final_program = APACaseSplit(programs_conditions_filtered).toProgram
+ (final_precondition, final_program)
+ }
+ }
+ }
+
+ /** Returns a string representing the given output assignment. */
+ def outputAssignmentToString(variable: OutputVar, value: APATerm):String = {
+ APASynthesis.rendering_mode match {
+ case RenderingScala => "val "+ variable.name + " = " + value.toCommonString
+ case RenderingPython => variable.name + " = " + value.toCommonString
+ }
+ }
+}
+
+/** Class representing the top-level program
+ * Contains the a definition of the needed input variables and required output variables.
+ *
+ * def progname(A: Int, ... input variables) {
+ * input assignments val k = ...
+ * case splits if ... else ...
+ * output assignments val x = ... a ...
+ * (output variables) (x, ...)
+ * }
+ *
+ * @param input_variables The input variables this program needs to run.
+ * @param input_assignment The input assignments defining new input variables this program needs to run.
+ * @param case_splits An eventual split among conditions.
+ * @param output_assignment The output assignements defining the output variables this program needs to compute.
+ * @param output_variables The output variables this program needs to compute.
+ */
+case class APAProgram(input_variables: List[InputVar],
+ input_assignment: List[InputAssignment],
+ case_splits: APASplit,
+ output_assignment: List[(OutputVar, APATerm)],
+ output_variables: List[OutputVar]) extends APAAbstractProgram {
+ var name="result"
+
+ /** Changes the name of this program
+ * Used when the program is rendered as a named function. */
+ def setName(new_name: String) = name = new_name
+
+ /** Returns a string representing this program. */
+ override def toString = toCommonString(" ")
+
+ /** Returns a string representing this program, depending on the rendering mode APASynthesis.rendering_mode
. */
+ def toCommonString(indent: String): String = APASynthesis.rendering_mode match {
+ case RenderingScala => toScalaString(indent)
+ case RenderingPython => toPythonString(indent)
+ }
+
+ /** Returns a string representing the content of the function described by this program.
+ * Namely, the input assignments, the case split and the output assignments. */
+ def innerCommonContent(indent: String): String = {
+ val prog_input = InputAssignment.listToCommonString(input_assignment, indent)
+ val prog_case_split = case_splits.toCommonString(indent)
+ val prog_output = output_assignment map {
+ case (i, t) => indent + APAProgram.outputAssignmentToString(i, t)
+ } match {
+ case Nil => ""
+ case l => l reduceLeft (APAAbstractProgram.combineSentences(_, _))
+ }
+ (prog_input::prog_case_split::prog_output::Nil).reduceLeft(APAAbstractProgram.combineSentences(_, _))
+ }
+
+ /** Returns a string representing the result of the function described by this program,
+ * like "(x, y, z)" where x, y, z are the output variables of this program. */
+ def resultCommonContent(indent:String):String = {
+ indent+(output_variables match {
+ case Nil => "()"
+ case (a::Nil) => a.name
+ case l => "(" + (l map (_.name) sortWith {_ < _} reduceLeft (_+", "+_)) + ")"
+ })
+ }
+
+ /** Returns a string containing a default value when there is an error,
+ * depending on the programming language used APASynthesis.rendering_mode
. */
+ def errorResultCommon(rm: RenderingMode): String = {
+ APASynthesis.rendering_mode match {
+ case RenderingPython => errorResultPython(rm)
+ case RenderingScala => errorResultScala(rm)
+ }
+ }
+
+ /** Returns a string containing a default value when there is an error, in Scala. */
+ def errorResultScala(rm: RenderingMode): String = {
+ if(APASynthesis.run_time_checks) {
+ rm.error_string
+ } else {
+ output_variables match {
+ case Nil => "()"
+ case (a::Nil) => "0"
+ case l => "("+(l map { _ => "0"} reduceLeft(_ + ", " + _))+")"
+ }
+ }
+ }
+
+ /** Returns a string containing a default value when there is an error, in Python. */
+ def errorResultPython(rm: RenderingMode): String = {
+ if(APASynthesis.run_time_checks) {
+ rm.error_string
+ } else {
+ output_variables match {
+ case Nil => "()"
+ case (a::Nil) => a.name + " = 0"
+ case l => "("+(l map (_.name ) reduceLeft (_ + ", " + _)) + ") = (" + (l map { _ => "0"} reduceLeft (_ + ", " + _)) + ")"
+ }
+ }
+ }
+
+ /** Returns a string containing the input variables in argument of the function.
+ * depending on the programming language used APASynthesis.rendering_mode
.*/
+ def inputCommonContent:String = APASynthesis.rendering_mode match {
+ case RenderingScala => inputScalaContent
+ case RenderingPython => inputPythonContent
+ }
+
+ /** Returns a string containing the input variables in argument of the function, in Scala. */
+ def inputScalaContent:String = input_variables match {
+ case Nil => ""
+ case l => (input_variables map (_.name + " : Int") reduceLeft (_+", "+_))
+ }
+
+ /** Returns a string containing the input variables in argument of the function, in Python. */
+ def inputPythonContent:String = input_variables match {
+ case Nil => ""
+ case l => (input_variables map (_.name) reduceLeft (_+", "+_))
+ }
+
+ /** Returns a string containing the whole program in Python. */
+ def toPythonString(indent: String):String = {
+ val function_definition = "def "+name+"("+inputCommonContent+"):"
+ val inner_content = innerCommonContent(indent)
+ val result = indent + "return " + resultCommonContent("")
+ (function_definition::inner_content::result::Nil).reduceLeft(APAAbstractProgram.combineSentences(_, _))
+ }
+
+ /** Returns a string containing the whole program in Scala. */
+ def toScalaString(indent: String):String = {
+ val return_type = output_variables match {
+ case Nil => "()"
+ case (a::Nil) => "Int"
+ case l => "("+(l map {_=>"Int"} reduceLeft (_ + ", " + _)) +")"
+ }
+ val function_definition = "def "+name+"("+inputCommonContent+"):" + return_type + " = {"
+ val inner_content= innerCommonContent(indent)
+ val result = resultCommonContent(indent)
+ var prog = function_definition
+ (function_definition::inner_content::result::"}"::Nil).reduceLeft(APAAbstractProgram.combineSentences(_, _))
+ }
+
+ /** Returns the values this program generates with the input l. */
+ def execute(l: Map[InputVar, Int]): Map[OutputVar, Int] = {
+ var input_value_map = l
+ input_assignment foreach {
+ case SingleInputAssignment(v, t) => val input_value_assignment = APAAbstractProgram.toAPAInputAssignment(input_value_map)
+ t.replaceList(input_value_assignment) match {
+ case APAInputCombination(i, Nil) => input_value_map += (v -> i)
+ case t =>
+ throw new Exception("Was not able to reduce term "+t+" to integer under the mapping "+input_value_map)
+ }
+ case BezoutInputAssignment(vl, tl) => val input_value_assignment = APAAbstractProgram.toAPAInputAssignment(input_value_map)
+ val bezout_coefs:List[Int] = tl map (_.replaceList(input_value_assignment)) map {
+ case APAInputCombination(i, Nil) => i
+ case t => throw new Exception("Was not able to reduce term "+t+" to integer under the mapping "+input_value_map)
+ }
+ // Double zip and add all assignments to variables
+ (vl zip Common.bezoutWithBase(1, bezout_coefs)) map { case (l1, l2) => l1 zip l2 } foreach { _ foreach { input_value_map += _ } }
+ }
+ var output_value_map = case_splits.execute(input_value_map)
+ val input_assignments_listed = APAAbstractProgram.toAPAInputAssignment(input_value_map)
+ output_assignment foreach {
+ case (v, t) =>
+ t.replaceListInput(input_assignments_listed).replaceList(APAAbstractProgram.toAPAOutputAssignment(output_value_map)) match {
+ case APACombination(APAInputCombination(i, Nil), Nil) => output_value_map += (v -> i)
+ case g =>
+ throw new Exception("Was not able to reduce term to integer : "+t+"\nunder the mappings "+input_value_map+" and "+output_value_map+"\nGot : "+g)
+ }
+ }
+ Map[OutputVar, Int]() ++ (output_variables map {case v => (v, (output_value_map(v)))})
+ }
+}
+
diff --git a/src/main/scala/leon/synthesis/comfusy/APASyntaxTree.scala b/src/main/scala/leon/synthesis/comfusy/APASyntaxTree.scala
new file mode 100644
index 000000000..ab146ebaf
--- /dev/null
+++ b/src/main/scala/leon/synthesis/comfusy/APASyntaxTree.scala
@@ -0,0 +1,678 @@
+package leon.synthesis.comfusy
+
+/*****************************
+ * Abstract syntax tree *
+ *****************************/
+
+// dummy
+object APASyntaxTree
+
+/** Describes the concept of a variable. */
+trait APAVariable
+
+/** A class which can be converted to a linear combination. */
+trait ConvertibleToCombination {
+
+ /** Returns the corresponding linear combination equivalent to this expression. */
+ implicit def toCombination():APACombination
+}
+
+/** Class representing an output variable, with some syntactic sugar. */
+case class OutputVar(name: String) extends APAVariable with ConvertibleToCombination {
+ def toCombination():APACombination = APACombination(this)
+
+ /** Syntactic sugar */
+ /** Returns the addition of this variable with a linear combination. */
+ def +(pac : APACombination) = pac+APACombination(this)
+
+ /** Returns the addition of this variable with an input variable. */
+ def +(v : InputVar) = APAInputCombination(v)+APACombination(this)
+
+ /** Returns the addition of this variable with another output variable. */
+ def +(v : OutputVar) = APACombination(v)+APACombination(this)
+
+ /** Returns the multiplication of this variable by an integer. */
+ def *(i : Int) = APACombination(i, this)
+
+ /** Returns the multiplication of this variable by an input variable. */
+ def *(i : InputVar):APACombination = APACombination(APAInputCombination(0, Nil), (APAInputCombination(0, (1, i)::Nil), this)::Nil)
+}
+
+/** Abstract class describing an expression. Almost everything is an expression, except variables.
+ * Methods to convert or extract are regrouped here.
+ */
+abstract sealed class APAExpression {
+
+ /** Returns the list of output variables this expression contains. */
+ def output_variables:List[OutputVar] = this match {
+ case APACombination(c, o) => o map (_._2)
+ case APADivides(c, pac) => pac.output_variables
+ case APAEqualZero(pac) => pac.output_variables
+ case APAGreaterZero(pac) => pac.output_variables
+ case APAGreaterEqZero(pac) => pac.output_variables
+ case APATrue() => Nil
+ case APAFalse() => Nil
+ case APADivision(pac, n) => pac.output_variables
+ case APADisjunction(l) => (l flatMap (_.output_variables)).distinct
+ case APAConjunction(l) => (l flatMap (_.output_variables)).distinct
+ case APAMinimum(l) => (l flatMap (_.output_variables)).distinct
+ case APAMaximum(l) => (l flatMap (_.output_variables)).distinct
+ case APANegation(e)=> e.output_variables
+ }
+
+ /** Returns the list of input variables this expression contains. */
+ def input_variables:List[InputVar] = this match {
+ case APACombination(c, o) => (c.input_variables ++ ((o map (_._1)) flatMap (_.input_variables))).distinct
+ case APADivides(c, pac) => (pac.input_variables ++ c.input_variables).distinct
+ case APAEqualZero(pac) => pac.input_variables
+ case APAGreaterZero(pac) => pac.input_variables
+ case APAGreaterEqZero(pac) => pac.input_variables
+ case APATrue() => Nil
+ case APAFalse() => Nil
+ case APADivision(pac, c) => (pac.input_variables ++ c.input_variables).distinct
+ case APADisjunction(l) => (l flatMap (_.input_variables)).distinct
+ case APAConjunction(l) => (l flatMap (_.input_variables)).distinct
+ case APAMinimum(l) => (l flatMap (_.input_variables)).distinct
+ case APAMaximum(l) => (l flatMap (_.input_variables)).distinct
+ case APANegation(e)=> e.input_variables
+ }
+
+ /** Returns a boolean indicating if the number of output variables is at least 1. */
+ def has_output_variables: Boolean = (output_variables != Nil) //OptimizeMe!
+
+ /** Returns a simplified version of this expression. */
+ def simplified: APAExpression
+
+ /** Returns a version of this expression where all occurences of y have been replaced by the linear combination t. */
+ def replace(y: OutputVar, t: APACombination): APAExpression
+
+ /** Returns a string representing this expression. */
+ override def toString = toCommonString
+
+ /** Returns a string representing this expression. Alias for toString. */
+ def toCommonString: String = toStringGeneral(APASynthesis.rendering_mode)
+
+ /** Returns a string representing this expression, depending on the rendering mode rm.*/
+ def toStringGeneral(rm: RenderingMode) = this match {
+ case APADivides(n, pac) =>
+ val pac_s = pac.toNiceString
+ if(APASynthesis.advanced_modulo && APASynthesis.rendering_mode != RenderingPython) {
+ val string_pac = (if(((pac_s indexOf '-') >= 0) || ((pac_s indexOf '+') >= 0))
+ "("+ pac_s + ")"
+ else
+ pac_s)
+ val advanced_divisibility = rm.mod_function(string_pac, n.toString) + " == 0"
+ advanced_divisibility
+ } else {
+ val basic_divisibility = (if(((pac_s indexOf '-') >= 0) || ((pac_s indexOf '+') >= 0))
+ ("("+ pac_s + ") % " + n + " == 0")
+ else
+ (pac_s + " % " + n + " == 0"))
+ val zero_divisibility = pac_s + " == 0"
+ n match {
+ case n if n.isZero =>
+ zero_divisibility
+ case n if n.isPositive =>
+ basic_divisibility
+ case _ =>
+ "(("+n+"==0 "+rm.and_symbol+" "+zero_divisibility+") "+rm.or_symbol+" ("+n+"!=0 "+rm.and_symbol+" "+basic_divisibility+"))"
+ }
+ }
+ case APAEqualZero(pac) => pac.toString + " == 0"
+ case APAGreaterZero(pac) => pac.toString + " > 0"
+ case APAGreaterEqZero(pac) => pac.toString + " >= 0"
+ case APADivision(numerator, denominator) =>
+ var string_numerator = numerator.toString
+ var string_denominator = denominator.toString
+ if((string_numerator indexOf '-') >=0 || (string_numerator indexOf '+') >=0)
+ string_numerator = "("+string_numerator+")"
+ if((string_denominator indexOf '-') >=0 || (string_denominator indexOf '+') >=0 || (string_denominator indexOf '*') >=0 || (string_denominator indexOf '/') >=0)
+ string_denominator = "("+string_denominator+")"
+ if(APASynthesis.rendering_mode == RenderingPython) // Python is cool : (-2)/3 = -1
+ string_numerator+"/" + string_denominator
+ else {
+ numerator match {
+ case APACombination(i, Nil) if i.isPositiveZero =>
+ string_numerator+"/" + string_denominator
+ case _ => "("+ string_numerator + " - " + rm.mod_function(string_numerator, string_denominator) +")/" + string_denominator
+ }
+ }
+ case APAMinimum(l) => rm.min_symbol + "(" + (l map (_.toString) reduceLeft(_ + ", " + _)) + ")"
+ case APAMaximum(l) => rm.max_symbol + "(" + (l map (_.toString) reduceLeft(_ + ", " + _)) + ")"
+ case pac@APACombination(_, _) => pac.toNiceString
+ case APATrue() => rm.true_symbol
+ case APAFalse() => rm.false_symbol
+ case APAConjunction(eqs) => eqs match {
+ case Nil => rm.true_symbol
+ case (a::Nil) => a.toString
+ case l => l map (_ match {
+ case t if t.isInstanceOf[APAEquation] => t.toString
+ case pac@APAConjunction(_) => pac.toString
+ case t => "("+t.toString+")"
+ }
+ ) reduceLeft (_+ " "+rm.and_symbol+" " + _)
+ }
+ case APADisjunction(eqs) => eqs match {
+ case Nil => rm.false_symbol
+ case (a::Nil) => a.toString
+ case l => l map (_ match {
+ case t if t.isInstanceOf[APAEquation] => t.toString
+ case pac@APADisjunction(_) => pac.toString
+ case t => "("+t.toString+")"
+ }
+ ) reduceLeft (_+ " "+rm.or_symbol+" " + _)
+ }
+ case APANegation(eq) => rm.not_symbol+"("+eq.toString+")"
+ }
+
+ /** Returns a string representing this expression, in Scala. */
+ def toScalaString = {
+ toStringGeneral(RenderingScala)
+ }
+
+ /** Returns a string representing this expression, in Python. */
+ def toPythonString = {
+ toStringGeneral(RenderingPython)
+ }
+
+ /** Method to assume signs of input terms.
+ *
+ * To assume all occurences of the variable b to be >= 0 in myterm, call :
+ * myterm.assumeSignInputTerm(InputVar("b").toInputTerm, ASign(1, 1, 0))
+ * To assume all occurences of the variable b to be <= 0 in myterm, call :
+ * myterm.assumeSignInputTerm(InputVar("b").toInputTerm, ASign(0, 1, 1))
+ */
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAExpression
+}
+
+/** Class representing formulas.
+ * A formula is an expression, which when evaluated, gives a boolean indicating if the formula is true or false.
+ */
+abstract sealed class APAFormula extends APAExpression {
+
+ /** Returns a version of this formula where all occurences of the input variable y have been replaced by the input term t. */
+ def replace(y: InputVar, t: APAInputTerm): APAFormula
+
+ /** Returns a version of this formula where all occurences of the output variable y have been replaced by the linear combination t. */
+ def replace(y: OutputVar, t: APACombination): APAFormula
+
+ /** Processes multiple replacements of input variables. */
+ def replaceListInput(lxt : List[(InputVar, APAInputTerm)]): APAFormula = {
+ lxt.foldLeft(this){ case (result, (x, t)) => result.replace(x, t) }
+ }
+
+ /** Processes multiple replacements of output variables. */
+ def replaceList(lxt : List[(OutputVar, APACombination)]): APAFormula = {
+ lxt.foldLeft(this){ case (result, (x, t)) => result.replace(x, t) }
+ }
+
+ /** Returns a simplified version of this formula. */
+ def simplified: APAFormula = this match {
+ case APAConjunction(eqs) => val eqs_simplified = eqs map (_.simplified)
+ eqs_simplified match {
+ case Nil => APATrue()
+ case l if l contains APAFalse() => APAFalse()
+ case l => l filterNot (_ == APATrue()) match {
+ case Nil => APATrue()
+ case (a::Nil) => a
+ case l => APAConjunction(l)
+ }
+ }
+ case APADisjunction(eqs) => eqs map (_.simplified) match {
+ case Nil => APAFalse()
+ case l if l contains APATrue() => APATrue()
+ case l => l filterNot (_ == APAFalse()) match {
+ case Nil => APAFalse()
+ case (a::Nil) => a
+ case l => APADisjunction(l)
+ }
+ }
+ case APANegation(f) => f.simplified match {
+ case APATrue() => APAFalse()
+ case APAFalse() => APATrue()
+ case l => APANegation(l)
+ }
+ // This matching IS exhaustive since this method is overriden in other child classes.
+ case t:APAFormula => throw new Error("formulas should have been simplified : "+this)
+ }
+
+ /** Returns the conjunction of this and that formulas. */
+ def &&(that : APAFormula) = APAConjunction(this::that::Nil).simplified
+
+ /** Returns the disjunction of this and that formulas. */
+ def ||(that : APAFormula) = APADisjunction(this::that::Nil).simplified
+
+ /** Get a stream of the DNF form of the formula. */
+ def getEquations = getEquations_tailrec(Nil)
+
+ /** Get a stream of the DNF form of the formula. Recursive version. */
+ def getEquations_tailrec(currentList: List[APAEquation]) : Stream[List[APAEquation]] = this match {
+ case t@APADivides(_, _) => Stream(currentList++List(t))
+ case t@APAEqualZero(_) => Stream(currentList++List(t))
+ case t@APAGreaterZero(_) => Stream(currentList++List(t))
+ case t@APAGreaterEqZero(_) => Stream(currentList++List(t))
+ case APAConjunction(Nil) => Stream(currentList)
+ case APAConjunction(a::Nil) =>
+ a.getEquations_tailrec(currentList)
+ case APAConjunction(a::q) =>
+ val p1 = a.getEquations_tailrec(currentList)
+ val p2 = APAConjunction(q).getEquations_tailrec(Nil)
+ (p1, p2)
+ val pp = (p1 map { eqs1 => p2 map { eqs2 => eqs1 ++ eqs2} })
+ val result = pp.foldLeft(Stream.empty:Stream[List[APAEquation]])(_.append(_))
+ result
+ case APADisjunction(Nil) => Stream(List(APAFalse()))
+ case APADisjunction(a::Nil) => a.getEquations_tailrec(currentList)
+ case APADisjunction(a::q) => a.getEquations_tailrec(currentList) append APADisjunction(q).getEquations_tailrec(currentList)
+ case APATrue() => Stream(currentList)
+ case APAFalse() => Stream(List(APAFalse()))
+ case APANegation(f) => throw new Error("Negation is not supported yet")
+ }
+
+ /** Gets a stream of immediate equalities and inequalities if there are some + the rest as a stream. of possibilities. */
+ def getLazyEquations = getLazyEquations_tailrec
+
+ /** Gets a stream of immediate equalities and inequalities if there are some + the rest as a stream. of possibilities. Recursive version. */
+ def getLazyEquations_tailrec: FormulaSplit = this match {
+ case t@APADivides(_, _) => FormulaSplit(Nil, List(t), Stream.empty)
+ case t@APAEqualZero(_) => FormulaSplit(t::Nil, Nil, Stream.empty)
+ case t@APAGreaterZero(_) => FormulaSplit(Nil, List(t), Stream.empty)
+ case t@APAGreaterEqZero(_) => FormulaSplit(Nil, List(t), Stream.empty)
+ case APAConjunction(Nil) => APATrue().getLazyEquations_tailrec
+ case APAConjunction(a::Nil) => a.getLazyEquations_tailrec
+ case APAConjunction(l) => l map (_.getLazyEquations_tailrec) reduceLeft (FormulaSplit.conjunction(_,_))
+ case APADisjunction(Nil) => APAFalse().getLazyEquations_tailrec
+ case APADisjunction(a::Nil) => a.getLazyEquations_tailrec
+ case APADisjunction(l) => FormulaSplit(Nil, Nil, l.toStream map (_.getLazyEquations_tailrec))
+ case APATrue() => FormulaSplit(Nil, Nil, Stream.empty)
+ case APAFalse() => FormulaSplit(Nil, APAFalse()::Nil, Stream.empty)
+ case APANegation(f) => throw new Error("Negation is not supported yet")
+ }
+
+ /** Assumes the sign of the input term t1 throughout the formula. */
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAFormula
+}
+
+/** Object providing methods for the class FormulaSplit.
+ */
+object FormulaSplit {
+
+ /** Returns the conjunction of two FormulaSplit. */
+ def conjunction(f1:FormulaSplit, f2:FormulaSplit):FormulaSplit = (f1, f2) match {
+ case (FormulaSplit(eqs1, ineqs1, rest1), FormulaSplit(eqs2, ineqs2, rest2)) =>
+ FormulaSplit(eqs1++eqs2, ineqs1++ineqs2, disjunction(rest1, rest2))
+ }
+
+ /** Returns the disjunction of two FormulaSplit. */
+ def disjunction(sf1:Stream[FormulaSplit], sf2:Stream[FormulaSplit]):Stream[FormulaSplit] = {
+ if(sf1.isEmpty) sf2
+ else if(sf2.isEmpty) sf1
+ else {
+ sf1 flatMap { f1 => sf2 map { f2 => conjunction(f1, f2)}}
+ }
+ }
+}
+
+/** A FormulaSplit is a formula representing a conjunction of known equalities and inequalities, and a disjunction of other formula splits.
+ * e.g. FormulaSplit(eqs, noneqs, remaining) === (eqs && noneqs && (remaining1 || remaining2 || ...))
+ */
+case class FormulaSplit(eqs: List[APAEqualZero], noneqs : List[APAEquation], remaining:Stream[FormulaSplit]) {
+
+ /** Replaces all output variables by corresponding value in this FormulaSplit. */
+ def replaceList(assignments: List[(OutputVar, APACombination)]): FormulaSplit = {
+ var new_equalities = eqs
+ var new_nonequalities = noneqs
+ assignments foreach {
+ case (v, pac) =>
+ val (new_eqs1, new_noneqs1) = APASynthesis.partitionPAEqualZero(new_equalities map (_.replace(v, pac)))
+ val (new_eqs2, new_noneqs2) = APASynthesis.partitionPAEqualZero(new_nonequalities map (_.replace(v, pac)))
+ new_equalities = new_eqs1 ++ new_eqs2
+ new_nonequalities = new_noneqs1 ++ new_noneqs2
+ }
+ var new_remaining_disjunctions = remaining map (_.replaceList(assignments))
+ FormulaSplit(new_equalities, new_nonequalities, new_remaining_disjunctions)
+ }
+}
+
+/** Abstract class describing atomic equations, or literals, like divide predicates, greater or equal to zero predicates, equal to zero, etc.
+ */
+abstract sealed class APAEquation extends APAFormula {
+
+ /** Returns a simplified version of this equation. */
+ override def simplified: APAEquation = this match {
+ case APADivides(_, APACombination(APAInputCombination(0, Nil), Nil)) => APATrue()
+ case APADivides(APAInputCombination(1, Nil), pac) => APATrue()
+ case APADivides(APAInputCombination(0, Nil), pac) => APAEqualZero(pac.simplified.normalizedForEquality)
+ case APADivides(APAInputCombination(n, Nil), APACombination(APAInputCombination(i, Nil), Nil)) => if((i % n) == 0) APATrue() else APAFalse()
+ case APADivides(n, pac) => APADivides(n, pac.simplified) // OptimizeMe ! divides by gcd of n and pac
+ case APAEqualZero(pac) => APAEqualZero(pac.simplified.normalizedForEquality) match {
+ case APAEqualZero(APACombination(n, Nil)) if n.isZero => APATrue()
+ case APAEqualZero(APACombination(n, Nil)) if n.isNotZero => APAFalse()
+ case t => t
+ }
+ case APAGreaterZero(pac) => APAGreaterZero(pac.simplified.normalized) match {
+ case APAGreaterZero(APACombination(n, Nil)) if n.isPositive => APATrue()
+ case APAGreaterZero(APACombination(n, Nil)) if n.isNotPositive => APAFalse()
+ case t => t
+ }
+ case APAGreaterEqZero(pac) => APAGreaterEqZero(pac.simplified.normalized) match {
+ case APAGreaterEqZero(APACombination(n, Nil)) if n.isPositiveZero =>
+ APATrue()
+ case APAGreaterEqZero(APACombination(n, Nil)) if n.isNotPositiveZero => APAFalse()
+ case APAGreaterEqZero(tn@APACombination(n, Nil)) if n.isNegativeZero => APAEqualZero(tn)
+ case t => t
+ }
+ case APATrue() => APATrue()
+ case APAFalse() => APAFalse()
+ }
+
+ /** Returns a version of this equation where all occurences of the output variable y have been replaced by the term t. */
+ def replace(y: OutputVar, t: APACombination): APAEquation
+
+ /** Returns a version of this equation where each occurence of the input term t1 is assumed to have the sign s. */
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAEquation
+}
+
+/** Class representing terms, i.e. expressions that would evaluate to integers when fully evaluated.
+ */
+sealed abstract class APATerm extends APAExpression {
+
+ /** Returns a version of this term where all occurences of the output variable y have been replaced by the term t. */
+ def replace(y: OutputVar, t: APACombination):APATerm
+
+ /** Returns a version of this term where all occurences of the input variable y have been replaced by the input term t. */
+ def replace(y: InputVar, t: APAInputTerm):APATerm
+
+ /** Returns a simplified version of this term. */
+ def simplified:APATerm
+
+ /** Processes multiple replacements of output variables. */
+ def replaceList(lxt : List[(OutputVar, APACombination)]): APATerm = {
+ lxt.foldLeft(this){ case (result, (x, t)) => result.replace(x, t) }
+ }
+
+ /** Processes multiple replacements of input variables. */
+ def replaceListInput(lxt : List[(InputVar, APAInputTerm)]): APATerm = {
+ lxt.foldLeft(this){ case (result, (x, t)) => result.replace(x, t) }
+ }
+
+ /** Returns a version of this term where each occurence of the input term t1 is assumed to have the sign s. */
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APATerm
+}
+
+/*******************
+ * General terms *
+ *******************/
+
+/** Class representing a division of a linear combination by an input term.
+ * It is not required for the denominator to divide the expression.
+ */ // TODO : comment from here.
+case class APADivision(numerator : APACombination, denominator : APAInputTerm) extends APATerm {
+
+ def replace(y: OutputVar, t: APACombination):APATerm = APADivision(numerator.replace(y, t), denominator).simplified
+
+ def replace(y: InputVar, t: APAInputTerm):APATerm = APADivision(numerator.replace(y, t), denominator.replace(y, t)).simplified
+
+ def simplified:APATerm = {
+ val result = (numerator.simplified, denominator) match {
+ case (n, APAInputCombination(1, Nil)) => n
+ case (n, d0@APAInputCombination(0, Nil)) => APADivision(n, d0)
+ case (APACombination(APAInputCombination(n, Nil), Nil), APAInputCombination(d, Nil)) =>
+ APACombination(APAInputCombination((n - (d + (n % d))%d)/d, Nil), Nil)
+ case (n, d) => APADivision(n, d)
+ }
+ result
+ }
+
+
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APATerm = {
+ val new_numerator = numerator.assumeSignInputTerm(t1, s)
+ val new_denominator = denominator.assumeSignInputTerm(t1, s)
+ APADivision(new_numerator, new_denominator).simplified
+ }
+}
+
+case class APAMinimum(expressions: List[APATerm]) extends APATerm {
+ def replace(y: OutputVar, t: APACombination):APATerm = APAMinimum(expressions map (_.replace(y, t))).simplified
+ def replace(y: InputVar, t: APAInputTerm):APATerm = APAMinimum(expressions map (_.replace(y, t))).simplified
+ def simplified:APATerm = expressions match {
+ case Nil => APACombination(0)
+ case a::Nil => a.simplified
+ case a::b::q =>
+ (a.simplified, b.simplified) match {
+ case (APACombination(APAInputCombination(i, Nil), Nil), APACombination(APAInputCombination(j, Nil), Nil)) => APAMinimum(APACombination(APAInputCombination(Math.min(i, j), Nil), Nil)::q).simplified //OptimizeMe
+ case (p@APACombination(APAInputCombination(0, Nil), Nil), APACombination(j, Nil)) if j.isPositiveZero => APAMinimum(p::q).simplified //OptimizeMe
+ case (APACombination(APAInputCombination(0, Nil), Nil), p@APACombination(j, Nil)) if j.isNegativeZero => APAMinimum(p::q).simplified //OptimizeMe
+ case (a, b) => APAMinimum(a::b::(q map (_.simplified)))
+ }
+ }
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APATerm = {
+ APAMinimum(expressions map (_.assumeSignInputTerm(t1, s))).simplified
+ }
+}
+case class APAMaximum(expressions: List[APATerm]) extends APATerm {
+ def replace(y: OutputVar, t: APACombination):APATerm = APAMaximum(expressions map (_.replace(y, t))).simplified
+ def replace(y: InputVar, t: APAInputTerm):APATerm = APAMaximum(expressions map (_.replace(y, t))).simplified
+ def simplified:APATerm = expressions match {
+ case Nil => APACombination(0)
+ case a::Nil => a.simplified
+ case a::b::q =>
+ (a.simplified, b.simplified) match {
+ case (APACombination(APAInputCombination(i, Nil), Nil), APACombination(APAInputCombination(j, Nil), Nil)) => APAMaximum(APACombination(APAInputCombination(Math.max(i, j), Nil), Nil)::q).simplified //OptimizeMe
+ case (APACombination(APAInputCombination(0, Nil), Nil), p@APACombination(j, Nil)) if j.isPositiveZero => APAMaximum(p::q).simplified //OptimizeMe
+ case (p@APACombination(APAInputCombination(0, Nil), Nil), APACombination(j, Nil)) if j.isNegativeZero => APAMaximum(p::q).simplified //OptimizeMe
+ case (a, b) => APAMaximum(a::b::(q map (_.simplified)))
+ }
+ }
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APATerm = {
+ APAMaximum(expressions map (_.assumeSignInputTerm(t1, s))).simplified
+ }
+}
+
+object APACombination {
+ def apply(v: APAVariable):APACombination = this(1, v)
+ def apply(k: Int, v: APAVariable):APACombination= v match {
+ case v@InputVar(n) => APACombination(APAInputCombination(0, (k, v)::Nil), Nil)
+ case v@OutputVar(n) => APACombination(APAInputCombination(0, Nil), (APAInputCombination(k, Nil), v)::Nil)
+ }
+ def apply(i: Int): APACombination = APACombination(APAInputCombination(i, Nil), Nil)
+ def apply(i: APAInputTerm): APACombination = APACombination(i, Nil)
+ def apply(c: Int, i: List[(Int, InputVar)], o: List[(Int, OutputVar)]): APACombination = {
+ APACombination(APAInputCombination(c, i), o map { case (i, v) => (APAInputCombination(i), v)})
+ }
+}
+
+// Const_part does not contain any output variables
+case class APACombination(const_part: APAInputTerm, output_linear: List[(APAInputTerm, OutputVar)]) extends APATerm with CoefficientAbstraction {
+ if(output_linear == Nil) {
+ setNoCoefficients()
+ } else if(output_linear exists { case (i, _) if i.isNotZero => true; case _ => false}) {
+ setNotAllCoefficientsAreZero()
+ }
+ def normalClone():this.type = APACombination(const_part, output_linear).asInstanceOf[this.type]
+ // Sorting functions
+ def by_OutputVar_name(i1:(APAInputTerm, OutputVar), i2:(APAInputTerm, OutputVar)) : Boolean = (i1, i2) match {
+ case ((_, OutputVar(name1)), (_, OutputVar(name2))) => name1 < name2
+ }
+ // Regrouping functions
+ def fold_Outputvar_name(i:(APAInputTerm, OutputVar), regrouped_vars:List[(APAInputTerm, OutputVar)]):List[(APAInputTerm, OutputVar)] = (i, regrouped_vars) match {
+ case (i, Nil) => i::Nil
+ case ((coef1, OutputVar(name1)), (coef2, OutputVar(name2))::q) if name1 == name2 => (coef1 + coef2, OutputVar(name1))::q
+ case (i, q) => i::q
+ }
+
+ /** Simplified means that the variables are alphabetically sorted, that there are no null coefficients, and the gcd of all coefficients is 1. */
+ def simplified: APACombination = {
+ val output_linear2 = (output_linear sortWith by_OutputVar_name).foldRight[List[(APAInputTerm, OutputVar)]](Nil){ case (a, b) => fold_Outputvar_name(a, b)}
+ APACombination(const_part.simplified, output_linear2 filterNot (_._1.isZero)).propagateCoefficientAbstraction(this)
+ }
+
+ // Normalized means that we are within an equality or inequality
+ // It should have been simplified before (without 0-coefs)
+ def normalized_aux(for_equality: Boolean): APACombination = {
+ const_part match {
+ case t:APAInputCombination if t.has_gcd_coefs =>
+ var coefs:List[Int] = Nil
+ ((output_linear forall {
+ case (t@APAInputCombination(i, Nil), _) =>
+ coefs = i::coefs
+ true
+ case _ => false
+ }), output_linear.isEmpty) match {
+ case (true, false) =>
+ val gcd = Common.gcd(t.gcd_coefs, Common.gcdlist(coefs))
+ (this/gcd).propagateCoefficientAbstraction(this)
+ case (true, true) =>
+ val gcd = t.gcd_coefs
+ val factor = (if(for_equality) t.first_sign_present*gcd else gcd)
+ (this/factor)
+ case (false, _) => this
+ }
+ case _=> this
+ }
+ }
+ def normalized = normalized_aux(false)
+ def normalizedForEquality = normalized_aux(true)
+
+ /** Division of this combination by an integer. Caution ! It should be divisible. */
+ def /(i : Int): APACombination = {
+ APACombination(const_part / APAInputCombination(i), output_linear map {t => (t._1 / APAInputCombination(i), t._2)})
+ }
+ /** Division of this combination by an integer. Caution ! It should be divisible. */
+ def /(i : APAInputTerm): APACombination = {
+ APACombination(const_part / i, output_linear map {t => (t._1 / i, t._2)})
+ }
+ /** Multiplication by an integer */
+ def *(i : Int): APACombination = {
+ val result = APACombination(const_part * APAInputCombination(i), output_linear map {t => (t._1 * APAInputCombination(i), t._2)})
+ result.assumeMultCoefficientAbstraction(this, i)
+ }
+ /** Multiplication by an APAInputTerm */
+ def *(i : APAInputTerm): APACombination = {
+ APACombination(const_part * i, output_linear map {t => (t._1 * i, t._2)})
+ }
+ /** Addition between two combinations */
+ def +(pac : APACombination): APACombination = pac match {
+ case APACombination(c, o) =>
+ val result = APACombination(const_part + c, output_linear ++ o).simplified
+ result.assumeSumCoefficientAbstraction(this, pac)
+ }
+ /** Substraction between two combinations */
+ def -(that : APACombination): APACombination = this + (that * (-1))
+ /** Addition with a new variable and its coefficient */
+ def +(kv : (APAInputTerm, OutputVar)): APACombination = this + APACombination(APAInputCombination(0, Nil), kv::Nil)
+ def +(k : APAInputTerm): APACombination = this + APACombination(k, Nil)
+ /** Substraction with a new variable and its coefficient */
+ def -(kv : (APAInputTerm, OutputVar)): APACombination = this - APACombination(APAInputCombination(0, Nil), kv::Nil)
+ def -(k : APAInputTerm): APACombination = this + APACombination(-k, Nil)
+ def unary_-(): APACombination = (APACombination(APAInputCombination(0, Nil), Nil) - this)
+ /** Comparison */
+ def ===(that: APACombination):APAEquation = APAEqualZero(this - that).simplified
+ def >= (that: APACombination):APAEquation = APAGreaterEqZero(this - that).simplified
+ def > (that: APACombination):APAEquation = APAGreaterZero(this - that).simplified
+ def <= (that: APACombination):APAEquation = APAGreaterEqZero((-this) + that).simplified
+ def < (that: APACombination):APAEquation = APAGreaterZero((-this) + that).simplified
+ def ===(that: APAInputTerm):APAEquation = this === APACombination(that)
+ def >= (that: APAInputTerm):APAEquation = this >= APACombination(that)
+ def > (that: APAInputTerm):APAEquation = this > APACombination(that)
+ def <= (that: APAInputTerm):APAEquation = this <= APACombination(that)
+ def < (that: APAInputTerm):APAEquation = this < APACombination(that)
+ def divisible_by(that: APAInputTerm): APAEquation = APADivides(that, this)
+
+ /** Replacement of a variable by another */
+ def replace(y: OutputVar, t: APACombination):APACombination = (y, t) match {
+ case (OutputVar(name), pac_for_y@APACombination(ci2, o2)) =>
+ val (output_linear_with_y, output_linear_without_y) = output_linear partition (_._2 == y)
+ val pac_without_y = APACombination(const_part, output_linear_without_y)
+ val total_y_coefficient = (output_linear_with_y map (_._1)).foldLeft(APAInputCombination(0, Nil):APAInputTerm)(_+_)
+ val result = pac_without_y + (pac_for_y*total_y_coefficient)
+ result.simplified
+ }
+ def replace(y: InputVar, t: APAInputTerm):APACombination = {
+ APACombination(const_part.replace(y, t), (output_linear map {case (k, v) => (k.replace(y, t), v)})).simplified
+ }
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APACombination = {
+ val new_const_part = const_part.assumeSignInputTerm(t1, s)
+ val new_output_linear = output_linear map {case (i, v) => (i.assumeSignInputTerm(t1, s).propagateSign(i), v)}
+ APACombination(new_const_part, new_output_linear).propagateCoefficientAbstraction(this)
+ }
+
+ def toNiceString:String = {
+ def outputElementToString(kv : (APAInputTerm, OutputVar)) = kv._1 match {
+ case APAInputCombination(1, Nil) => kv._2.name
+ case APAInputCombination(-1, Nil) => "-" + kv._2.name
+ case APAInputCombination(k, Nil) => k + "*" + kv._2.name
+ case k => "("+ k.toString + ")*" + kv._2.name
+ }
+ def makePlus(l: List[String]):String = l match {
+ case Nil => ""
+ case a::p => val s = makePlus(p)
+ if(s=="") a
+ else if(a=="") s
+ else if(s.charAt(0) == '-')
+ a + s
+ else
+ a + "+" + s
+ }
+ var c_string = const_part.toString
+ if(c_string == "0") c_string = ""
+ var o_string = output_linear match {
+ case Nil => ""
+ case a::l => l.foldLeft(outputElementToString(a)) { (s, t2) =>
+ val t2s = outputElementToString(t2)
+ s + (if(t2s.charAt(0) =='-') t2s else "+" + t2s)}
+ }
+ val s = makePlus(c_string::o_string::Nil)
+ if(s == "") "0" else s
+ }
+}
+
+case class APADivides (coefficient: APAInputTerm, pac: APACombination) extends APAEquation {
+ override def replace(y: OutputVar, t: APACombination): APAEquation = APADivides(coefficient, pac.replace(y, t)).simplified
+ override def replace(y: InputVar, t: APAInputTerm): APAEquation = APADivides(coefficient.replace(y, t), pac.replace(y, t)).simplified
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAEquation = APADivides(coefficient.assumeSignInputTerm(t1, s), pac.assumeSignInputTerm(t1, s)).simplified
+}
+case class APAEqualZero (pac: APACombination) extends APAEquation {
+ override def replace(y: OutputVar, t: APACombination): APAEquation = APAEqualZero(pac.replace(y, t)).simplified
+ override def replace(y: InputVar, t: APAInputTerm): APAEquation = APAEqualZero(pac.replace(y, t)).simplified
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAEquation = APAEqualZero(pac.assumeSignInputTerm(t1, s)).simplified
+}
+case class APAGreaterZero (pac: APACombination) extends APAEquation {
+ override def replace(y: OutputVar, t: APACombination): APAEquation = APAGreaterZero(pac.replace(y, t)).simplified
+ override def replace(y: InputVar, t: APAInputTerm): APAEquation = APAGreaterZero(pac.replace(y, t)).simplified
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAEquation = APAGreaterZero(pac.assumeSignInputTerm(t1, s)).simplified
+}
+case class APAGreaterEqZero(pac: APACombination) extends APAEquation {
+ override def replace(y: OutputVar, t: APACombination): APAEquation = APAGreaterEqZero(pac.replace(y, t)).simplified
+ override def replace(y: InputVar, t: APAInputTerm): APAEquation = APAGreaterEqZero(pac.replace(y, t)).simplified
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAEquation = APAGreaterEqZero(pac.assumeSignInputTerm(t1, s)).simplified
+}
+case class APATrue() extends APAEquation {
+ override def replace(y: OutputVar, t: APACombination): APAEquation = APATrue()
+ override def replace(y: InputVar, t: APAInputTerm): APAEquation = APATrue()
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAEquation = APATrue()
+}
+case class APAFalse() extends APAEquation {
+ override def replace(y: OutputVar, t: APACombination): APAEquation = APAFalse()
+ override def replace(y: InputVar, t: APAInputTerm): APAEquation = APAFalse()
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAEquation = APAFalse()
+}
+
+case class APAConjunction(eqs : List[APAFormula]) extends APAFormula {
+ override def replace(y: OutputVar, t: APACombination): APAFormula = APAConjunction(eqs map (_.replace(y, t))).simplified
+ override def replace(y: InputVar, t: APAInputTerm): APAFormula = APAConjunction(eqs map (_.replace(y, t))).simplified
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAFormula =
+ APAConjunction(eqs map (_.assumeSignInputTerm(t1, s))).simplified
+}
+case class APADisjunction(eqs : List[APAFormula]) extends APAFormula {
+ override def replace(y: OutputVar, t: APACombination): APAFormula = APADisjunction(eqs map (_.replace(y, t))).simplified
+ override def replace(y: InputVar, t: APAInputTerm): APAFormula = APADisjunction(eqs map (_.replace(y, t))).simplified
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAFormula =
+ APADisjunction(eqs map (_.assumeSignInputTerm(t1, s))).simplified
+}
+case class APANegation(eq: APAFormula) extends APAFormula {
+ override def replace(y: OutputVar, t: APACombination): APAFormula = APANegation(eq.replace(y, t)).simplified
+ override def replace(y: InputVar, t: APAInputTerm): APAFormula = APANegation(eq.replace(y, t)).simplified
+ def assumeSignInputTerm(t1: APAInputTerm, s: SignAbstraction):APAFormula =
+ APANegation(eq.assumeSignInputTerm(t1, s)).simplified
+}
diff --git a/src/main/scala/leon/synthesis/comfusy/APASynthesis.scala b/src/main/scala/leon/synthesis/comfusy/APASynthesis.scala
new file mode 100644
index 000000000..124d9807f
--- /dev/null
+++ b/src/main/scala/leon/synthesis/comfusy/APASynthesis.scala
@@ -0,0 +1,806 @@
+package leon.synthesis.comfusy
+//import scala.annotation.tailrec
+
+//*********************************** Pressburger Synthesis ************************************************//
+
+sealed abstract class RenderingMode {
+ val true_symbol:String
+ val false_symbol:String
+ val and_symbol: String
+ val or_symbol:String
+ val not_symbol:String
+ val min_symbol: String
+ val max_symbol: String
+ val error_string: String
+ val abs_symbol: String
+ val gcd_symbol: String
+ val lcm_symbol: String
+ def mod_function(operand: String, divisor: String): String
+}
+
+case object RenderingScala extends RenderingMode {
+ val (true_symbol, false_symbol, and_symbol, or_symbol, not_symbol, min_symbol, max_symbol, error_string) =
+ ("true", "false", "&&", "||", "!", "Math.min", "Math.max", "throw new Error(\"No solution exists\")")
+ val (abs_symbol, gcd_symbol, lcm_symbol) = ("Math.abs", "Common.gcdlist", "Common.lcmlist")
+ def mod_function(string_numerator: String, denominator: String): String = {
+ if(APASynthesis.advanced_modulo)
+ string_numerator+"%%"+denominator
+ else
+ "("+denominator+" + "+string_numerator+"%"+denominator+")%"+denominator
+
+ }
+}
+
+case object RenderingPython extends RenderingMode {
+ val (true_symbol, false_symbol, and_symbol, or_symbol, not_symbol, min_symbol, max_symbol, error_string) =
+ ("True", "False", "and", "or", "not", "min", "max", "raise Exception(\"No solution exists\")")
+ val (abs_symbol, gcd_symbol, lcm_symbol) = ("abs", "gcd", "lcm")
+ def mod_function(operand: String, divisor: String): String = {
+ operand+"%" + divisor
+ }
+}
+
+object APASynthesis {
+ /* ************* Synthesis options *************** */
+ /** Rendering expressions of the form a %% b where a %% 0 == a, and else (k %% b) is always between 0 and b-1 and congruent to k modulo b. */
+ var advanced_modulo = false
+
+ /** To turn off run-time checks : if true, the "throw new Error" are replaced by tuples filled with zeros. */
+ var run_time_checks = false
+
+ /** top-level rendering mode for toString */
+ var rendering_mode:RenderingMode = RenderingScala
+
+ // ************* Different ways of specifying solving conditions ***************/** */
+
+ /** Returns all output variables in the given list of equations */
+ def getOutputVariables(eqs: List[APAEquation]):List[OutputVar] = {
+ (eqs flatMap (_.output_variables)).distinct
+ }
+
+ /** Solve the equations in a lazy way */
+ def solveLazyEquations(input_variables: List[InputVar], output_variables: List[OutputVar], eqslazy: FormulaSplit):(APACondition, APAProgram) = {
+ return (new APASynthesis(eqslazy, input_variables, output_variables)).solve()
+ }
+
+ def solveLazyEquations(name: String, output_variables: List[OutputVar], eqs: APAFormula):(APACondition, APAProgram) = {
+ val input_variables = eqs.input_variables
+ var (cond, prog) = (new APASynthesis(eqs.getLazyEquations, input_variables, output_variables)).solve()
+ prog.setName(name)
+ (cond, prog)
+ }
+
+ /*def solveEquations(name: String, variables: List[OutputVar], eqs: List[APAEquation]) = {
+ var (cond, prog) = (new APASynthesis(eqs, variables)).solve()
+ prog.setName(name)
+ (cond, prog)
+ }*/
+
+ def solve(name: String, output_variables: List[OutputVar], formula_sequence: APAFormula*):(APACondition, APAProgram) = {
+ val formula = APAConjunction(formula_sequence.toList).simplified
+ //val dnf:Stream[List[APAEquation]] = formula.getEquations
+ //val output_variables = variables
+ //val input_variables = formula.input_variables
+ //val programs_conditions = (dnf map {solveEquations("", output_variables, _)}).toList
+ solveLazyEquations(name, output_variables, formula)
+ }
+ def solve(name: String, formula_sequence: APAFormula*):(APACondition, APAProgram) = {
+ solve(name, formula_sequence.toList)
+ }
+ def solve(name: String, formula_sequence: List[APAFormula]):(APACondition, APAProgram) = {
+ val formula = APAConjunction(formula_sequence.toList).simplified
+ solve(name, formula.output_variables, formula)
+ }
+ def solve(variables:List[OutputVar], formula_sequence: APAFormula*):(APACondition, APAProgram) = {
+ solve(variables, formula_sequence.toList)
+ }
+ def solve(variables:List[OutputVar], formula_sequence: List[APAFormula]):(APACondition, APAProgram) = {
+ val formula = APAConjunction(formula_sequence.toList).simplified
+ solve("result", variables, formula)
+ }
+ def solve(formula_sequence: APAFormula*):(APACondition, APAProgram) = {
+ solve(formula_sequence.toList)
+ }
+ def solve(formula_sequence: List[APAFormula]):(APACondition, APAProgram) = {
+ val formula = APAConjunction(formula_sequence).simplified
+ solve("result", formula.output_variables, formula)
+ }
+
+
+ /** ************* Function used in the algorithm *************** */
+ val alphabet = "abcdefghijklmnopqrstuvwxyz"
+ def newOutputVariable(input_existing: List[InputVar], output_existing : List[OutputVar]): OutputVar = {
+ //var typical = "xyzmnpqrstuvw"
+ var i = 0
+ val names = (input_existing map (_.name)) ++ (output_existing map (_.name))
+ (0 to 25) foreach { i =>
+ val test = "y"+alphabet.substring(i, i+1)
+ if(!(names contains test))
+ return OutputVar(test)
+ }
+ while(names contains ("y"+i)) {
+ i+=1
+ }
+ OutputVar("y"+i)
+ }
+ def newInputVariable(input_existing: List[InputVar], output_existing : List[OutputVar]): InputVar = {
+ var i = 0
+ val names = (input_existing map (_.name)) ++ (output_existing map (_.name))
+ while(names contains ("k"+i)) {
+ i+=1
+ }
+ InputVar("k"+i)
+ }
+
+ // Split the list into APAEqualZero one left and not APAEqualZero on right
+ def partitionPAEqualZero(eqs : List[APAEquation]):(List[APAEqualZero], List[APAEquation]) = eqs match {
+ case Nil => (Nil, Nil)
+ case (p@APAEqualZero(pac)::q) =>
+ val (a, b) = partitionPAEqualZero(q)
+ (APAEqualZero(pac)::a, b)
+ case (p::q) =>
+ val (a, b) = partitionPAEqualZero(q)
+ (a, p::b)
+ }
+ // Splits the list into equalities, inequalities, and a boolean indicating if the system is consistent.
+ def partitionPAGreaterEqZero(eqs : List[APAEquation]):(List[APAEqualZero], List[APAGreaterEqZero], Boolean) = eqs match {
+ case Nil => (Nil, Nil, true)
+ case (p@APAEqualZero(pac)::q) =>
+ val (a, b, c) = partitionPAGreaterEqZero(q)
+ (APAEqualZero(pac)::a, b, c)
+ case (p@APAGreaterEqZero(pac)::q) =>
+ val (a, b, c) = partitionPAGreaterEqZero(q)
+ (a, APAGreaterEqZero(pac)::b, c)
+ case (p@APAGreaterZero(APACombination(coef, o))::q) =>
+ val (a, b, c) = partitionPAGreaterEqZero(q)
+ (a, APAGreaterEqZero(APACombination(coef+APAInputCombination(-1), o))::b, c)
+ case (APATrue()::q) =>
+ partitionPAGreaterEqZero(q)
+ case (APAFalse()::q) =>
+ (Nil, Nil, false)
+ case (APADivides(n, pac)::q) =>
+ throw new Error("Divides are not supported at this point")
+ }
+
+
+ def recursive_propagation(
+ output_assignments : List[(OutputVar, APATerm)],
+ assignments_to_propagate : List[(OutputVar, APACombination)],
+ interesting_variables : List[OutputVar])
+ : List[(OutputVar, APATerm)] =
+ output_assignments match {
+ case Nil => Nil
+ case (v, pac@APACombination(_, _))::q => pac.replaceList(assignments_to_propagate) match {
+ case pac@APACombination(APAInputCombination(_, Nil), Nil) => // We propagate constants
+ if(interesting_variables contains v) {
+ (v, pac)::recursive_propagation(q, (v,pac)::assignments_to_propagate, interesting_variables)
+ } else {
+ recursive_propagation(q, (v,pac)::assignments_to_propagate, interesting_variables)
+ }
+ case t => (v, t)::recursive_propagation(q, assignments_to_propagate, interesting_variables)
+ }
+ case (v, t)::q => (v, t.replaceList(assignments_to_propagate))::recursive_propagation(q, assignments_to_propagate, interesting_variables)
+ }
+
+ // Propagate simple assignments, by removing the introduced variables if possible.
+ def propagateAssignment(y: OutputVar, t: APACombination, output_assignments : List[(OutputVar, APATerm)], output_variables_initial : List[OutputVar]): List[(OutputVar, APATerm)] = {
+ recursive_propagation(output_assignments, (y,t)::Nil, output_variables_initial)
+ }
+
+ def needsLessOperations(coef1: APAInputTerm, coef2: APAInputTerm): Boolean = (coef1, coef2) match {
+ case (APAInputCombination(k1, Nil), APAInputCombination(k2, Nil)) => Math.abs(k1) < Math.abs(k2)
+ case (APAInputCombination(k1, Nil), _) => true
+ case (_, APAInputCombination(k2, Nil)) => false
+ case (_, _) => false
+ }
+
+ def minInputTerms(coef1: APAInputTerm, coef2:APAInputTerm) = if(needsLessOperations(coef1, coef2)) coef1 else coef2
+
+ /** Sorting function (OptimizeMe) */
+ /** Priority to constant terms */
+ def by_least_outputvar_coef(eq1: APAEqualZero, eq2: APAEqualZero): Boolean = (eq1, eq2) match {
+ case (APAEqualZero(pac1@APACombination(c1, o1)), APAEqualZero(pac2@APACombination(c2, o2))) =>
+ val min_coefs_o1 = o1 map (_._1) reduceLeft (minInputTerms(_, _))
+ val min_coefs_o2 = o2 map (_._1) reduceLeft (minInputTerms(_, _))
+ needsLessOperations(min_coefs_o1, min_coefs_o2)
+ }
+}
+
+class APASynthesis(equations: FormulaSplit, input_variables_initial:List[InputVar], output_variables_initial:List[OutputVar]) {
+ import APASynthesis._
+ var output_variables:List[OutputVar] = output_variables_initial
+ var output_variables_encountered:List[OutputVar] = output_variables_initial
+
+ var input_variables:List[InputVar] = input_variables_initial
+ var input_variables_encountered:List[InputVar] = input_variables_initial
+
+ // Global_precondition is a conjunction of disjunctions of conjunctions.
+ var global_precondition: List[APAFormula] = Nil
+ // equation should not have output variables
+ def addPrecondition (e: APAFormula):Unit = {
+ val f = e.simplified
+ if(f.output_variables != Nil) // Debug sentence
+ throw new Exception("Error: there should be no output variables in this precondition :"+f)
+ f match {
+ case APATrue() =>
+ case APAFalse() => setFalsePrecondition()
+ case APAConjunction(l) => l foreach (addPrecondition(_))
+ case APAGreaterEqZero(APACombination(i, Nil)) => // We forward the constraint.
+ input_assignments = input_assignments map { case assignment =>
+ assignment.assumeSignInputTerm(i, PositiveZeroSign())
+ }
+ output_assignments = output_assignments map {
+ case (v, t) =>
+ (v, t.assumeSignInputTerm(i, PositiveZeroSign()))
+ }
+ global_precondition = f :: global_precondition
+ case f =>
+ global_precondition = f :: global_precondition
+ }
+ }
+ def setFalsePrecondition() = global_precondition = APAFalse()::Nil
+ def addOutputVar(y: OutputVar) = {
+ output_variables = (y::output_variables)
+ output_variables_encountered = (y::output_variables_encountered). distinct
+ }
+ def delOutputVar(y: OutputVar) = output_variables = output_variables.filterNot(_ == y)
+ def addInputVar (y: InputVar) = {
+ input_variables = (y::input_variables)
+ input_variables_encountered = (y::input_variables_encountered)
+ }
+ def getNewOutputVarWithoutRegistering() = APASynthesis.newOutputVariable(input_variables_encountered, output_variables_encountered)
+ def getNewOutputVar() = {
+ val y = getNewOutputVarWithoutRegistering()
+ addOutputVar(y)
+ y
+ }
+ def getNewInputVar() = {
+ val x = APASynthesis.newInputVariable(input_variables_encountered, output_variables_encountered)
+ addInputVar(x)
+ x
+ }
+ // List of reversed assignments: At the end, leftmost assignments should be done at the end.
+ var input_assignments: List[InputAssignment] = Nil
+ var output_assignments: List[(OutputVar, APATerm)] = Nil
+ def addSingleInputAssignment (x: InputVar, t: APAInputTerm) = input_assignments = input_assignments ++ (SingleInputAssignment(x, t.simplified)::Nil)
+ def addBezoutInputAssignment (xl: List[List[InputVar]], tl: List[APAInputTerm]) = input_assignments = input_assignments ++ (BezoutInputAssignment(xl, tl).simplified)
+ def addOutputAssignment(y: OutputVar, t: APATerm) = output_assignments = (y, t.simplified)::output_assignments
+ def removeInputAssignment(y: InputVar) = input_assignments = input_assignments filterNot {case SingleInputAssignment(x, _) if x == y => true; case _ => false}
+ /************* Functions used in the algorithm *************** */
+ def simplifyEquations(equations: List[APAEquation]) : List[APAEquation] = {
+ equations flatMap {
+ case e@APADivides(k, APACombination(c, Nil)) =>
+ addPrecondition(e.simplified)
+ Nil
+ case APADivides(k, APACombination(c, o)) =>
+ val y = getNewOutputVar()
+ APAEqualZero(APACombination(c, (k, y)::o)).simplified::Nil
+ case APAGreaterZero(APACombination(c, o)) => APAGreaterEqZero(APACombination(c-APAInputCombination(1), o)).simplified::Nil
+ case e => e.simplified::Nil
+ }
+ }
+
+ /** Returns the remaining non_equalities (non_equalities should not contain equalities, nor will the returned term do) */
+ def solveEqualities(data: FormulaSplit): APASplit = {
+ val FormulaSplit(equalities, non_equalities, remaining_disjunctions) = data
+
+ /** Make sure all equalities have at least one output variable, else remove them. */
+ val (interesting_equalities, precondition_equalities) = equalities partition (_.has_output_variables)
+ addPrecondition(APAConjunction(precondition_equalities))
+
+ val sorted_equalities = interesting_equalities sortWith by_least_outputvar_coef
+
+ sorted_equalities match {
+ case Nil =>
+ val newfs = FormulaSplit(Nil, non_equalities, remaining_disjunctions)
+ solveEquations(newfs)
+ case (eq1@APAEqualZero(pac@APACombination(c1, o1)))::rest_equalities =>
+ var const_part:APAInputTerm = c1
+ var o1_coefs = o1 map (_._1)
+ var o1_vars = o1 map (_._2)
+ val gcd = APAInputGCD(o1_coefs).replaceList(input_assignments flatMap (_.extract)).simplified
+
+ gcd match {
+ case APAInputCombination(1, Nil) =>
+ // Perfect !! We know that there is a solution.
+ // Continue to CONTINUE_POINT
+ case n =>
+ val coefs_are_zero = APAConjunction(o1_coefs map (APACombination(_)===APAInputCombination(0))).simplified
+ if(coefs_are_zero == APATrue() || pac.allCoefficientsAreZero) {
+ // We add the precondition const_part == 0
+ addPrecondition(APACombination(const_part)===APAInputCombination(0))
+ return solveEqualities(FormulaSplit(rest_equalities, non_equalities, remaining_disjunctions))
+ } else if(coefs_are_zero == APAFalse() || pac.notAllCoefficientsAreZero) {
+ // Regular run. We know that the GCD of the numbers is positive.
+ addPrecondition(APADivides(n, APACombination(const_part, Nil)))
+ val x = getNewInputVar()
+ val n_positive = n.assumeSign(1)
+ val gcd_expr = n_positive match {
+ case APAInputCombination(i, Nil) =>
+ n_positive
+ case APAInputCombination(0, ((k, _)::Nil)) if Math.abs(k) == 1 =>
+ n_positive
+ case _ =>
+ val gcd_var = getNewInputVar().assumePositive()
+ val result = APAInputCombination(gcd_var).replaceList(input_assignments flatMap (_.extract))
+ assert(result.isPositiveZero)
+ addSingleInputAssignment(gcd_var, n_positive)
+ result
+ }
+ addSingleInputAssignment(x, APAInputDivision(const_part, gcd_expr).simplified)
+ const_part = APAInputCombination(0, (1, x)::Nil)
+ o1_coefs = o1_coefs map (APAInputDivision(_, gcd_expr).simplified)
+ } else {
+ var (cond1, prog1) = APASynthesis.solve(output_variables, APAEqualZero(pac.assumeAllCoefficientsAreZero) :: rest_equalities ++ non_equalities) // Case where the coefficients are null.
+ cond1 = cond1.assumeBefore(coefs_are_zero)
+ var (cond2, prog2) = APASynthesis.solve(output_variables, APAEqualZero(pac.assumeNotAllCoefficientsAreZero) :: rest_equalities ++ non_equalities) //solve with the knowledge that not all the coefficients are null.
+ cond2 = cond2.assumeBefore(APANegation(coefs_are_zero))
+ return APACaseSplit.optimized((cond1, prog1)::(cond2, prog2)::Nil)
+ }
+ }
+ // CONTINUE_POINT
+ // Now the gcd of the output variables is for sure 1.
+ // We find a solution to o1_coefs.o1_vars + 1 = 0
+ // Then we know that by multiplying the first line by const_part, we obtain the general solution
+ // Express the input variables by assignments
+
+ val new_input_variables: List[List[InputVar]]= o1_vars.indices.toList map { _ => o1_vars.indices.toList map { _ => getNewInputVar()}}
+ addBezoutInputAssignment(new_input_variables, o1_coefs)
+
+ val first_line:List[APACombination] = new_input_variables.head map {
+ case iv =>
+ val p = APAInputCombination(0, (1, iv)::Nil)
+ p.replaceList(input_assignments flatMap (_.extract)) match {
+ case t@APAInputCombination(i, Nil) =>
+ removeInputAssignment(iv)
+ APACombination(const_part * t)
+ case t@APAInputCombination(0, (i, v)::Nil) if Math.abs(i) == 1 => // Simple case 2
+ removeInputAssignment(iv)
+ APACombination(const_part * t)
+ case _ => // If it's not an integer, keep the variable name.
+ APACombination(const_part * p)
+ }
+ }
+ // From this solution, we introduce |o1| - 1 new variables to solve the equality and remove the equation.
+ val new_assignments:List[APACombination] = new_input_variables.tail.foldLeft(first_line:List[APACombination]) { case (assignments, line) =>
+ val y = getNewOutputVar()
+ (assignments zip line) map {
+ case (expr:APACombination, iv) =>
+ //expr + y*iv
+ val p = APAInputCombination(0, (1, iv)::Nil)
+ p.replaceList(input_assignments flatMap (_.extract)) match {
+ case t@APAInputCombination(i, Nil) => // Simple case 2
+ removeInputAssignment(iv)
+ expr + (APACombination(y)*t)
+ case t@APAInputCombination(0, (i, v)::Nil) if Math.abs(i) == 1 => // Simple case 2
+ removeInputAssignment(iv)
+ expr + (APACombination(y)*t)
+ case _ =>
+ expr + (APACombination(y)*p)
+ }
+ }
+ }
+ // We add the variables if they are needed.
+ //if(((new_assignments flatMap (_.input_variables)).distinct intersect new_input_variables) != Nil)
+
+
+ //var new_equalities = rest_equalities
+ //var new_nonequalities = non_equalities
+ val assignments = (o1_vars zip new_assignments)
+ assignments foreach {
+ case (v, pac) => addOutputAssignment(v, pac)
+ //val (new_eqs1, new_noneqs1) = partitionPAEqualZero(new_equalities map (_.replace(v, pac)))
+ //val (new_eqs2, new_noneqs2) = partitionPAEqualZero(new_nonequalities map (_.replace(v, pac)))
+ //new_equalities = new_eqs1 ++ new_eqs2
+ //new_nonequalities = new_noneqs1 ++ new_noneqs2
+ delOutputVar(v)
+ }
+ //var new_remaining_disjunctions = remaining_disjunctions map (_.replaceList(assignments))
+ val newfs = FormulaSplit(rest_equalities, non_equalities, remaining_disjunctions).replaceList(assignments)
+ solveEqualities(newfs)
+ }
+ }
+
+ def setRemainingVariablesToZero(output_variables : List[OutputVar]):Unit = output_variables match {
+ case Nil =>
+ case y::q =>
+ output_variables_initial contains y match {
+ case true => output_assignments = propagateAssignment(y, APACombination(APAInputCombination(0, Nil), Nil), output_assignments, output_variables_initial)
+ addOutputAssignment(y, APACombination(APAInputCombination(0, Nil), Nil))
+ delOutputVar(y)
+ setRemainingVariablesToZero(q)
+ case false => output_assignments = propagateAssignment(y, APACombination(APAInputCombination(0, Nil), Nil), output_assignments, output_variables_initial)
+ delOutputVar(y)
+ setRemainingVariablesToZero(q)
+ }
+ }
+
+ // Returns (cond, l_left, l_right, l_remaining) such that:
+ // l_left contains elements (A, a) such that A <= a*v
+ // l_right contains elements (b, B) such that b*v <= B
+ // l_remaining contains elements which do not contain v
+ // l_cond is a list of formulas
+ // Properties : The length of the stream is 3^l_formulas.size of the first element.
+ def getInequalitiesForVariable(v: OutputVar, inequalities:List[APAGreaterEqZero]): Stream[(List[APAFormula], List[(APACombination, APAInputTerm)], List[(APAInputTerm, APACombination)], List[APAEquation])] = {
+ def getInequalitiesForVariable_aux(v: OutputVar,
+ inequalities:List[APAEquation],
+ result: (List[APAFormula], List[(APACombination, APAInputTerm)], List[(APAInputTerm, APACombination)], List[APAEquation])
+ ) : Stream[(List[APAFormula], List[(APACombination, APAInputTerm)], List[(APAInputTerm, APACombination)], List[APAEquation])] =
+ inequalities match {
+ case Nil =>
+ // At this split point, we can solve.
+ Stream(result)
+ case ((p@APAGreaterEqZero(pac@APACombination(c, o)))::q) =>
+ val (l_formulas, l_left, l_right, l_remaining)=result
+ o find (_._2 == v) match {
+ case None =>
+ getInequalitiesForVariable_aux(v, q, (l_formulas, l_left, l_right, p::l_remaining))
+ case Some(found_element@(k, v)) =>
+ if(k.isPositive)
+ getInequalitiesForVariable_aux(v, q, (l_formulas, (APACombination(c, o.filterNot(_ == found_element)) * (-1), k)::l_left, l_right, l_remaining))
+ else if(k.isZero) // Should not happen, but this is just to keep a coherent skeleton.
+ getInequalitiesForVariable_aux(v, q, (l_formulas, l_left, l_right, APAGreaterEqZero(APACombination(c, o.filterNot(_ == found_element)))::l_remaining))
+ else if(k.isNegative)
+ getInequalitiesForVariable_aux(v, q, (l_formulas, l_left, (-k, APACombination(c, o.filterNot(_ == found_element)))::l_right, l_remaining))
+ else {
+ def replaceLeftBound(left: List[(APACombination, APAInputTerm)], t1: APAInputTerm, s: SignAbstraction): List[(APACombination, APAInputTerm)] = {
+ left map {
+ case (pac, i) =>
+ val result = (pac.assumeSignInputTerm(t1, s), i.assumeSignInputTerm(t1, s))
+ result
+ }
+ }
+ def replaceRightBound(right: List[(APAInputTerm, APACombination)], t1: APAInputTerm, s: SignAbstraction): List[(APAInputTerm, APACombination)] = {
+ right map {
+ case (i, pac) =>
+ val result = (i.assumeSignInputTerm(t1, s), pac.assumeSignInputTerm(t1, s))
+ result
+ }
+ }
+ def replaceRemaining(remaining: List[APAEquation], t1: APAInputTerm, s: SignAbstraction): List[APAEquation] = {
+ val result = remaining map (_.assumeSignInputTerm(t1, s))
+ result
+ }
+ (if(k.can_be_positive) { // k can be positive
+ val k_positive = k.assumeSign(1)
+ assert(k_positive.isPositive)
+ val new_l_formulas = (APACombination(k) > APAInputCombination(0))::l_formulas
+ val new_l_left = (APACombination(c, o.filterNot(_ == found_element))*(-1), k_positive)::replaceLeftBound(l_left, k, k_positive)
+ val new_l_right = replaceRightBound(l_right, k, k_positive)
+ val new_l_remaining = replaceRemaining(l_remaining, k, k_positive)
+ val new_q = replaceRemaining(q, k, k_positive)
+ getInequalitiesForVariable_aux(v, new_q, (new_l_formulas, new_l_left, new_l_right, new_l_remaining))
+ } else Stream()) append
+ (if(k.can_be_zero) { // k can be zero
+ val k_nul = APAInputCombination(0)
+ assert(k_nul.isZero)
+ val new_l_formulas = (APACombination(k) === APAInputCombination(0))::l_formulas
+ val new_l_left = replaceLeftBound(l_left, k, k_nul)
+ val new_l_right = replaceRightBound(l_right, k, k_nul)
+ val new_l_remaining = APAGreaterEqZero(APACombination(c, o.filterNot(_ == found_element)))::replaceRemaining(l_remaining, k, k_nul)
+ val new_q = replaceRemaining(q, k, k_nul)
+ getInequalitiesForVariable_aux(v, new_q, (new_l_formulas, new_l_left, new_l_right, new_l_remaining))
+ } else Stream()) append
+ (if(k.can_be_negative) { // k can be negative
+ val k_negative = k.assumeSign(-1)
+ val mk_negative = -k_negative
+ assert(mk_negative.isPositive)
+ val new_l_formulas = (APACombination(k) < APAInputCombination(0))::l_formulas
+ val new_l_left = replaceLeftBound(l_left, k, k_negative)
+ val new_l_right = (mk_negative, APACombination(c, o.filterNot(_ == found_element)))::replaceRightBound(l_right, k, k_negative)
+ val new_l_remaining = replaceRemaining(l_remaining, k, k_negative)
+ val new_q = replaceRemaining(q, k, k_negative)
+ getInequalitiesForVariable_aux(v, new_q, (new_l_formulas, new_l_left, new_l_right, new_l_remaining))
+ } else Stream())
+ }
+ }
+ case APATrue()::q =>
+ val (l_formulas, l_left, l_right, l_remaining)=result
+ getInequalitiesForVariable_aux(v, q, (l_formulas, l_left, l_right, l_remaining))
+ case APAFalse()::q =>
+ val (l_formulas, l_left, l_right, l_remaining)=result
+ getInequalitiesForVariable_aux(v, q, (l_formulas, l_left, l_right, APAFalse()::l_remaining))
+ case f::q =>
+ throw new Error("Could not handle "+f)
+ }
+ getInequalitiesForVariable_aux(v, inequalities, (Nil, Nil, Nil, Nil))
+ }
+
+ /** Solve them, so now we only have non-equalities */
+ /** The simplification of inequalities can generate new equalities, so we handle them. */
+ def solveEquations(data: FormulaSplit):APASplit = {
+ val FormulaSplit(equalities, non_equalities, remaining_disjunctions) = data
+ // Let's extract the divisibility predicates and converts them to equalities.
+ val (new_equalities, new_non_equalities) = partitionPAEqualZero(simplifyEquations(non_equalities))
+ val total_equalities = equalities ++ new_equalities
+ if(total_equalities != Nil) {
+ solveEqualities(FormulaSplit(total_equalities, new_non_equalities, remaining_disjunctions))
+ } else {
+ val filtered_non_equalities = non_equalities filterNot (_==APATrue())
+ if(filtered_non_equalities contains APAFalse()) return APAFalseSplit()
+ //TODO :Here, verify that we don't have remaining_disjunctions anymore before handling the rest.
+ if(!remaining_disjunctions.isEmpty) {
+ assert(equalities == Nil)
+ // We merge the current non equalities to the subproblems.
+ val problems:Stream[FormulaSplit] = remaining_disjunctions map (FormulaSplit.conjunction(FormulaSplit(Nil, filtered_non_equalities, Stream.empty), _))
+ // We recombine the subproblems together.
+ val solutions = (problems map (APASynthesis.solveLazyEquations(input_variables, output_variables, _))).toList
+ return APACaseSplit.optimized(solutions)
+ }
+ assert(remaining_disjunctions.isEmpty)
+
+ /** Get only inequalities, plus maybe with "False" in other */
+ val (equalities2, inequalities, is_consistent) = partitionPAGreaterEqZero(non_equalities)
+ // equalities2 should be empty given that new_non_equalities cannot contain equalities
+ assert(equalities2 == Nil)
+ if(!is_consistent) return APAFalseSplit()
+
+ /** Remove redundant inequalities, maybe generating equalities */
+ //val (inequalities3, equalities3, is_consistent3) = removeRedundancies(inequalities)
+ val (inequalities3, equalities3, is_consistent3) = (inequalities, Nil, true)
+
+ if(!is_consistent3) return APAFalseSplit()
+ if(equalities3 != Nil)
+ return solveEqualities(FormulaSplit(equalities3, inequalities3, Stream.empty))
+
+ var current_inequalities = inequalities3
+ var current_noninequalities = List[APAEquation]()
+ var is_consistent4 = true
+ /** Solves for unbounded variables, when there are no case splits. */
+ /** The value of output_variable is going to change, but we just need the initial one. */
+ var current_inequalities_saved = APATrue()::current_inequalities
+ while(current_inequalities != current_inequalities_saved) {
+ current_inequalities_saved = current_inequalities
+ output_variables foreach { y =>
+ getInequalitiesForVariable(y, current_inequalities) match {
+ case Stream((l_formula@Nil, l_left@Nil, l_right@Nil, l_remaining)) =>
+ setRemainingVariablesToZero(y::Nil)
+ case Stream((l_formula@Nil, l_left@Nil, l_right@l, l_remaining)) =>
+ // Only bounded on the right by equations of style b*y <= pac, so we know that the integer upper bounds are pac/b
+ val upper_bounds = l_right map { case (b , pac) => APADivision(pac, b).simplified }
+ addOutputAssignment(y, APAMinimum(upper_bounds))
+ delOutputVar(y)
+ val (eqs, ineqs, consistency) = partitionPAGreaterEqZero(l_remaining)
+ if(eqs != Nil) throw new Exception("Support for equalities appearing after split is not supported (yet)")
+ is_consistent4 &&= consistency
+ current_inequalities = ineqs
+ case Stream((l_formula@Nil, l_left@l, l_right@Nil, l_remaining)) =>
+ // Only bounded on the left by equations of style pac <= a*y, so we know that the integer upper bounds are (pac+a-1)/a
+ val lower_bounds = l_left map { case (pac, a) => APADivision(pac + APACombination(a-APAInputCombination(1)), a).simplified }
+ addOutputAssignment(y, APAMaximum(lower_bounds))
+ delOutputVar(y)
+ val (eqs, ineqs, consistency) = partitionPAGreaterEqZero(l_remaining)
+ if(eqs != Nil) throw new Exception("Support for equalities appearing after split is not supported (yet)")
+ current_inequalities = ineqs
+ is_consistent4 &&= consistency
+ case _ =>
+ }
+ }
+ }
+ if(!is_consistent4) return APAFalseSplit()
+ if(output_variables == Nil) {
+ addPrecondition(APAConjunction(current_inequalities))
+ return APAEmptySplit()
+ }
+
+ // Now at this point, all variables are bounded on both sides.
+ // Let's find the one for which the LCM of its coefficients is the smallest.
+ // (Number of splits, min_coef)
+ val output_variables_with_min_coefs:List[((Int, APAInputTerm), OutputVar)] = output_variables map {
+ case y =>
+ // Both l_left and r_right are not empty because of the previous computation
+ getInequalitiesForVariable(y, current_inequalities) match {
+ case Stream((l_formula, l_left, l_right, l_remaining)) =>
+ assert(l_formula == Nil) // l_left and l_right only contain integers for bounds.
+ val l_left_coefs:List[APAInputTerm] = l_left map (_._2)
+ val l_right_coefs:List[APAInputTerm] = l_right map (_._1)
+ val min_coef = APAInputLCM(l_left_coefs ++ l_right_coefs).simplified
+ ((l_formula.size, min_coef), y)
+ case Stream.cons((l_formula, l_left, l_right, l_remaining), _) =>
+ // We have to split everything for this variable, so we don't do anything yet
+ ((l_formula.size, APAInputCombination(0)), y)
+ }
+ }
+ def min_coef(i1:((Int, APAInputTerm), OutputVar), i2:((Int, APAInputTerm), OutputVar)) : ((Int, APAInputTerm), OutputVar) = (i1, i2) match {
+ case (t1@((split1, k1), v1), t2@((split2, k2), v2)) =>
+ if(split1 < split2) t1 else (if(split1==split2) (if(needsLessOperations(k1, k2)) t1 else t2) else t2)
+ }
+ val (_, y) = output_variables_with_min_coefs.reduceRight(min_coef(_, _))
+
+ getInequalitiesForVariable(y, current_inequalities) match {
+ case Stream((Nil, l_left, l_right, l_remaining)) => // The signs are fully determined !!
+ val (eqs, ineqs, consistency) = partitionPAGreaterEqZero(l_remaining)
+ if(eqs != Nil) throw new Exception("Support for equalities appearing after split is not supported (yet)")
+ current_inequalities = ineqs
+ is_consistent4 &&= consistency
+
+ if(l_right.size <= l_left.size) {
+ val upper_bounds = l_right map { case (b , pac) => APADivision(pac, b).simplified }
+ addOutputAssignment(y, APAMinimum(upper_bounds))
+ delOutputVar(y)
+ } else {
+ val lower_bounds = l_left map { case (pac, a) => APADivision(pac + APACombination(a-APAInputCombination(1)), a).simplified }
+ addOutputAssignment(y, APAMaximum(lower_bounds))
+ delOutputVar(y)
+ }
+ val prog_needed_afterwards = output_variables != Nil
+
+ // OptimizeMe : If a is smaller than b, use it instead of a.
+ var output_variables_used = (output_variables_encountered).distinct
+ var input_variables_used = (input_variables_encountered).distinct
+
+ // We don't care about pairs of equations that are trivial to reduce.
+ l_left foreach { case (eqA, a) =>
+ l_right foreach { case (b, eqB) =>
+ if(a==APAInputCombination(1, Nil)) current_inequalities = (APAGreaterEqZero(eqB-(eqA*b)))::current_inequalities
+ else if(b==APAInputCombination(1, Nil)) current_inequalities = (APAGreaterEqZero((eqB*a)-eqA))::current_inequalities
+ }
+ }
+ val l_left_filtered = l_left filterNot (_._2==APAInputCombination(1, Nil))
+ val l_right_filtered = l_right filterNot (_._1==APAInputCombination(1, Nil))
+
+ val lcm_value = APAInputLCM((l_left_filtered map (_._2)) ++ (l_right_filtered map (_._1))).simplified
+ val lcm_int:Option[Int] = lcm_value match {
+ case APAInputCombination(i, Nil) => Some(i)
+ case _=> None
+ }
+ var lcm_expr = lcm_int match {
+ case Some(i) => APAInputCombination(i)
+ case None =>
+ lcm_value match {
+ case t@APAInputCombination(0, (i, v)::Nil) => t // Simple enough to get propagated easily
+ case t => // "Too complicated"
+ val var_lcm = getNewInputVar()
+ addSingleInputAssignment(var_lcm, lcm_value)
+ APAInputCombination(var_lcm)
+ }
+ }
+
+ val l_left_normalized = l_left_filtered map {
+ case (eqA, a) =>
+ assert(a.isPositive)
+ eqA*(lcm_expr/a)
+ }
+ val l_right_normalized = l_right_filtered map {
+ case (b, eqB) =>
+ assert(b.isPositive)
+ eqB*(lcm_expr/b)
+ }
+
+ var collected_new_input_variables:List[InputVar] = Nil
+ val collected_new_equations:List[APAEquation] = l_right_normalized flatMap { case eqB =>
+ val new_mod_bounds = l_left_normalized map { case eqA => (eqB - eqA) } filterNot {
+ case APACombination(APAInputCombination(i, Nil), Nil) =>
+ lcm_int match {
+ case Some(lcm) => i >= lcm-1
+ case None => false
+ }
+ case _ => false
+ }
+ // OptimizeMe ! If eqB%L contains only input variables, assign a new input variable to it.
+ // OptimizeMe ! If eqB & eqA contain only input variables, add a precondition for it.
+ new_mod_bounds match {
+ case Nil => Nil // Ok, nothing to add
+ case _ => // We need decomposition
+ val k = getNewInputVar().assumePositiveZero()
+ val ov = getNewOutputVar()
+ collected_new_input_variables = k::collected_new_input_variables
+ val k_expr = APAInputCombination(0, (1, k)::Nil)
+ assert(k_expr.isPositiveZero)
+ val new_ineqs = new_mod_bounds map {
+ case mod_bound =>
+ val result = APACombination(k_expr, Nil) <= mod_bound
+ result
+ }
+ val new_eq = eqB === APACombination(k_expr, (lcm_value, ov)::Nil)
+ new_eq::new_ineqs
+ }
+ }
+ (collected_new_equations, collected_new_input_variables) match {
+ case (Nil, Nil) =>
+ if(current_inequalities == Nil) {
+ APAEmptySplit()
+ } else {
+ solveEquations(FormulaSplit(Nil, current_inequalities, Stream.empty))
+ }
+ case (Nil, t) => throw new Error("How could this happen ? Both variables should be Nil, but the second one is "+t)
+ case (t, Nil) => throw new Error("How could this happen ? Both variables should be Nil, but the first one is "+t)
+ case (_, _) =>
+ val (condition, program) = APASynthesis.solve(collected_new_equations ++ current_inequalities)
+ if(prog_needed_afterwards) {
+ APAForSplit(collected_new_input_variables, APAInputCombination(0), lcm_value-APAInputCombination(1), condition, program)
+ } else {
+ //We don't need the program. Just the precondition
+ APAForSplit(collected_new_input_variables, APAInputCombination(0), lcm_value-APAInputCombination(1), condition, APAProgram.empty)
+ }
+ }
+ case stream => // The signs are not fully determined.
+ // OptimizeMe ! Too bad, the split is already done, but we cannot continue that way
+ // because we cannot assign it in other cases. Something better ?
+ val possibilities = stream.toList
+ val solutions = possibilities.zipWithIndex map {
+ case (t@(l_formula, l_left, l_right, l_remaining), i) =>
+ //println(i + " on variable " + y + " with equations " + t)
+ val left_equations = l_left map {
+ case (p, k) =>
+ if(k.isNotDefined) {
+ APAFalse()
+ } else {
+ assert(k.isPositive)
+ val right_member = APACombination(y)*k
+ p <= right_member
+ }
+ }
+ val right_equations = l_right map {
+ case (k, p) =>
+ if(k.isNotDefined) {
+ APAFalse()
+ } else {
+ assert(k.isPositive)
+ val left_member = APACombination(y)*k
+ left_member <= p
+ }
+ }
+ val collected_new_equations = left_equations ++ right_equations ++ l_remaining
+ //println("Collected : " + collected_new_equations)
+ val (condition, program) = APASynthesis.solve(output_variables, collected_new_equations)
+ val result = (condition && APAConjunction(l_formula), program)
+ result
+ }
+ //println("Regrouping solutions")
+ APACaseSplit.optimized(solutions)
+ }
+ }
+ }
+
+
+ def solve():(APACondition, APAProgram) = {
+ /************* Main algorithm *************** */
+ //***** Step 1: There are no quantifiers by construction. Nothing to do
+ //***** Step 2: Remove divisibility constraints
+ // Convert "Greater" to "GreaterEq"
+ // Simplify everything
+ //val equations2 = equations.simplified //simplifyEquations(equations)
+
+ //***** Step 3 : converting to DNF : Nothing to do, the argument is a conjunction
+ //***** Step 4 : Case Splitting. Nothing to do.
+ //***** Step 5 : Removing equalities
+
+ // All equations without output vars go directly to the global precondition
+ //val (eq_with_outputvars, eq_without_outputvars) = equations2 partition (_.has_output_variables)
+ //addPrecondition(APAConjunction(eq_without_outputvars))
+ // Retrieve all equalities
+ //var (equalities, non_equalities) = partitionPAEqualZero(eq_with_outputvars)
+
+ val result = solveEquations(equations)
+
+ // Looking for variables bounded only on one side.
+ result match {
+ case APAFalseSplit() =>
+ setFalsePrecondition()
+ (APACondition.optimized(Nil, APAConjunction(global_precondition), APAEmptySplitCondition()), APAProgram.empty)
+ case (pa_split@APACaseSplit(list_cond_prog)) =>
+ val splitted_conditions:List[APACondition] = list_cond_prog map (_._1)
+ if(list_cond_prog == Nil) { // Nobody took care of the remaining output variables.
+ setRemainingVariablesToZero(output_variables)
+ }
+ (APACondition.optimized(input_assignments, APAConjunction(global_precondition), APACaseSplitCondition(splitted_conditions)),
+ APAProgram.optimized(input_variables_initial, input_assignments, pa_split, output_assignments, output_variables_initial))
+ case pa_split@APAForSplit(vars, l, u, cond, prog) =>
+ prog match {
+ case t if t == APAProgram.empty =>
+ (APACondition.optimized(input_assignments, APAConjunction(global_precondition), APAForCondition(vars, l, u, cond)),
+ APAProgram.optimized(input_variables_initial, input_assignments, APAEmptySplit(), output_assignments, output_variables_initial))
+ case _ =>
+ (APACondition.optimized(input_assignments, APAConjunction(global_precondition), APAForCondition(vars, l, u, cond)),
+ APAProgram.optimized(input_variables_initial, input_assignments, pa_split, output_assignments, output_variables_initial))
+ }
+ case pa_split@APAEmptySplit() =>
+ setRemainingVariablesToZero(output_variables)
+ (APACondition.optimized(input_assignments, APAConjunction(global_precondition), APAEmptySplitCondition()),
+ APAProgram.optimized(input_variables_initial, input_assignments, pa_split, output_assignments, output_variables_initial))
+ case t =>
+ throw new Error("handling of "+t+" not implemented yet")
+ }
+ }
+}
diff --git a/src/main/scala/leon/synthesis/comfusy/APASynthesisExamples.scala b/src/main/scala/leon/synthesis/comfusy/APASynthesisExamples.scala
new file mode 100644
index 000000000..bcf8bafa3
--- /dev/null
+++ b/src/main/scala/leon/synthesis/comfusy/APASynthesisExamples.scala
@@ -0,0 +1,277 @@
+package leon.synthesis.comfusy
+import scala.language.implicitConversions
+
+object APASynthesisExamples {
+ import APASynthesis._
+ def O(name: String) = OutputVar(name)
+ def I(name: String) = InputVar(name)
+ implicit def OutputVarToPACombination(o: OutputVar):APACombination = APACombination(o)
+ implicit def InputVarToPACombination(i: InputVar):APACombination = APACombination(i)
+ implicit def IntegerToPACombination(k: Int):APAInputCombination = APAInputCombination(k)
+ implicit def InputToPACombination(k: APAInputCombination):APACombination = APACombination(k)
+
+ val x = O("x")
+ val x1 = O("x1")
+ val y = O("y")
+ val y1 = O("y1")
+ val z = O("z")
+
+ val a = I("a")
+ val b = I("b")
+ val c = I("c")
+ val d = I("d")
+
+ APASynthesis.advanced_modulo = false
+ APASynthesis.run_time_checks = true
+ //APASynthesis.rendering_mode = RenderingPython
+ APASynthesis.rendering_mode = RenderingScala
+ Common.computing_mode = Common.OTBezout()
+
+ def main(args : Array[String]) : Unit = {
+ /*completeExample()
+ hourMinutSecondExample()
+ balancedProblem()
+ dividesExample()
+ rhymesExample()*/
+ quotientExample()
+ //multiplicativeInverseExample()
+ //OptimizedExample()
+ //extract7and8()
+ //stepExample()
+ //extractRatio()
+ //multiArray()
+ //APAExample()
+ //paboucle()
+ }
+
+ def hourMinutSecondExample() {
+ val seconds = I("seconds")
+ val s = O("s")
+ val m = O("m")
+ val h = O("h")
+ val condition = (
+ seconds === s + (m * 60) + (h*3600)
+ && s >= 0 && s < 60
+ && m >= 0 && m < 60
+ )
+ val solution = APASynthesis.solve("getHourMinutSeconds", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+ def balancedProblem() {
+ val weight = I("weight")
+ val w1 = O("w1")
+ val w2 = O("w2")
+ val w3 = O("w3")
+ val c1 = APAEqualZero(APACombination(0, (-1, weight)::Nil, (1, w1)::(3, w2)::(9, w3)::Nil))
+ val c2 = APAGreaterEqZero(APACombination(1, Nil, (1, w1)::Nil))
+ val c3 = APAGreaterEqZero(APACombination(1, Nil, (-1, w1)::Nil))
+ val c4 = APAGreaterEqZero(APACombination(1, Nil, (1, w2)::Nil))
+ val c5 = APAGreaterEqZero(APACombination(1, Nil, (-1, w2)::Nil))
+ val c6 = APAGreaterEqZero(APACombination(1, Nil, (1, w3)::Nil))
+ val c7 = APAGreaterEqZero(APACombination(1, Nil, (-1, w3)::Nil))
+ val solution = APASynthesis.solve("getWeights", c1::c2::c3::c4::c5::c6::c7::Nil)
+ println(solution._1)
+ println(solution._2)
+ }
+
+ def dividesExample() {
+ val pac1 = APADivides(5, b+y)
+ val pac2 = APADivides(7, c+y*b)
+ val solution = APASynthesis.solve("divides5And7", pac1::pac2::Nil)
+ println(solution._1)
+ println(solution._2)
+ }
+ def completeExample() {
+ val condition = ( y*c === b || y*b === c)
+ val solution = APASynthesis.solve("solveEquation", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+
+ def stepExample() {
+ val Nsyls = I("\\Nsyls")
+ val Isyls = I("\\Isyls")
+ val Elines = I("\\Elines")
+ val Ilines = I("\\Ilines")
+ val Tlines = O("\\Tlines")
+ val Nlines = O("\\Nlines")
+ val lines8 = O("\\lineeight")
+ val lines7 = O("\\lineseven")
+ val NSlines = O("\\NSlines")
+
+ val condition = (
+ Nsyls + Isyls === lines8*8 + lines7*7
+ && lines8 >= 0
+ && lines7 >= 0
+ && lines8 < 4*7
+ && Tlines === Elines + Nlines
+ && Nlines === lines8 + lines7
+ && Nlines === Ilines + NSlines
+ && (Tlines divisible_by 4)
+ )
+
+ val solution = APASynthesis.solve("rhymes78", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+
+ def rhymesExample() {
+ /* Solves the following poem problem:
+ We want to arrange names in a poem such that :
+ -- Most verses are made of 8 syllables of names (ex: Denver Youcef Elisabeth)
+ -- Maybe some are made of 7 syllables of names + 1 conjunction syllabe. (ex: Viktor Ruzica _and_ Philip)
+ -- on verse rhymes with its predecessor or its successor.
+ Aside from the rhyming problem, there is the arrangement problem.
+ We have to know in advance how many lines of 7 and 8 syllables of names we will have, before filling them.
+
+ Furthermore, the user supplies existing lines (such as the examples given above)
+ *
+ */
+ val complete_lines = I("complete_lines")
+ val incomplete_syllables = I("incomplete_syllables")
+ val input_syllables = I("input_syllables") /* Number of syllables to place */
+ val incomplete_lines = I("incomplete_lines")
+ val new_lines = O("new_lines")
+ val new_empty_lines = O("new_empty_lines")
+ val total_lines = O("total_lines")
+ val lines_7_syllables = O("lines_7_syllables")
+ val lines_8_syllables = O("lines_8_syllables")
+ val condition = (
+ total_lines === complete_lines + new_lines
+ && new_lines === incomplete_lines + new_empty_lines
+ && new_lines === lines_7_syllables + lines_8_syllables
+ && incomplete_syllables + input_syllables === lines_7_syllables * 7 + lines_8_syllables * 8
+ && (total_lines divisible_by 4)
+
+ && lines_7_syllables >= 0
+ && lines_8_syllables >= 0
+ && total_lines >= 0
+ && lines_7_syllables < 8*4
+ )
+ val solution = APASynthesis.solve("rhymes78", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+
+ def quotientExample() {
+ //val b2 = b.assumeSign(1).toInputTerm
+ //val condition = (a === (z*b2)*b2 + x*b2 + y && y >= 0 && y < b2 && x >= 0 && x < b2)
+ //val condition = (a === x*b2 + y && y >= 0 && y < b2)
+ val j = O("j")
+ val k = O("k")
+ val i = I("i")
+ val x = I("x")
+ val y = I("y")
+ /*val condition = (i === k * x + j
+ && j >= 0 && j < x
+ && k >= 0&& k < y)*/
+ val condition = APAConjunction(List(APAEqualZero(APACombination(APAInputCombination(0,
+ List((1,InputVar("i")))),List((APAInputCombination(-1,List()),OutputVar("j")),
+ (APAInputCombination(0,List((-1,InputVar("x")))),OutputVar("k"))))),
+ APAGreaterEqZero(APACombination(APAInputCombination(0,List()),
+ List((APAInputCombination(1,List()),OutputVar("j"))))),
+ APAGreaterEqZero(APACombination(APAInputCombination(-1,List((1,InputVar("x")))),List((APAInputCombination(-1,List()),OutputVar("j"))))), APAGreaterEqZero(APACombination(APAInputCombination(0,List()),List((APAInputCombination(1,List()),OutputVar("k"))))), APAGreaterEqZero(APACombination(APAInputCombination(-1,List((1,InputVar("y")))),List((APAInputCombination(-1,List()),OutputVar("k")))))))
+
+ println(condition)
+ val solution = APASynthesis.solve("quotient", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+
+ def multiplicativeInverseExample() {
+ val M = InputVar("M")
+ val n = InputVar("n")
+ val x = OutputVar("x")
+ val condition = ((x*n - 1) divisible_by M.toInputTerm())
+ val solution = APASynthesis.solve("multiplicativeInverse", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+
+ /*def ArmandoExample() {
+ return
+ val N = InputVar("N").assumeSign(1)
+ val P = InputVar("P").assumeSign(1)
+ val p1 = InputVar("p1")
+ val p2 = InputVar("p2")
+ val e1 = OutputVar("e1")
+ val s1 = OutputVar("s1")
+ val e2 = OutputVar("e2")
+ val s2 = OutputVar("s1")
+ val p1len = OutputVar("p1len")
+ val p2len = OutputVar("p2len")
+ val rstart_c1 = OutputVar("rstart_c1")
+ val rstart_c2 = OutputVar("rstart_c2")
+ val rstart_c3 = OutputVar("rstart_c3")
+ val rstart_c4 = OutputVar("rstart_c4")
+ val rstart_c5 = OutputVar("rstart_c5")
+ val rstart_c6 = OutputVar("rstart_c6")
+ val rstart_c7 = OutputVar("rstart_c7")
+ val rstart_c8 = OutputVar("rstart_c8")
+ val rstart_c9 = OutputVar("rstart_c9")
+ val condition = (
+ ({val (proc, totProc, N) = (p1, P, N)
+ val modNtoProc = APAInputMod(N.toInputTerm(), totProc.toInputTerm())
+ (proc+rstart_c1 > modNtoProc +rstart_c2 && (proc-1+rstart_c3)*((N-1+rstart_c4)/totProc+rstart_c5-1)+modNtoProc+rstart_c6) ||
+ (proc+rstart_c1 <= modNtoProc +rstart_c2 && (proc-1+rstart_c7)*((N-1+rstart_c8)/totProc+rstart_c9-1))
+ }) &&
+ p1len === e1-s1 &&
+ p2len === e2-s2 &&
+ (p1len === p2len || p1len === p2len.toCombination() + 1 || p1len === p2len -1) &&
+ (p2 > p1+1 || p2 < p1+1 || s2===e1) &&
+ (p2 > p1-1 || p2 < p1-1 || e2===N) &&
+ (p1 > 0 || p1 < 0 || s1 === 0)
+ )
+ val solution = APASynthesis.solve("Armando", condition)
+ println(solution._1)
+ println(solution._2)
+ }*/
+
+ def OptimizedExample() {
+ val condition = (x+y === a+b && (b < x && x < y || a < y && y < x))
+ val solution = APASynthesis.solve("testnotdnf", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+
+ def extract7and8() {
+ val condition = (x*7+y*8+z*3 === a && x*2 >= y && y*2 >= z && z*2 >= x)
+ val solution = APASynthesis.solve("extract7and8", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+
+ def extractRatio() {
+ val condition = (y*b === c || y*c === b)
+ val solution = APASynthesis.solve("extractRatio", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+ def APAExample() {
+ val condition = (x*(-2)+y*a+1 <= 0 && x*b+y*3 + (-1) >= 0)
+ val solution = APASynthesis.solve("APAExample", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+
+ def multiArray() {
+ val x = I("x")
+ val y = I("y")
+ val c = O("c")
+ val i = O("i")
+ val j = O("j")
+ val condition = ((c + (i + j*640)*3 === x + y*1024) && c >= 0 && c < 3 && i >= 0 && i < 640 && j >= 0 && j < 480 && x >= 0 && x < 1024)
+ val solution = APASynthesis.solve("multiArray", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+
+ def paboucle() {
+ val a = I("a")
+ val condition = c - y <= a - x*6 && a - x*6 <= b + x + y*7 && x > y + z && z*9 <= x+y && z*5 > b + x + 8
+ val solution = APASynthesis.solve("paboucle", condition)
+ println(solution._1)
+ println(solution._2)
+ }
+}
diff --git a/src/main/scala/leon/synthesis/comfusy/Common.scala b/src/main/scala/leon/synthesis/comfusy/Common.scala
new file mode 100644
index 000000000..174171919
--- /dev/null
+++ b/src/main/scala/leon/synthesis/comfusy/Common.scala
@@ -0,0 +1,390 @@
+package leon.synthesis.comfusy
+
+object Common {
+
+ sealed abstract class BezoutType
+ case class OTBezout() extends BezoutType // Home-made
+ case class MMBezout() extends BezoutType // Made manually.
+
+ var computing_mode:BezoutType = MMBezout()
+
+ def bezout(e: Int, a : List[Int]):List[Int] = {
+ computing_mode match {
+ case OTBezout() => bezoutOT(e, a)
+ case MMBezout() => bezoutMM(a, e / gcdlist(a))
+ }
+ }
+ def bezoutWithBase(e: Int, a: List[Int]): (List[List[Int]]) = {
+ computing_mode match {
+ case OTBezout() => bezoutWithBaseOT(e, a)
+ case MMBezout() => bezoutWithBaseMM(e, a)
+ }
+ }
+
+ /** Shortcuts */
+ def bezout(e: Int, a : Int*): List[Int] = bezout(e, a.toList)
+ def bezoutWithBase(e: Int, a : Int*): List[List[Int]] = bezoutWithBase(e, a.toList)
+
+ /*
+ * Algorithms derived from the Omega-tests
+ */
+
+ // Finds a vector x such that x.a + e is 0 if this is possible.
+ def bezoutOT(e: Int, a : List[Int]):List[Int] = {
+ a match {
+ case Nil => Nil
+ case 0::Nil => 0::Nil
+ case k::Nil => ((-e + smod(e, k))/k) :: Nil
+ case a =>
+ val a_indexed:List[(Int, Int)] = (a.indices zip a).toList
+ (a_indexed find (_._2 == 0)) match {
+ case Some((index, element)) =>
+ // Takes the result when removing the zeros, then inserts some zeros.
+ val a_filtered = a filterNot (_==0)
+ val a_filtered_solved = bezoutOT(e, a_filtered )
+ val (_, a_solved) = a.foldLeft((a_filtered_solved, Nil:List[Int]))((_,_) match {
+ case ((q, l), 0) => (q, 0::l)
+ case ((Nil, l), k) => (Nil, l) // This should not happen, as we have many zeroes.
+ case ((a::q, l), k) => (q, a::l)
+ })
+ a_solved.reverse
+ case None =>
+ (a_indexed find {case a => Math.abs(a._2) == 1}) match {
+ case Some((index, element)) => // This is easy, like solving 4x+y+45z=30, just say that y=30 and other are zero
+ val sign = if(element > 0) 1 else -1
+ (a_indexed map { case (i, c) => if(i==index) -e/sign else 0 })
+ case None =>
+ val (index, min_coef) = a_indexed reduceLeft {
+ (_, _) match {
+ case (t1@(i, ci), t2@(j, cj)) => if(Math.abs(ci) < Math.abs(cj)) t1 else t2
+ }}
+ val min_coef_sign = if(min_coef > 0) 1 else -1
+ val abs_min_coef_plus_1 = Math.abs(min_coef) + 1
+ val e_assign = smod(e, abs_min_coef_plus_1)
+ val a_assign = abs_min_coef_plus_1 :: (a map {case k => smod(k, abs_min_coef_plus_1)})
+ // the coefficient at index 'index+1' is now 1 or -1.
+
+ val new_e = (e + Math.abs(min_coef) * (smod(e, abs_min_coef_plus_1)))/abs_min_coef_plus_1
+ val new_a = Math.abs(min_coef)::(a map {
+ case k => (k + Math.abs(min_coef) * (smod(k, abs_min_coef_plus_1)))/abs_min_coef_plus_1
+ })
+ // the coefficient at index 'index+1' is now 0, so it will be removed.
+
+ // Now, there is at least one zero in new_a
+ bezoutOT(new_e, new_a) match {
+ case Nil => throw new Error("Should not happen, the size of the input and output are the same")
+ case q@(w::l) =>
+ l.splitAt(index) match {
+ // The coefficient at index 'index' of l should be zero, we replace it by its assignment
+ case (left, 0::right) =>
+ val solution_a = scalarProduct(a_assign, q)
+ val solution = (solution_a + e_assign)*(min_coef_sign)
+ val result = left++(solution::right)
+ result
+ case (l, m) => throw new Error("The list "+l+" should have 0 at index "+index+" but this is not the case")
+ }
+ }
+ }
+ }
+ }
+ }
+
+ def enumerate[A](a: List[A]):List[(Int, A)] = (a.indices zip a).toList
+
+ // a is an indexed list
+ def replaceElementAtIndexByCoef(a: List[(Int, Int)], index: Int, coef: Int) = a map { _ match {
+ case (i, c) => if(i == index) coef else c
+ } }
+
+ // a is an indexed list
+ def replaceEverythingByCoef1ExceptCoef2AtIndex(a: List[(Int, Int)], coef1:Int, coef2: Int,index: Int): List[Int] =
+ a map { _ match {
+ case (i, c) if i == index => coef2
+ case _ => coef1
+ }
+ }
+
+ // a is an indexed list
+ def replaceEverythingByZeroExcept1AtIndex(a: List[(Int, Int)], index: Int): List[Int] =
+ replaceEverythingByCoef1ExceptCoef2AtIndex(a, 0, 1, index)
+
+ // a is an indexed list
+ def putCoef1AtIndex1andCoef2AtIndex2(a: List[(Int, Int)], coef1: Int, index1: Int, coef2: Int, index2: Int ):List[Int] = {
+ a map { _ match {
+ case (i, c) =>
+ if(i == index1) coef1
+ else if(i==index2) coef2
+ else 0
+ }
+ }
+ }
+
+ //insertPreviousZeros([a, b, 0, c, 0, d], [x, y, z, w]) = [x, y, 0, z, 0, d]
+ def insertPreviousZeros(list_with_zeros: List[Int], list_to_complete: List[Int]) :List[Int] = {
+ val (_, result) = list_with_zeros.foldLeft((list_to_complete, Nil: List[Int]))( (_, _) match {
+ case ((l, result), 0) => (l, 0::result)
+ case ((a::q, result), _) => (q, a::result)
+ })
+ result.reverse
+ }
+
+ def scalarProduct(a: List[Int], b:List[Int]): Int = {
+ (a zip b).foldLeft(0){case (result, (e_a, e_b)) => result + e_a * e_b}
+ }
+
+
+ // If size(a) == n, finds a matrix x of size n x n
+ // such that (1, k1, k2, .. kn-1).x.a + e = 0 if this is possible,
+ // and x describes all integer solutions of this equation. Rank(x) = n-1
+ // Output representation : it's the list of rows.
+ // n n n
+ def bezoutWithBaseOT(e: Int, a: List[Int]): (List[List[Int]]) = {
+ a match {
+ case Nil => Nil
+ case 0::Nil => ((0::Nil)::Nil)
+ case k::Nil => (((-e + smod(e, k))/k) :: Nil) :: Nil
+ case a =>
+ val a_indexed:List[(Int, Int)] = enumerate(a)
+ (a_indexed find (_._2 == 0)) match {
+ case Some((index, element)) =>
+ // Takes the result when removing the zeros, then inserts some zeros.
+ val a_filtered = a filterNot ( _ == 0 )
+ val solution = bezoutWithBase(e, a_filtered)
+ val (_, solution_completed) = a_indexed.foldLeft((solution, Nil:List[List[Int]]))((_,_) match {
+ case ((q, l), (i, 0)) => (q, replaceEverythingByZeroExcept1AtIndex(a_indexed, i)::l)
+ case ((Nil, l), (i, k)) => (Nil, l) // This should not happen, as we have many zeroes.
+ case ((b::q, l), (i, k)) => (q, insertPreviousZeros(a, b)::l)
+ })
+ // For each zero in a_indexed, add a new vector where 1 is at this position and it's zero everywhere else.
+ val new_vectors = a_indexed flatMap {
+ case (index, 0) => List(replaceEverythingByZeroExcept1AtIndex(a_indexed, index))
+ case t => Nil
+ }
+ solution_completed.reverse
+ case None =>
+ (a_indexed find {case a => Math.abs(a._2) == 1}) match {
+ case Some((index, element)) =>
+ // This is easy, like solving 4x+y+45z=30, just say that y=30 and other are zero
+ // For the vectors, just take the other variables as themselves.
+ val neg_sign = if(element > 0) -1 else 1
+ replaceEverythingByCoef1ExceptCoef2AtIndex(a_indexed, 0, neg_sign*e, index) ::
+ (a_indexed flatMap ( _ match {
+ case (i, c) =>
+ if(i == index)
+ Nil
+ else
+ List(putCoef1AtIndex1andCoef2AtIndex2(a_indexed, 1, i, neg_sign*c, index))
+ }))
+ case None =>
+ val (index, min_coef) = a_indexed reduceLeft {
+ (_, _) match {
+ case (t1@(i, ci), t2@(j, cj)) => if(Math.abs(ci) < Math.abs(cj)) t1 else t2
+ }}
+ val min_coef_sign = if(min_coef > 0) 1 else -1
+ val min_coef_sign2 = if(min_coef > 0) -1 else 1
+ val abs_min_coef_plus_1 = Math.abs(min_coef) + 1
+ val e_assign = smod(e, abs_min_coef_plus_1)
+ val a_assign = abs_min_coef_plus_1 :: (a map {case k => smod(k, abs_min_coef_plus_1)})
+ // the coefficient at index 'index+1' is now 1 or -1.
+
+ val new_e = (e + Math.abs(min_coef) * (smod(e, abs_min_coef_plus_1)))/abs_min_coef_plus_1
+ val new_a = Math.abs(min_coef)::(a map {
+ case k => (k + Math.abs(min_coef) * (smod(k, abs_min_coef_plus_1)))/abs_min_coef_plus_1
+ })
+ // the coefficient at index 'index+1' is now 0, so it will be removed.
+
+ // Now, there is at least one zero in new_a
+ bezoutWithBase(new_e, new_a) match {
+ case Nil => throw new Error("Should not happen as the input was not empty")
+ case v =>
+ val v_reduced = enumerate(v) flatMap {
+ case (i, Nil) => throw new Error("Should not happen as the input was not empty")
+ case (i, vs@(a::q)) =>
+ if(i == index +1) // We drop the line indicated by index
+ Nil
+ else {
+ val vs_replaced = replaceElementAtIndexByCoef(enumerate(vs), index+1, 0)
+ if(i==0) {
+ val new_element = (e_assign+scalarProduct(a_assign, vs_replaced)) * min_coef_sign
+ List(replaceElementAtIndexByCoef(enumerate(q), index, new_element))
+ } else {
+ val new_element = (scalarProduct(a_assign, vs_replaced)) * min_coef_sign
+ List(replaceElementAtIndexByCoef(enumerate(q), index, new_element))
+ }
+ }
+ }
+ v_reduced
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Hand-made algorithms.
+ */
+
+ // Finds (x1, x2, k) such that x1.a + x2.b + gcd(a,b) = 0 and k =
+ // gcd(a ,b)
+ def advancedEuclid(a_in: Int, b_in: Int):(Int, Int, Int) = {
+ var (x, lastx) = (0, 1)
+ var (y, lasty) = (1, 0)
+ var (a, b) = (a_in, b_in)
+ var (quotient, temp) = (0, 0)
+ while(b != 0) {
+ val amodb = (Math.abs(b) + a%b)%b
+ quotient = (a - amodb)/b
+ a = b
+ b = amodb
+ temp = x
+ x = lastx-quotient*x
+ lastx = temp
+ temp = y
+ y = lasty-quotient*y
+ lasty = temp
+ }
+ if(a < 0)
+ return (lastx, lasty, -a)
+ else
+ return (-lastx, -lasty, a)
+ }
+
+ // Finds coefficients x such that k*gcd(a_in) + x.a_in = 0
+ def bezoutMM(a_in: List[Int], k: Int):List[Int] = {
+ var a = a_in
+ var a_in_gcds = a_in.foldRight(Nil:List[Int]){
+ case (i, Nil) => List(i)
+ case (i, a::q) => gcd(a, i)::a::q
+ }
+ var result:List[Int] = Nil
+ var last_coef = -1
+ while(a != Nil) {
+ a match {
+ case Nil =>
+ case 0::Nil =>
+ result = 0::result
+ a = Nil
+ case el::Nil =>
+ // Solution is -el/abs(el)
+ result = (k*(-last_coef * (-el/Math.abs(el))))::result
+ a = Nil
+ case (el1::el2::Nil) =>
+ val (u, v, _) = advancedEuclid(el1, el2)
+ result = (-v*k*last_coef)::(-u*k*last_coef)::result
+ a = Nil
+ case (el1::q) =>
+ val el2 = a_in_gcds.tail.head
+ val (u, v, _) = advancedEuclid(el1, el2)
+ result = (-u*k*last_coef)::result
+ last_coef = -v*last_coef
+ a = q
+ a_in_gcds = a_in_gcds.tail
+ }
+ }
+ result.reverse
+ }
+
+ // Finds coefficients x such that gcd(a_in) + x.a_in = 0
+ def bezoutMM(a_in: List[Int]):List[Int] = bezoutMM(a_in, 1)
+
+
+ def bezoutWithBaseMM(e: Int, a: List[Int]): (List[List[Int]]) = {
+ var coefs = a
+ var coefs_gcd = coefs.foldRight(Nil:List[Int]){
+ case (i, Nil) => List(Math.abs(i))
+ case (i, a::q) => gcd(a, i)::a::q
+ }
+ var n = a.length
+ var result = List(bezoutMM(a, e/coefs_gcd.head)) // The gcd of all coefs divides e.
+ var i = 1
+ var zeros:List[Int] = Nil
+ while(i <= n-1) {
+ val kii = coefs_gcd.tail.head / coefs_gcd.head
+ val kijs = bezoutMM(coefs.tail, coefs.head/coefs_gcd.head)
+ result = (zeros ::: (kii :: kijs))::result
+ coefs = coefs.tail
+ coefs_gcd = coefs_gcd.tail
+ zeros = 0::zeros
+ i += 1
+ }
+ result.reverse
+ }
+
+ class MatrixIterator(val m: List[List[Int]]) {
+ private var l:List[Int] = m.flatten
+ def next: Int = {
+ val result = l.head
+ l = l.tail
+ result
+ }
+ }
+
+ def smod(x:Int, m_signed:Int) = {
+ val m = Math.abs(m_signed)
+ val c = x % m
+ if(c >= (m+1)/2)
+ c - m
+ else if(c < -m/2)
+ c + m
+ else c
+ }
+
+ // Computes the GCD between two numbers
+ // Unsafe if y = -2^31
+ def gcd(x:Int, y:Int): Int = {
+ if (x==0) Math.abs(y)
+ else if (x<0) gcd(-x, y)
+ else if (y<0) gcd(x, -y)
+ else gcd(y%x, x)
+ }
+
+ // Computes the GCD between two numbers. Binary speed-up.
+ def binaryGcd(a:Int, b:Int):Int = {
+ var g = 1
+ var (u, v) = (a, b)
+ while(u%2 == 0 && v%2 == 0) {
+ u = u/2
+ v = v/2
+ g = 2*g
+ }
+ while(u != 0) {
+ if(u % 2 == 0) u = u/2
+ else if(v % 2 == 0) v = v/2
+ else {
+ val t = Math.abs(u-v)/2
+ if(u >= v) u = t else v = t
+ }
+ }
+ return g*v;
+ }
+
+ // Computes the LCM between two numbers
+ def lcm(x: Int, y: Int): Int = {
+ if(x < 0) lcm(-x, y)
+ else if(y < 0) lcm(x, -y)
+ else x * y / gcd(x, y)
+ }
+ // Computes the LCM over a list of numbers
+ // Do not handle correctly zero numbers.
+ def lcmlist(l:List[Int]):Int = l match {
+ case Nil => 1
+ case 0::q => lcmlist(q)
+ case a::Nil => if(a < 0) -a else a
+ case (a::b::q) => lcmlist(lcm(a,b)::q)
+ }
+ // Computes the GCD over a list of numbers
+ // Do not handle zero numbers.
+ def gcdlist(l:List[Int]):Int = l match {
+ case Nil => throw new Exception("Cannot compute GCD of nothing")
+ case 0::Nil => throw new Exception("Cannot compute GCD of zero")
+ case a::Nil => if(a < 0) -a else a
+ case (a::b::q) => gcdlist(gcd(a,b)::q)
+ }
+ // None represents infinity
+ def gcdlistComplete(l:List[Int]):Option[Int] = l match {
+ case Nil => None
+ case 0::q => gcdlistComplete(q)
+ case a::Nil => if(a < 0) Some(-a) else Some(a)
+ case (a::b::q) => gcdlistComplete(gcd(a, b)::q)
+ }
+}
diff --git a/src/main/scala/leon/synthesis/rules/IntegerEquality.scala b/src/main/scala/leon/synthesis/rules/IntegerEquality.scala
new file mode 100644
index 000000000..7b2dbf7ac
--- /dev/null
+++ b/src/main/scala/leon/synthesis/rules/IntegerEquality.scala
@@ -0,0 +1,556 @@
+/* Copyright 2009-2016 EPFL, Lausanne */
+
+package leon
+package synthesis
+package rules
+
+
+import scala.annotation.tailrec
+import scala.collection.mutable.ListBuffer
+import evaluators.AbstractEvaluator
+import purescala.Definitions._
+import purescala.Common._
+import purescala.Types._
+import purescala.Constructors._
+import purescala.Expressions._
+import purescala.Extractors._
+import purescala.TypeOps
+import purescala.DefOps
+import purescala.ExprOps
+import purescala.SelfPrettyPrinter
+import solvers.ModelBuilder
+import solvers.string.StringSolver
+import programsets.DirectProgramSet
+import programsets.JoinProgramSet
+import leon.synthesis.comfusy._
+import leon.utils.Bijection
+
+class VarContext(val inputVariables: Set[Identifier], val outputVariables: Set[Identifier], val sctx: SearchContext) {
+ private var idToInputVar = new Bijection[Identifier, InputVar]
+ private var idToOutputVar = new Bijection[Identifier, OutputVar]
+
+ def getInputVar(id: Identifier): InputVar = {
+ idToInputVar.cachedB(id){
+ var i = 0
+ while(idToInputVar.containsB(InputVar(id.name + i))) i+=1
+ InputVar(id.name + i)
+ }
+ }
+
+ def getOutputVar(id: Identifier): OutputVar = {
+ idToOutputVar.cachedB(id){
+ var i = 0
+ while(idToOutputVar.containsB(OutputVar(id.name + i))) i+=1
+ OutputVar(id.name + i)
+ }
+ }
+
+ def getIdentifier(iv: InputVar): Identifier= {
+ idToInputVar.cachedA(iv){
+ FreshIdentifier(iv.name, IntegerType, false)
+ }
+ }
+
+ def getIdentifier(ov: OutputVar): Identifier= {
+ idToOutputVar.cachedA(ov){
+ FreshIdentifier(ov.name, IntegerType, false)
+ }
+ }
+
+ def newIdentifierName(init: String = ""): String = {
+ for(i <- 0 until 26) {
+ val c = init + ('a'.toInt + i).toChar.toString
+ if(idToInputVar.iterator.forall(_._1.name != c) && idToOutputVar.iterator.forall(_._1.name != c)) {
+ return c
+ }
+ }
+ newIdentifierName(init + "a")
+ }
+}
+
+case class NotInputVarException(msg: String) extends Exception(msg)
+
+object ComfusyConverters {
+
+ def convertToAPAInputTerm(e: Expr)(implicit vc: VarContext): APAInputTerm = e match {
+ case Plus(a, b) => convertToAPAInputTerm(a) + convertToAPAInputTerm(b)
+ case Minus(a, b) => convertToAPAInputTerm(a) - convertToAPAInputTerm(b)
+ case UMinus(a) => -convertToAPAInputTerm(a)
+ case Times(a, b) => convertToAPAInputTerm(a) * convertToAPAInputTerm(b)
+ case Division(a, b) => convertToAPAInputTerm(a) / convertToAPAInputTerm(b).assumeNotZero()
+ case purescala.Expressions.Assert(_, Some("Division by zero"), a) => convertToAPAInputTerm(a)
+ case IfExpr(LessEquals(a1, InfiniteIntegerLiteral(zero)), UMinus(a2), a3) if a1 == a2 && a2 == a3 && zero == BigInt(0) => APAInputAbs(convertToAPAInputTerm(a1))
+ //APAInputMod is not supported in Comfusy
+ case Variable(i) if vc.inputVariables contains i => APAInputCombination(0, (1, vc.getInputVar(i))::Nil)
+ case InfiniteIntegerLiteral(k) => APAInputCombination(k.toInt, Nil)
+ case _ => throw NotInputVarException(s"$e is not an input variable. Only + / * - and ${vc.inputVariables.mkString(",")} are allowed")
+ }
+
+ def convertToAPACombination(e: Expr)(implicit vc: VarContext): APACombination = e match {
+ case Plus(a, b) => convertToAPACombination(a) + convertToAPACombination(b)
+ case Minus(a, b) => convertToAPACombination(a) - convertToAPACombination(b)
+ case UMinus(a) => -convertToAPACombination(a)
+ case Times(a, b) =>
+ if(isInputVarParsable(a)) {
+ convertToAPAInputTerm(a) * convertToAPACombination(b)
+ } else {
+ convertToAPAInputTerm(b) * convertToAPACombination(a)
+ }
+ case Division(a, b) =>
+ convertToAPACombination(a) / convertToAPAInputTerm(b)
+ case Variable(i) if vc.outputVariables contains i =>
+ APACombination(APAInputCombination(0), (APAInputCombination(1), vc.getOutputVar(i))::Nil)
+ case e => APACombination(convertToAPAInputTerm(e), Nil)
+ }
+ def convertToAPAEqualZero(e: Equals)(implicit vc: VarContext) = {
+ APAEqualZero(convertToAPACombination(e.lhs) - convertToAPACombination(e.rhs))
+ }
+
+ def isInputVarParsable(e: Expr)(implicit vc: VarContext): Boolean = {
+ def aux(e: Expr): Boolean = e match {
+ case Plus(a, b) => aux(a) && aux(b)
+ case Minus(a, b) => aux(a) && aux(b)
+ case UMinus(a) => aux(a)
+ case Times(a, b) => aux(a) && aux(b)
+ case Division(a, b) => aux(a) && aux(b)
+ case purescala.Expressions.Assert(_, Some("Division by zero"), a) => aux(a)
+ //case Remainder(a, b) => aux(a) && aux(b)
+ //case Modulo(a, b) => aux(a) && aux(b)
+ case Variable(i) => vc.inputVariables contains i
+ case InfiniteIntegerLiteral(k) => true
+ case _ => false
+ }
+ aux(e)
+ }
+
+ def isLinInOutputVariables(e: Expr)(implicit vc: VarContext): Boolean = {
+ def aux(e: Expr): Boolean = e match {
+ case Plus(a, b) => aux(a) && aux(b)
+ case Minus(a, b) => aux(a) && aux(b)
+ case UMinus(a) => aux(a)
+ case Times(a, b) => (isInputVarParsable(a) && isLinInOutputVariables(b)) ||
+ (isInputVarParsable(b) && isLinInOutputVariables(a))
+ case Division(a, b) => isInputVarParsable(b) && isLinInOutputVariables(a)
+ case Variable(i) => (vc.inputVariables contains i) || (vc.outputVariables contains i)
+ case InfiniteIntegerLiteral(k) => true
+ case _ => isInputVarParsable(e)
+ }
+ aux(e)
+ }
+
+ def isLinEqInOutputVariables(e: Expr)(implicit vc: VarContext): Boolean = {
+ e match {
+ case Equals(a, b) => isLinInOutputVariables(a) &&
+ isLinInOutputVariables(b)
+ }
+ }
+
+
+
+ def convertToSolution(cond: APACondition, prog: APAProgram)(implicit vc: VarContext): Solution = {
+ val precond: Expr = APAConditionToExpr(cond)
+ val term: Expr = APAProgramToExpr(prog, prog.output_variables)
+ Solution(precond, Set.empty, term)
+ }
+
+ def APACombinationToExpr(e: APACombination)(implicit vc: VarContext): Expr = e match {
+ case APACombination(coef, varsWithCoefs) => (APAInputTermToExpr(coef) /: varsWithCoefs) { (c: Expr, iv) =>
+ val (i, v) = iv
+ Plus(c, Times(APAInputTermToExpr(i), Variable(vc.getIdentifier(v))))
+ }
+ }
+
+ def APAFormulaToExpr(e: APAFormula)(implicit vc: VarContext): Expr = e match {
+ case e: APAEquation => APAEquationToExpr(e)
+ case APAConjunction(conjuncts) => And(conjuncts map APAFormulaToExpr)
+ case APADisjunction(conjuncts) => Or(conjuncts map APAFormulaToExpr)
+ case APANegation(formula) => Not(APAFormulaToExpr(formula))
+ }
+
+ def APAEquationToExpr(e: APAEquation)(implicit vc: VarContext): Expr = e match {
+ case APADivides(coefficient, pac) => Equals(Modulo(APACombinationToExpr(pac), APAInputTermToExpr(coefficient)), InfiniteIntegerLiteral(BigInt(0)))
+ case APAEqualZero(pac) => Equals(APACombinationToExpr(pac), InfiniteIntegerLiteral(BigInt(0)))
+ case APAGreaterZero(pac) => GreaterThan(APACombinationToExpr(pac), InfiniteIntegerLiteral(BigInt(0)))
+ case APAGreaterEqZero(pac) => GreaterEquals(APACombinationToExpr(pac), InfiniteIntegerLiteral(BigInt(0)))
+ case APATrue() => BooleanLiteral(true)
+ case APAFalse() => BooleanLiteral(false)
+ }
+
+ def InputAssignmentToExpr(input_assignment: InputAssignment)(implicit vc: VarContext): Expr => Expr = {
+ input_assignment match {
+ case SingleInputAssignment(iv, t) =>
+ (x: Expr) => Let(vc.getIdentifier(iv), APAInputTermToExpr(t), x)
+ case BezoutInputAssignment(vs, ts) =>
+ val tsConverted = ts map APAInputTermToExpr
+ import vc.sctx.program.library.{ List => LeonList, Cons => LeonCons, Nil => LeonNil }
+ val listType = LeonList.get
+ val applyfun = vc.sctx.program.library.lookupUnique[FunDef]("leon.collection.List.apply")
+ val bezoutWithBase = vc.sctx.program.library.bezoutWithBase.get
+ val b = FreshIdentifier(vc.newIdentifierName(), listType.typed(Seq(listType.typed(Seq(IntegerType)))), false)
+ val decomposed: List[Expr => Expr] = vs.zipWithIndex.map{ case (lv, i) =>
+ lv.zipWithIndex.map { case (v, j) =>
+ (w: Expr) =>
+ Let(vc.getIdentifier(v),
+ functionInvocation(applyfun, Seq(
+ functionInvocation(applyfun, Seq(Variable(b), InfiniteIntegerLiteral(BigInt(i)))),
+ InfiniteIntegerLiteral(BigInt(j)))), w)
+ }
+ }.flatten
+ val decomposed_recomposed = decomposed.reduce(_ compose _)
+ val tsConvertedToList = (tsConverted :\ (CaseClass(CaseClassType(LeonNil.get, Seq(IntegerType)), Seq()): Expr)) {
+ (v, l) =>
+ CaseClass(CaseClassType(LeonCons.get, Seq(IntegerType)), Seq(v, l))
+ }
+ val initial_fun = (x: Expr) => Let(b, functionInvocation(bezoutWithBase, Seq(InfiniteIntegerLiteral(BigInt(1)), tsConvertedToList)), x)
+ initial_fun compose decomposed_recomposed
+ }
+ }
+
+ def APASplitConditionToExpr(sc: APASplitCondition)(implicit vc: VarContext): Expr = sc match {
+ case APAEmptySplitCondition() => BooleanLiteral(true)
+ case APACaseSplitCondition(Nil) => BooleanLiteral(true)
+ case APACaseSplitCondition(a::Nil) => APAAbstractConditionToExpr(a)
+ case APACaseSplitCondition(csl) => Or(csl.map(e => APAAbstractConditionToExpr(e)))
+ case APAForCondition(vl, lower_range, upper_range, global_condition) =>
+ val range = vc.sctx.program.library.lookupUnique[FunDef]("leon.collection.List.range")
+ val flatMap = vc.sctx.program.library.lookupUnique[FunDef]("leon.collection.List.flatMap")
+ val map = vc.sctx.program.library.lookupUnique[FunDef]("leon.collection.List.map")
+ val exists = vc.sctx.program.library.lookupUnique[FunDef]("leon.collection.List.exists")
+ val basic_range = functionInvocation(range, Seq(APAInputTermToExpr(lower_range), APAInputTermToExpr(upper_range)))
+ var ranges = basic_range
+ var tupleType: TypeTree = IntegerType
+ vl.tail foreach { k =>
+ val t = FreshIdentifier("t", IntegerType)
+ val i = FreshIdentifier("i", IntegerType)
+ ranges = functionInvocation(flatMap, Seq(ranges, Lambda(Seq(ValDef(t)),
+ functionInvocation(map, Seq(basic_range, Lambda(Seq(ValDef(i)), tupleWrap(Seq(Variable(i), Variable(t)))))))))
+ tupleType = TupleType(Seq(IntegerType, tupleType))
+ }
+ def getPattern(i: InputVar): Pattern = WildcardPattern(Some(vc.getIdentifier(i)))
+ val tuplePattern = (vl.init :\ getPattern(vl.last)) {
+ case (v, p) => tuplePatternWrap(Seq(getPattern(v), p))
+ }
+
+ val evars = FreshIdentifier("evars", tupleType)
+ functionInvocation(exists, Seq(ranges,
+ Lambda(Seq(ValDef(evars)),
+ MatchExpr(Variable(evars),
+ Seq(MatchCase(tuplePattern, None, APAConditionToExpr(global_condition)))))
+ ))
+ }
+
+ def APAAbstractConditionToExpr(cond: APAAbstractCondition)(implicit vc: VarContext): Expr = {
+ cond match {
+ case c: APACondition => APAConditionToExpr(c)
+ case s: APASplitCondition => APASplitConditionToExpr(s)
+ }
+ }
+
+ def ListOutputAssignmentToExpr(l: List[(OutputVar, APATerm)])(implicit vc: VarContext): Expr => Expr = {
+ val outputAssignments = if(l.isEmpty) None
+ else Some((l map (e => (x: Expr) => Let(vc.getIdentifier(e._1), APATermToExpr(e._2), x))) reduce (_ compose _))
+ (e: Expr) => outputAssignments.map(f => f(e)).getOrElse(e)
+ }
+
+ def ListInputAssignmentToExpr(l: List[InputAssignment])(implicit vc: VarContext): Expr => Expr = {
+ val inputAssignments = if(l.isEmpty) None
+ else Some((l map (e => InputAssignmentToExpr(e))) reduce (_ compose _))
+ (e: Expr) => inputAssignments.map(f => f(e)).getOrElse(e)
+ }
+
+ def APAConditionToExpr(cond: APACondition)(implicit vc: VarContext): Expr = {
+ val assigns = ListInputAssignmentToExpr(cond.input_assignment)
+ val g = APAFormulaToExpr(cond.global_condition)
+ if(cond.isTrivial) {
+ BooleanLiteral(true)
+ } else {
+ cond.pf match {
+ case APAEmptySplitCondition() =>
+ assigns(g)
+ case _ => // There are some more splits.
+ if(g == BooleanLiteral(true)) {
+ assigns(APASplitConditionToExpr(cond.pf))
+ } else {
+ assigns(And(g, APASplitConditionToExpr(cond.pf)))
+ }
+ }
+ }
+ }
+
+ def APAProgramToExpr(prog: APAProgram, expected_output_vars: List[OutputVar])(implicit vc: VarContext): Expr = {
+ val assigns = ListInputAssignmentToExpr(prog.input_assignment)
+ val affectedOutputVariables = prog.output_assignment.map(_._1).toSet
+ val referedOutputVariables = (expected_output_vars ++ prog.output_assignment.flatMap(_._2.output_variables)) filterNot affectedOutputVariables
+ val assignMiddle = APASplitToExpr(prog.case_splits, referedOutputVariables)
+ val assigns2 = ListOutputAssignmentToExpr(prog.output_assignment)
+ val output_expr = tupleWrap(expected_output_vars.map(ov => Variable(vc.getIdentifier(ov))))
+ assigns(assignMiddle(assigns2(output_expr)))
+ }
+
+ def conditionProgramToIfExpr(p: (APACondition, APAProgram), expected_output: List[OutputVar])(implicit vc: VarContext): Expr => Expr = {
+ val subprogram = APAProgramToExpr(p._2, expected_output)
+ (els: Expr) => IfExpr(APAConditionToExpr(p._1), subprogram, els)
+ }
+
+ def APASplitToExpr(p: APASplit, expected_output: List[OutputVar])(implicit vc: VarContext): Expr => Expr = p match {
+ case APAFalseSplit() => Let(FreshIdentifier("wrong", UnitType), NoTree(tupleTypeWrap(expected_output.map(_ => IntegerType))), _)
+ case APAEmptySplit() => e => e
+ case APACaseSplit(conditionPrograms) =>
+ val cps = conditionPrograms map (cp => conditionProgramToIfExpr(cp, expected_output))
+ if(cps.isEmpty) e => e
+ else {
+ val composition = cps.reduce(_ compose _)
+ (x: Expr) => letTuple(expected_output.map(vc.getIdentifier),
+ composition(Error(tupleTypeWrap(expected_output.map((ov: OutputVar) => IntegerType)), "Unreachable case")), x)
+ }
+ case APAForSplit(vl: List[InputVar], lower_range: APAInputTerm, upper_range: APAInputTerm, condition: APACondition, program: APAProgram) =>
+ import vc.sctx.program.library.{None => LeonNone, Some => LeonSome, List => LeonList, Cons => LeonCons, Nil => LeonNil, _}
+ val range = lookupUnique[FunDef]("leon.collection.List.range")
+ val flatMap = lookupUnique[FunDef]("leon.collection.List.flatMap")
+ val map = lookupUnique[FunDef]("leon.collection.List.map")
+ val find = lookupUnique[FunDef]("leon.collection.List.find")
+ val get = lookupUnique[FunDef]("leon.collection.Option.get")
+ val basic_range = functionInvocation(range, Seq(APAInputTermToExpr(lower_range), APAInputTermToExpr(upper_range)))
+ var ranges = basic_range
+ var tupleType: TypeTree = IntegerType
+ vl.tail foreach { k =>
+ val t = FreshIdentifier("t", IntegerType)
+ val i = FreshIdentifier("i", IntegerType)
+ ranges = functionInvocation(flatMap, Seq(ranges, Lambda(Seq(ValDef(t)),
+ functionInvocation(map, Seq(basic_range, Lambda(Seq(ValDef(i)), tupleWrap(Seq(Variable(i), Variable(t)))))))))
+ tupleType = TupleType(Seq(IntegerType, tupleType))
+ }
+ def getPattern(i: InputVar): Pattern = WildcardPattern(scala.Some(vc.getIdentifier(i)))
+ val tuplePattern = (vl.init :\ getPattern(vl.last)) {
+ case (v, p) => tuplePatternWrap(Seq(getPattern(v), p))
+ }
+
+ val evars = FreshIdentifier("evars", tupleType)
+ val findExpr = functionInvocation(find, Seq(ranges,
+ Lambda(Seq(ValDef(evars)),
+ MatchExpr(Variable(evars),
+ Seq(MatchCase(tuplePattern, scala.None, APAConditionToExpr(condition)))))
+ ))
+ val finalExpr = functionInvocation(get, Seq(findExpr))
+ (x: Expr) => letTuple(expected_output.map(vc.getIdentifier), finalExpr, x)
+ }
+
+ def APATermToExpr(e: APATerm)(implicit vc: VarContext): Expr = e match {
+ case APADivision(n, d) => Division(APACombinationToExpr(n), APAInputTermToExpr(d))
+ case APAMinimum(l) => l match {
+ case Nil => throw new Exception("Minimum of nothing")
+ case a::Nil => APATermToExpr(a)
+ case l =>
+ val minDef = vc.sctx.program.library.lookup("leon.math.min").collect{
+ case fd: FunDef => fd
+ }.filter { fd => fd.paramIds.head.getType == IntegerType }.head
+
+ (l.init :\ APATermToExpr(l.last)) { (v, res) =>
+ functionInvocation(minDef, Seq(APATermToExpr(v), res))
+ }
+ }
+ case APAMaximum(l) => l match {
+ case Nil => throw new Exception("Maximum of nothing")
+ case a::Nil => APATermToExpr(a)
+ case l =>
+ val maxDef = vc.sctx.program.library.lookup("leon.math.max").collect{
+ case fd: FunDef => fd
+ }.filter { fd => fd.paramIds.head.getType == IntegerType }.head
+
+ (l.init :\ APATermToExpr(l.last)) { (v, res) =>
+ functionInvocation(maxDef, Seq(APATermToExpr(v), res))
+ }
+ }
+ case a: APACombination =>
+ APACombinationToExpr(a)
+ }
+ def APAInputTermToExpr(t: APAInputTerm)(implicit vc: VarContext): Expr = t match {
+ case APAInputCombination(coef, input_linear) =>
+ ((InfiniteIntegerLiteral(BigInt(coef)): Expr) /: input_linear) { (c: Expr, iv) =>
+ val (i, v) = iv
+ val newTerm = if( i == BigInt(1) )
+ Variable(vc.getIdentifier(v))
+ else
+ Times(InfiniteIntegerLiteral(i), Variable(vc.getIdentifier(v)))
+ if( c == InfiniteIntegerLiteral(BigInt(0)))
+ newTerm
+ else
+ Plus(c, newTerm)
+ }
+ case APAInputDivision(numerator, denominator) =>
+ Division(APAInputTermToExpr(APAInputMultiplication(numerator)), APAInputTermToExpr(APAInputMultiplication(denominator)))
+ case APAInputMultiplication(operands) =>
+ val v = (operands map APAInputTermToExpr)
+ v match {
+ case Nil => InfiniteIntegerLiteral(1)
+ case a::Nil => a
+ case l => l.reduce(Times)
+ }
+ case APAInputAddition(operands) =>
+ val v = (operands map APAInputTermToExpr)
+ v match {
+ case Nil => InfiniteIntegerLiteral(0)
+ case a::Nil => a
+ case l => l.reduce(Plus)
+ }
+ case APAInputAbs(a) =>
+ val absFun = vc.sctx.program.library.lookupUnique[FunDef]("leon.math.abs")
+ functionInvocation(absFun, Seq(APAInputTermToExpr(a)))
+
+ case APAInputGCD(List(a)) =>
+ APAInputTermToExpr(APAInputAbs(a))
+
+ case APAInputGCD(numbers@List(a, b)) =>
+ import vc.sctx.program.library._
+ val gcdFun = lookupUnique[FunDef]("leon.math.gcd")
+ val args = numbers map APAInputTermToExpr
+ functionInvocation(gcdFun, args)
+
+ case APAInputGCD(numbers) =>
+ import vc.sctx.program.library._
+ val gcdFun = lookupUnique[FunDef]("leon.math.gcdlist")
+ val args = numbers map APAInputTermToExpr
+ val l = (args :\ (CaseClass(CaseClassType(Nil.get, Seq(IntegerType)), Seq()): Expr)) { (v, l) =>
+ CaseClass(CaseClassType(Cons.get, Seq(IntegerType)), Seq(v, l))
+ }
+ functionInvocation(gcdFun, Seq(l))
+
+ case APAInputLCM(List(a)) =>
+ APAInputTermToExpr(APAInputAbs(a))
+
+ case APAInputLCM(numbers@List(a, b)) =>
+ import vc.sctx.program.library._
+ val lcmFun = lookupUnique[FunDef]("leon.math.lcm")
+ val args = numbers map APAInputTermToExpr
+ functionInvocation(lcmFun, args)
+
+ case APAInputLCM(numbers) =>
+ import vc.sctx.program.library._
+ val lcmFun = lookupUnique[FunDef]("leon.math.lcmlist")
+ val args = numbers map APAInputTermToExpr
+ val l = (args :\ (CaseClass(CaseClassType(Nil.get, Seq(IntegerType)), Seq()): Expr)) { (v, l) =>
+ CaseClass(CaseClassType(Cons.get, Seq(IntegerType)), Seq(v, l))
+ }
+ functionInvocation(lcmFun, Seq(l))
+ }
+}
+
+case object IntegerEquality extends Rule("Solve integer equality"){
+
+ import ComfusyConverters._
+
+ object Flatten {
+ def unapply(e: Expr): Option[Expr] = e match {
+ case v: Variable => Some(v)
+ case Tuple(vs) => Some(Tuple(vs.map(e => Flatten.unapply(e).getOrElse(return None))))
+ case TupleSelect(Flatten(e), n) => Some(TupleSelect(e, n))
+ case Let(i, Flatten(e), Variable(i2)) if i2 == i => Some(e)
+ case Let(i, Flatten(e), Tuple(seq))
+ if seq.zipWithIndex.forall{
+ case (TupleSelect(Flatten(Variable(i2)), n), nm1) if nm1 + 1 == n && i2 == i => true case _ => false } =>
+ Some(e)
+ case LetTuple(is, Flatten(e), Tuple(vs)) if vs.toList == is.map(Variable) => Some(e)
+ case _ => None
+ }
+
+ }
+
+ object GetOutputVariables {
+ def unapply(e: Expr)(implicit p: Problem): Option[(Seq[Expr], Seq[Identifier])] =
+ rec(e, p.xs.map(Variable))
+
+ def rec(e: Expr, originOutputVariables: Seq[Expr]): Option[(Seq[Expr], Seq[Identifier])] = {
+ //println(s"Reducing $e under $originOutputVariables")
+ e} match {
+ case Let(i, Flatten(j), b) =>
+ val k = originOutputVariables.indexOf(j)
+ if(k == -1) { // Maybe j is a TupleSelect, in which case we need to expand the corresponding originOutputVariable
+ val varsOfJ = ExprOps.variablesOf(j)
+ val newOutputvariables= originOutputVariables.flatMap(ov =>
+ ov.getType match {
+ case t: TupleType if ExprOps.exists{ case Variable(m) if varsOfJ(m) => true case _ => false }(ov) =>
+ t.bases.indices.map(i => TupleSelect(ov, i + 1))
+ case _ => List(ov)
+ }
+ )
+ if(originOutputVariables != newOutputvariables) {
+ rec(e, newOutputvariables)
+ } else {
+ // Maybe j is a tuple of variable, in which case we are going to replace i._m by the corresponding variable.
+ j match {
+ case Tuple(values) if values.forall(_.isInstanceOf[Variable])=>
+ val newB =
+ ExprOps.preMap{
+ case TupleSelect(Variable(ii), n) if ii == i => Some(values(n - 1))
+ case _ => None
+ }(b)
+ if( b != newB ) {
+ rec(newB, originOutputVariables)
+ } else {
+ //println(s"Was not able 2 reduce the expression $e with input variables $originOutputVariables")
+ None
+ }
+ case _ =>
+ //println(s"Was not able to reduce the expression $e with input variables $originOutputVariables")
+ None
+ }
+
+ }
+ } else {
+ rec(b, originOutputVariables.take(k) ++ Seq(Variable(i)) ++ originOutputVariables.drop(k+1))
+ }
+ case LetTuple(is, Flatten(res), b) =>
+ val k = originOutputVariables.indexOf(res)
+ if (k >= 0) {
+ val newOriginOutputVariables =
+ originOutputVariables.take(k) ++ is.map(Variable) ++ originOutputVariables.drop(k+1)
+ rec(b, newOriginOutputVariables)
+ } else {
+ //println(s"Was not able to reduce $e under input variables $originOutputVariables")
+ None
+ }
+ // Now it's possible that j is a tuple select so let's try this
+ case TopLevelAnds(conjuncts) =>
+ val finalReturnVariables = originOutputVariables.map{
+ case Variable(x) => x
+ case e =>
+ //println(s"This is not an output variable: $e")
+ return None
+ }
+ Some((conjuncts, finalReturnVariables))
+ case _ =>
+ //println("Was not able to simplify the expression " + e)
+ None
+ }
+ }
+
+ def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = {
+ p.phi match {
+ case GetOutputVariables(conjuncts, outputVars) =>
+ implicit lazy val vc = new VarContext(p.as.toSet, outputVars.toSet, hctx)
+ val candidates: List[Equals] = conjuncts.toList collect {
+ case e: Equals => e
+ } filter { e => isLinEqInOutputVariables(e) }
+ //println(s"Candidates $conjuncts for $outputVars")
+ if(candidates.isEmpty) {
+ //println("No candidates for integer equality. Got " + conjuncts)
+ Nil
+ } else {
+ val realCandidates = candidates map (e => convertToAPAEqualZero(e)) sortWith APASynthesis.by_least_outputvar_coef
+ val problem = new APASynthesis(FormulaSplit(realCandidates, Nil, Stream.empty),
+ p.as.toList.map(vc.getInputVar), outputVars.toList.map(vc.getOutputVar))
+ List(RuleInstantiation("Solve integer equalities") {
+ //println(s"Solve problem ${realCandidates}")
+ val (cond, prog) = problem.solve()
+ //println(s"Solution condition = $cond under program = $prog")
+ val solution = convertToSolution(cond, prog)
+ //println(s"Rendered to $solution")
+ RuleClosed(solution)
+ })
+ }
+ case _ =>
+ //println("Not parsable as equation with conjuncts")
+ Nil
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/scala/leon/utils/Library.scala b/src/main/scala/leon/utils/Library.scala
index 3e9b99c88..fed2a6a48 100644
--- a/src/main/scala/leon/utils/Library.scala
+++ b/src/main/scala/leon/utils/Library.scala
@@ -31,6 +31,8 @@ case class Library(pgm: Program) {
lazy val setMkString = lookup("leon.lang.Set.mkString").collectFirst { case fd : FunDef => fd }
lazy val bagMkString = lookup("leon.lang.Bag.mkString").collectFirst { case fd : FunDef => fd }
+
+ lazy val bezoutWithBase = lookup("leon.math.bezoutWithBase").collectFirst { case fd : FunDef => fd }
def lookup(name: String): Seq[Definition] = {
pgm.lookupAll(name)