diff --git a/src/main/scala/leon/purescala/Extractors.scala b/src/main/scala/leon/purescala/Extractors.scala index 392c7af337b8901a7c2378f248b1053c1c797a75..0b1fa4acd9e67a111cd72e413430d0957ecd1e77 100644 --- a/src/main/scala/leon/purescala/Extractors.scala +++ b/src/main/scala/leon/purescala/Extractors.scala @@ -74,6 +74,18 @@ object Extractors { case Concat(t1,t2) => Some((t1,t2,Concat)) case ListAt(t1,t2) => Some((t1,t2,ListAt)) case Let(binders, e, body) => Some((e, body, (e: Expr, b: Expr) => Let(binders, e, body))) + case LetDef(fd, body) => + fd.body match { + case Some(b) => + Some((b, body, (b: Expr, body: Expr) => { + val nfd = new FunDef(fd.id, fd.returnType, fd.args) + nfd.body = Some(b) + + LetDef(nfd, body) + })) + case _ => + None + } case LetTuple(binders, e, body) => Some((e, body, (e: Expr, b: Expr) => LetTuple(binders, e, body))) case wh@While(t1, t2) => Some((t1,t2, (t1, t2) => While(t1, t2).setInvariant(wh.invariant).setPosInfo(wh))) case ex: BinaryExtractable => ex.extract diff --git a/src/main/scala/leon/synthesis/Complexity.scala b/src/main/scala/leon/synthesis/Complexity.scala index 8106beed5d6ba3ae23e1253ba99f82b02e04d647..cea557ca3b729cd31e10ac747a12481b3ac67ca2 100644 --- a/src/main/scala/leon/synthesis/Complexity.scala +++ b/src/main/scala/leon/synthesis/Complexity.scala @@ -10,26 +10,23 @@ abstract class Complexity[T <: Complexity[T]] extends Ordered[T] { def value : Int } -case class TaskComplexity(t: Task) extends Complexity[TaskComplexity] { - def value= { - Option(t.rule) match { - case Some(r) => - 100*t.problem.complexity.value + (100-r.priority) + t.minSolutionCost - case None => - 0 - } - } +abstract class SolutionComplexity extends Complexity[SolutionComplexity] { + def value: Int } -case class SolutionComplexity(s: Solution) extends Complexity[SolutionComplexity] { +case class ConcreteSolutionComplexity(s: Solution) extends SolutionComplexity { lazy val value = { val chooses = collectChooses(s.term) - val chooseCost = 1000 * chooses.foldLeft(0)((i, c) => i + (math.pow(2, c.vars.size).toInt + formulaSize(c.pred))) + val chooseCost = chooses.foldLeft(0)((i, c) => i + (1000 * math.pow(2, c.vars.size).toInt + formulaSize(c.pred))) formulaSize(s.pre) + formulaSize(s.term) + chooseCost } } +case class FixedSolutionComplexity(c: Int) extends SolutionComplexity { + val value = c +} + case class ProblemComplexity(p: Problem) extends Complexity[ProblemComplexity] { lazy val value = { math.pow(2, p.xs.size).toInt + formulaSize(p.phi) diff --git a/src/main/scala/leon/synthesis/Heuristics.scala b/src/main/scala/leon/synthesis/Heuristics.scala index f96ecacbfe88a6949eae7135c9742924fc56212c..0e39e3f9cbf2c47c56addddca760a62d7fc6bcce 100644 --- a/src/main/scala/leon/synthesis/Heuristics.scala +++ b/src/main/scala/leon/synthesis/Heuristics.scala @@ -72,7 +72,7 @@ class OptimisticGround(synth: Synthesizer) extends Rule("Optimistic Ground", syn } -class IntInduction(synth: Synthesizer) extends Rule("Int Induction", synth, 8, 100) { +class IntInduction(synth: Synthesizer) extends Rule("Int Induction", synth, 8, 500) { def applyOn(task: Task): RuleResult = { val p = task.problem @@ -81,7 +81,7 @@ class IntInduction(synth: Synthesizer) extends Rule("Int Induction", synth, 8, 1 val tpe = TupleType(p.xs.map(_.getType)) val inductOn = FreshIdentifier(origId.name, true).setType(origId.getType) - + val postXs = p.xs map (id => FreshIdentifier("r", true).setType(id.getType)) val postXsMap = (p.xs zip postXs).toMap.mapValues(Variable(_)) @@ -94,7 +94,7 @@ class IntInduction(synth: Synthesizer) extends Rule("Int Induction", synth, 8, 1 val subGT = Problem(inductOn :: postXs, And(Seq(newPhi, GreaterThan(Variable(inductOn), IntLiteral(0)), postCondGT)), p.xs) val subLT = Problem(inductOn :: postXs, And(Seq(newPhi, LessThan(Variable(inductOn), IntLiteral(0)), postCondLT)), p.xs) - val onSuccess: List[Solution] => Solution = { + val onSuccess: List[Solution] => Solution = { case List(base, gt, lt) => val newFun = new FunDef(FreshIdentifier("rec", true), tpe, Seq(VarDecl(inductOn, inductOn.getType))) newFun.body = Some( diff --git a/src/main/scala/leon/synthesis/Rules.scala b/src/main/scala/leon/synthesis/Rules.scala index 51e1c02eabbb60055d14fa778eed6ad67d2000bb..213793663c43bb02e881c6f849a2349dd527ceda 100644 --- a/src/main/scala/leon/synthesis/Rules.scala +++ b/src/main/scala/leon/synthesis/Rules.scala @@ -41,7 +41,7 @@ abstract class Rule(val name: String, val synth: Synthesizer, val priority: Prio override def toString = name } -class OnePoint(synth: Synthesizer) extends Rule("One-point", synth, 30, 0) { +class OnePoint(synth: Synthesizer) extends Rule("One-point", synth, 30, 5) { def applyOn(task: Task): RuleResult = { val p = task.problem @@ -102,7 +102,7 @@ class Ground(synth: Synthesizer) extends Rule("Ground", synth, 50, 0) { } } -class CaseSplit(synth: Synthesizer) extends Rule("Case-Split", synth, 20, 0) { +class CaseSplit(synth: Synthesizer) extends Rule("Case-Split", synth, 20, 10) { def applyOn(task: Task): RuleResult = { val p = task.problem p.phi match { @@ -122,7 +122,7 @@ class CaseSplit(synth: Synthesizer) extends Rule("Case-Split", synth, 20, 0) { } } -class Assert(synth: Synthesizer) extends Rule("Assert", synth, 20, 0) { +class Assert(synth: Synthesizer) extends Rule("Assert", synth, 20, 1) { def applyOn(task: Task): RuleResult = { val p = task.problem @@ -154,7 +154,7 @@ class Assert(synth: Synthesizer) extends Rule("Assert", synth, 20, 0) { } } -class UnusedInput(synth: Synthesizer) extends Rule("UnusedInput", synth, 10, 0) { +class UnusedInput(synth: Synthesizer) extends Rule("UnusedInput", synth, 10, 1) { def applyOn(task: Task): RuleResult = { val p = task.problem val unused = p.as.toSet -- variablesOf(p.phi) @@ -169,7 +169,7 @@ class UnusedInput(synth: Synthesizer) extends Rule("UnusedInput", synth, 10, 0) } } -class UnconstrainedOutput(synth: Synthesizer) extends Rule("Unconstr.Output", synth, 10, 0) { +class UnconstrainedOutput(synth: Synthesizer) extends Rule("Unconstr.Output", synth, 10, 5) { def applyOn(task: Task): RuleResult = { val p = task.problem val unconstr = p.xs.toSet -- variablesOf(p.phi) @@ -193,7 +193,7 @@ class UnconstrainedOutput(synth: Synthesizer) extends Rule("Unconstr.Output", sy } object Unification { - class DecompTrivialClash(synth: Synthesizer) extends Rule("Unif Dec./Clash/Triv.", synth, 20, 0) { + class DecompTrivialClash(synth: Synthesizer) extends Rule("Unif Dec./Clash/Triv.", synth, 20, 5) { def applyOn(task: Task): RuleResult = { val p = task.problem @@ -248,7 +248,7 @@ object Unification { } -class ADTDual(synth: Synthesizer) extends Rule("ADTDual", synth, 20, 0) { +class ADTDual(synth: Synthesizer) extends Rule("ADTDual", synth, 20, 10) { def applyOn(task: Task): RuleResult = { val p = task.problem diff --git a/src/main/scala/leon/synthesis/Solution.scala b/src/main/scala/leon/synthesis/Solution.scala index 3fcf16eba23ab474d2d1f308e8a69b0553b09227..573ff7c56abfeab733c45c96583052e4e3270b80 100644 --- a/src/main/scala/leon/synthesis/Solution.scala +++ b/src/main/scala/leon/synthesis/Solution.scala @@ -9,7 +9,7 @@ import leon.purescala.TreeOps.simplifyLets class Solution(val pre: Expr, val term: Expr) { override def toString = "⟨ "+pre+" | "+term+" ⟩" - lazy val complexity: SolutionComplexity = new SolutionComplexity(this) + lazy val complexity: SolutionComplexity = new ConcreteSolutionComplexity(this) def toExpr = { if (pre == BooleanLiteral(true)) { diff --git a/src/main/scala/leon/synthesis/SynthesisPhase.scala b/src/main/scala/leon/synthesis/SynthesisPhase.scala index 7adfb5aef6d21fd473cc1565ce7814578084c336..f51749d8203232f8613cdae00423531241d86d3d 100644 --- a/src/main/scala/leon/synthesis/SynthesisPhase.scala +++ b/src/main/scala/leon/synthesis/SynthesisPhase.scala @@ -17,15 +17,16 @@ object SynthesisPhase extends LeonPhase[Program, Program] { LeonFlagOptionDef( "inplace", "--inplace", "Debug level"), LeonFlagOptionDef( "derivtrees", "--derivtrees", "Generate derivation trees"), LeonFlagOptionDef( "firstonly", "--firstonly", "Stop as soon as one synthesis solution is found"), + LeonValueOptionDef("timeout", "--timeout=T", "Timeout after T seconds when searching for synthesis solutions .."), LeonValueOptionDef("functions", "--functions=f1:f2", "Limit synthesis of choose found within f1,f2,..") ) def run(ctx: LeonContext)(p: Program): Program = { - val reporter = new SilentReporter - val solvers = List( - new TrivialSolver(reporter), - new FairZ3Solver(reporter) - ) + val reporter = new QuietReporter + + val mainSolver = new FairZ3Solver(reporter) + mainSolver.setProgram(p) + val uninterpretedZ3 = new UninterpretedZ3Solver(reporter) uninterpretedZ3.setProgram(p) @@ -33,12 +34,19 @@ object SynthesisPhase extends LeonPhase[Program, Program] { var genTrees = false var firstOnly = false var filterFun: Option[Seq[String]] = None + var timeoutMs: Option[Long] = None for(opt <- ctx.options) opt match { case LeonFlagOption("inplace") => inPlace = true case LeonValueOption("functions", ListValue(fs)) => filterFun = Some(fs) + case LeonValueOption("timeout", t) => + try { + timeoutMs = Some(t.toLong) + } catch { + case _: Throwable => + } case LeonFlagOption("firstonly") => firstOnly = true case LeonFlagOption("derivtrees") => @@ -46,10 +54,9 @@ object SynthesisPhase extends LeonPhase[Program, Program] { case _ => } - val synth = new Synthesizer(ctx.reporter, solvers, genTrees, filterFun.map(_.toSet), firstOnly) + val synth = new Synthesizer(ctx.reporter, mainSolver, genTrees, filterFun.map(_.toSet), firstOnly, timeoutMs) val solutions = synth.synthesizeAll(p) - // Simplify expressions val simplifiers = List[Expr => Expr]( simplifyTautologies(uninterpretedZ3)(_), diff --git a/src/main/scala/leon/synthesis/Synthesizer.scala b/src/main/scala/leon/synthesis/Synthesizer.scala index 0739733ea0cbdefbcd0769e8e693fc5a454575e3..895ec71c44dea208d6b82f1fb818b951c748a29e 100644 --- a/src/main/scala/leon/synthesis/Synthesizer.scala +++ b/src/main/scala/leon/synthesis/Synthesizer.scala @@ -13,10 +13,11 @@ import java.io.File import collection.mutable.PriorityQueue class Synthesizer(val r: Reporter, - val solvers: List[Solver], + solver: Solver, generateDerivationTrees: Boolean, filterFuns: Option[Set[String]], - firstOnly: Boolean) { + firstOnly: Boolean, + timeoutMs: Option[Long]) { import r.{error,warning,info,fatalError} @@ -28,27 +29,36 @@ class Synthesizer(val r: Reporter, val workList = new PriorityQueue[Task]() val rootTask = new RootTask(this, p) - val giveUpComplexity = new TaskComplexity(null) { - override def value = 100000 - } - workList += rootTask + val ts = System.currentTimeMillis + def timeoutExpired(): Boolean = { + timeoutMs match { + case Some(t) if (System.currentTimeMillis-ts)/1000 > t => true + case _ => false + } + } + + val worstSolution = Solution.choose(p) + def bestSolutionSoFar(): Solution= rootTask.solution.getOrElse(worstSolution) + while (!workList.isEmpty && !(firstOnly && rootTask.solution.isDefined)) { val task = workList.dequeue() - if (task.complexity > giveUpComplexity) { - // This task is too complicated, we give up with a choose solution - val solution = Solution.choose(task.problem) - task.solution = Some(solution) - task.parent.partlySolvedBy(task, solution) - } else { + // Check if solving this task has the slightest chance of improving the + // current solution + if (task.minSolutionComplexity < bestSolutionSoFar().complexity) { val subProblems = task.run for (p <- subProblems; r <- rules) yield { workList += new Task(this, task, p, r) } } + + if (timeoutExpired()) { + warning("Timeout reached") + workList.clear() + } } if (generateDerivationTrees) { @@ -57,30 +67,25 @@ class Synthesizer(val r: Reporter, derivationCounter += 1 } - rootTask.solution.getOrElse(Solution.none) + bestSolutionSoFar() } def solveSAT(phi: Expr): (Option[Boolean], Map[Identifier, Expr]) = { - for (s <- solvers) { - s.solveOrGetCounterexample(Not(phi)) match { - case (Some(true), _) => - return (Some(false), Map()) - case (Some(false), model) => - return (Some(true), model) - case _ => - } + solver.solveOrGetCounterexample(Not(phi)) match { + case (Some(true), _) => + (Some(false), Map()) + case (Some(false), model) => + (Some(true), model) + case (None, _) => + println("WOOPS: failure on "+phi) + (None, Map()) } - (None, Map()) } val rules = Rules.all(this) ++ Heuristics.all(this) import purescala.Trees._ def synthesizeAll(program: Program): List[(Choose, Solution)] = { - - solvers.foreach(_.setProgram(program)) - - def noop(u:Expr, u2: Expr) = u var solutions = List[(Choose, Solution)]() diff --git a/src/main/scala/leon/synthesis/Task.scala b/src/main/scala/leon/synthesis/Task.scala index c1c5bdb694b6de1a3e78792b2ddeb5e3a01c4684..4db32fd42102b16880e5ea13fd15c7620bde00c1 100644 --- a/src/main/scala/leon/synthesis/Task.scala +++ b/src/main/scala/leon/synthesis/Task.scala @@ -6,9 +6,15 @@ class Task(synth: Synthesizer, val problem: Problem, val rule: Rule) extends Ordered[Task] { - val complexity: TaskComplexity = new TaskComplexity(this) + def compare(that: Task) = { + val cproblem = -(this.problem.complexity compare that.problem.complexity) // problem DESC - def compare(that: Task) = that.complexity compare this.complexity // sort by complexity ASC + if (cproblem == 0) { + this.rule.priority-that.rule.priority + } else { + cproblem + } + } var subProblems: List[Problem] = Nil var onSuccess: List[Solution] => Solution = _ @@ -64,7 +70,7 @@ class Task(synth: Synthesizer, } } - lazy val minSolutionCost: Cost = rule.cost + parent.minSolutionCost + lazy val minSolutionComplexity: FixedSolutionComplexity = new FixedSolutionComplexity(rule.cost+parent.minSolutionComplexity.value) override def toString = "Applying "+rule+" on "+problem } @@ -72,7 +78,7 @@ class Task(synth: Synthesizer, class RootTask(synth: Synthesizer, problem: Problem) extends Task(synth, null, problem, null) { var solver: Option[Task] = None - override lazy val minSolutionCost = 0 + override lazy val minSolutionComplexity = new FixedSolutionComplexity(0) override def partlySolvedBy(t: Task, s: Solution) { if (isBetterSolutionThan(s, solution)) {