diff --git a/src/integration/scala/leon/test/solvers/ModelEnumerationSuite.scala b/src/integration/scala/leon/test/solvers/ModelEnumerationSuite.scala new file mode 100644 index 0000000000000000000000000000000000000000..9d1b16019f563a42e86ba695a65bf75530ca12c9 --- /dev/null +++ b/src/integration/scala/leon/test/solvers/ModelEnumerationSuite.scala @@ -0,0 +1,152 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon.test.purescala + +import leon.test._ +import leon._ +import leon.solvers._ +import leon.purescala.Definitions._ +import leon.purescala.Common._ +import leon.evaluators._ +import leon.purescala.Expressions._ + +class ModelEnumeratorSuite extends LeonTestSuiteWithProgram with helpers.ExpressionsDSL { + val sources = List( + """|import leon.lang._ + |import leon.annotation._ + | + |object List1 { + | abstract class List + | case class Cons(h: BigInt, t: List) extends List + | case object Nil extends List + | + | def size(l: List): BigInt = { + | l match { + | case Cons(h, t) => BigInt(1) + size(t) + | case Nil => BigInt(0) + | } + | } ensuring { _ >= 0 } + | + | def sum(l: List): BigInt = l match { + | case Cons(h, t) => h + size(t) + | case Nil => 0 + | } + | + | def sumAndSize(l: List) = (size(l), sum(l)) + | + |} """.stripMargin + ) + + def getModelEnum(implicit ctx: LeonContext, pgm: Program) = { + new ModelEnumerator(ctx, pgm, SolverFactory.getFromSettings) + } + + test("Simple model enumeration 1") { implicit fix => + val tpe = classDef("List1.List").typed + val l = FreshIdentifier("l", tpe) + + val cnstr = GreaterThan(fcall("List1.size")(l.toVariable), bi(2)) + + val me = getModelEnum + try { + val models = me.enumSimple(Seq(l), cnstr).take(5).toList + + assert(models.size === 5, "We can enumerate at least 5 lists of size 3+") + assert(models.toSet.size === 5, "Models are distinct") + } finally { + me.shutdown() + } + } + + test("Simple model enumeration 2") { implicit fix => + val tpe = classDef("List1.List").typed + val l = FreshIdentifier("l", tpe) + + val cnstr = Equals(fcall("List1.size")(l.toVariable), bi(0)) + + val me = getModelEnum + + try { + val models = me.enumSimple(Seq(l), cnstr).take(5).toList + + assert(models.size === 1, "We can only enumerate one list of size 0") + } finally { + me.shutdown() + } + } + + test("Varying model enumeration 1") { implicit fix => + val tpe = classDef("List1.List").typed + val l = FreshIdentifier("l", tpe) + + // 0 < list.size < 3 + val cnstr = And(GreaterThan(fcall("List1.size")(l.toVariable), bi(0)), + LessThan(fcall("List1.size")(l.toVariable), bi(3))) + + val car = fcall("List1.size")(l.toVariable) + + val evaluator = new DefaultEvaluator(fix._1, fix._2) + val me = getModelEnum + + try { + // 1 model of each size + val models1 = me.enumVarying(Seq(l), cnstr, car).toList + assert(models1.size === 2, "We can enumerate 2 lists of varying size 0 < .. < 3") + + // 3 models of each size + val models2 = me.enumVarying(Seq(l), cnstr, car, 3).toList + assert(models2.size === 6, "We can enumerate 6 lists of varying size 0 < .. < 3 with 3 per size") + + + val car2 = fcall("List1.sum")(l.toVariable) + + // 1 model of each sum + val models3 = me.enumVarying(Seq(l), cnstr, car2).take(4).toList + assert(models3.size === 4, "We can enumerate >=4 lists of varying sum, with 0 < .. < 3") + + val carResults3 = models3.groupBy(m => evaluator.eval(car2, m).result.get) + assert(carResults3.size === 4, "We should have 4 distinct sums") + assert(carResults3.forall(_._2.size === 1), "We should have 1 model per sum") + + // 2 model of each sum + val models4 = me.enumVarying(Seq(l), cnstr, car2, 2).take(4).toList + assert(models4.size === 4, "We can enumerate >=4 lists of varying sum, with 0 < .. < 3") + + val carResults4 = models4.groupBy(m => evaluator.eval(car2, m).result.get) + assert(carResults4.size >= 2, "We should have at least 2 distinct sums") + assert(carResults4.forall(_._2.size <= 2), "We should have at most 2 models per sum") + + + } finally { + me.shutdown() + } + } + + test("Varying model enumeration 2") { implicit fix => + val tpe = classDef("List1.List").typed + val l = FreshIdentifier("l", tpe) + + // 0 < list.size < 3 + val cnstr = And(GreaterThan(fcall("List1.size")(l.toVariable), bi(0)), + LessThan(fcall("List1.size")(l.toVariable), bi(3))) + + val car = Equals(fcall("List1.size")(l.toVariable), bi(1)) + + val me = getModelEnum + + try { + // 1 model of each caracteristic (which is a boolean, so only two possibilities) + val models3 = me.enumVarying(Seq(l), cnstr, car).take(10).toList + assert(models3.size === 2, "We can enumerate only 2 lists of varying size==0") + + + // 1 model of each caracteristic (which is a boolean, so only two possibilities) + val models4 = me.enumVarying(Seq(l), cnstr, car, 2).take(10).toList + assert(models4.size === 4, "We can enumerate only 4 lists of varying size==0 (2 each)") + + } finally { + me.shutdown() + } + } + +} diff --git a/src/main/scala/leon/solvers/ModelEnumerator.scala b/src/main/scala/leon/solvers/ModelEnumerator.scala new file mode 100644 index 0000000000000000000000000000000000000000..b22dc921031c1067659a5bd91df3418d6c4461bb --- /dev/null +++ b/src/main/scala/leon/solvers/ModelEnumerator.scala @@ -0,0 +1,85 @@ +package leon +package solvers + +import purescala.Definitions._ +import purescala.Common._ +import purescala.Expressions._ +import purescala.Constructors._ +import purescala.ExprOps._ +import purescala.Types._ +import evaluators._ + + +class ModelEnumerator(ctx: LeonContext, pgm: Program, sf: SolverFactory[Solver]) { + private[this] var reclaimPool = List[Solver]() + private[this] val evaluator = new DefaultEvaluator(ctx, pgm) + + def enumSimple(ids: Seq[Identifier], cnstr: Expr): Iterator[Map[Identifier, Expr]] = { + enumVarying0(ids, cnstr, None, -1) + } + + /** + * Enumerate at most `nPerCaracteristic` models with the same value for + * `caracteristic`. + * + * Note: there is no guarantee that the models enumerated consecutively share the + * same `caracteristic`. + */ + def enumVarying(ids: Seq[Identifier], cnstr: Expr, caracteristic: Expr, nPerCaracteristic: Int = 1) = { + enumVarying0(ids, cnstr, Some(caracteristic), nPerCaracteristic) + } + + def enumVarying0(ids: Seq[Identifier], cnstr: Expr, caracteristic: Option[Expr], nPerCaracteristic: Int = 1): Iterator[Map[Identifier, Expr]] = { + val s = sf.getNewSolver + reclaimPool ::= s + + s.assertCnstr(cnstr) + + val c = caracteristic match { + case Some(car) => + val c = FreshIdentifier("car", car.getType) + s.assertCnstr(Equals(c.toVariable, car)) + c + case None => + FreshIdentifier("noop", BooleanType) + } + + var perCarRemaining = Map[Expr, Int]() + + new Iterator[Map[Identifier, Expr]] { + def hasNext = { + s.check == Some(true) + } + + def next = { + val sm = s.getModel + val m = (ids.map { id => + id -> sm.getOrElse(id, simplestValue(id.getType)) + }).toMap + + + // Vary the model + s.assertCnstr(not(andJoin(m.toSeq.sortBy(_._1).map { case (k,v) => equality(k.toVariable, v) }))) + + caracteristic match { + case Some(car) => + val cValue = evaluator.eval(car, m).result.get + + perCarRemaining += (cValue -> (perCarRemaining.getOrElse(cValue, nPerCaracteristic) - 1)) + if (perCarRemaining(cValue) == 0) { + s.assertCnstr(not(equality(c.toVariable, cValue))) + } + + case None => + } + + m + } + } + } + + def shutdown() = { + reclaimPool.foreach{sf.reclaim(_)} + } + +} diff --git a/src/main/scala/leon/solvers/SolverFactory.scala b/src/main/scala/leon/solvers/SolverFactory.scala index a43dc966456363f50f2b7c9c4b1d74bbbca8f738..90fc5411051a441b732d23dbf19987f70fe4632d 100644 --- a/src/main/scala/leon/solvers/SolverFactory.scala +++ b/src/main/scala/leon/solvers/SolverFactory.scala @@ -47,7 +47,7 @@ object SolverFactory { case (name, desc) => f"\n $name%-14s : $desc" }.mkString("") - def getFromSettings(ctx: LeonContext, program: Program): SolverFactory[TimeoutSolver] = { + def getFromSettings(implicit ctx: LeonContext, program: Program): SolverFactory[TimeoutSolver] = { val names = ctx.findOptionOrDefault(SharedOptions.optSelectedSolvers) if (((names contains "fairz3") || (names contains "unrollz3")) && !hasNativeZ3) {