From 2d3e4476c5fe71a4b17387dd68f8c772c52dcdc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Mayer?= Date: Mon, 4 Sep 2017 16:00:06 +0200 Subject: [PATCH] Disambiguation integrated. --- .../synthesis/stringsolver/ProgramSet.scala | 116 ++++++++++++- .../synthesis/stringsolver/StreamUtils.scala | 157 ++++++++++++++++++ .../synthesis/stringsolver/StringSolver.scala | 89 +++++++++- 3 files changed, 358 insertions(+), 4 deletions(-) create mode 100644 src/main/scala/ch/epfl/lara/synthesis/stringsolver/StreamUtils.scala diff --git a/src/main/scala/ch/epfl/lara/synthesis/stringsolver/ProgramSet.scala b/src/main/scala/ch/epfl/lara/synthesis/stringsolver/ProgramSet.scala index 075159b..8407926 100644 --- a/src/main/scala/ch/epfl/lara/synthesis/stringsolver/ProgramSet.scala +++ b/src/main/scala/ch/epfl/lara/synthesis/stringsolver/ProgramSet.scala @@ -62,8 +62,11 @@ object ProgramSet { def flatMap[T](f: A => GenTraversableOnce[T]) = map(f).flatten private var cacheBest: Option[Any] = None def takeBest: A = { if(cacheBest.isEmpty) cacheBest = Some(takeBestRaw); cacheBest.get.asInstanceOf[A]} + private var cacheNBest: Map[Int, Seq[(Int, Any)]] = Map() + def takeNBest(n: Int): Seq[(Int, A)] = { if(cacheNBest.isEmpty) cacheNBest += n-> takeNBestRaw(n: Int); cacheNBest(n).asInstanceOf[Seq[(Int, A)]]} //def takeBestUsing(w: Identifier): A = takeBest def takeBestRaw: A + def takeNBestRaw(n: Int): Seq[(Int, A)] override def isEmpty: Boolean = this == SEmpty || ProgramSet.sizePrograms(this) == 0 def sizePrograms = ProgramSet.sizePrograms(this) override def toIterable: Iterable[A] = map((i: A) =>i) @@ -71,6 +74,10 @@ object ProgramSet { var weightMalus = 0 def examplePosition = 0 // Set only in SDag } + + + def weighted[A <: Program](p: A): (Int, A) = (weight(p), p) + /** * Set of switch expressions described in Programs.scala */ @@ -82,6 +89,10 @@ object ProgramSet { for(t <- combinations(s map _2)) f(Switch(s map _1 zip t)) } def takeBestRaw = Switch((s map _1) zip ((s map _2) map (_.takeBest))) + def takeNBestRaw(n: Int) = StreamUtils.cartesianProduct(s.map(_2).map(_.takeNBestRaw(n).toStream)).map{ x=> + val (scores, progs) = x.unzip + (scores.sum, Switch(s map _1 zip progs)) + }.sortBy(_1).take(n) //override def takeBestUsing(w: Identifier) = Switch((s map _1) zip ((s map _2) map (_.takeBestUsing(w)))) } /** @@ -114,7 +125,6 @@ object ProgramSet { } def takeBestRaw = { var minProg = Map[Node, List[AtomicExpr]]() - var weights = Map[Node, Int]() var nodesToVisit = new PriorityQueue[(Int, List[AtomicExpr], Node)]()(Ordering.by[(Int, List[AtomicExpr], Node), Int](e => e._1)) nodesToVisit.enqueue((0, Nil, ns)) while(!(minProg contains nt) && !nodesToVisit.isEmpty) { @@ -134,6 +144,43 @@ object ProgramSet { } Concatenate(minProg(nt))//TODO : alternative. } + + def Nneighbors(quantity: Int, n: Node, prev_weight: Int): Option[(Seq[(Int, AtomicExpr)], Node)] = { + ξ.collectFirst[(Node, Node)]{ case e@(start, end) if start == n => e } map { e => + val versions = W.getOrElse(e, Set.empty) + val possibilities = for(atomic <- versions.flatMap(_.takeNBest(quantity)).toList.sortBy(_1).take(quantity)) yield { + (-atomic._1 + prev_weight, atomic._2) + } + (possibilities, e._2) + } + } + def takeNBestRaw(quantity: Int): Seq[(Int, TraceExpr)] = { + var minProg = Map[Node, Seq[(Int, List[AtomicExpr])]]() + var nodesToVisit = new PriorityQueue[((Int, List[AtomicExpr]), Node)]()(Ordering.by[((Int, List[AtomicExpr]), Node), Int](e => e._1._1)) + nodesToVisit.enqueue(((0, Nil), ns)) + while(!(minProg.getOrElse(nt, Nil).length >= quantity) && !nodesToVisit.isEmpty) { + val ((weight, path), node) = nodesToVisit.dequeue() // Takes the first node with the minimal path. + minProg += node -> (((weight, path)) +: minProg.getOrElse(node, Nil)) + for(e@(newWeightsAtomics, newNode) <- Nneighbors(quantity, node, weight)) { +// (newweight, newAtomic + for((newweight, newAtomic) <- newWeightsAtomics) + { + val alreadyLookingFor = nodesToVisit.toStream.filter{ case ((w, p), n) => n == newNode } + //val shouldBeAdded = alreadyLookingFor.lengthCompare(quantity) < 0 || newweight > alreadyLookingFor(quantity - 1)._1._1 + //if(shouldBeAdded) { // We keep only the best quantity. + nodesToVisit.enqueue(((newweight, path ++ List[AtomicExpr](newAtomic)), newNode)) + var i = 0 + nodesToVisit = nodesToVisit.filterNot { + i += 1 + _._2 == newNode && i >= quantity // We still keep the first quantity best, we remove the rest. + } + //} + + } + } + } + minProg(nt).map(x => (x._1, Concatenate(x._2))) + } /*def neighborsUsing(n: Node, n_weight: Int, w: Identifier): Set[(Int, AtomicExpr, Node)] = { for(e <- ξ if e._1 == n; versions = W.getOrElse(e, Set.empty); @@ -210,10 +257,11 @@ object ProgramSet { for(prog <- e) f(Loop(i, prog, separator)) } def takeBestRaw = Loop(i, e.takeBest, separator)//.withAlternative(this.toIterable) + override def takeNBestRaw(n: Int): Seq[(Int, AtomicExpr)] = { + e.takeNBest(n).map(x => weighted(Loop(i, x._2, separator))) + } } - - /** * Set of SubStr expressions described in Programs.scala */ @@ -227,6 +275,15 @@ object ProgramSet { def takeBestRaw = SubStr(vi, p1.toList.map(_.takeBest).sortBy(weight(_)(true)).head, p2.toList.map(_.takeBest).sortBy(weight(_)(false)).head.withWeightMalus(this.weightMalus), methods.takeBest)//.withAlternative(this.toIterable) private var corresponding_string: (String, String, Int, Int) = ("", "", 0, -1) def setPos(from: String, s: String, start: Int, end: Int) = corresponding_string = (from, s, start, end) + + override def takeNBestRaw(n: Int): Seq[(Int, AtomicExpr)] = { + val left = p1.flatMap(_.takeNBest(n)).toSeq.sortBy(_._1).take(n) + val right = p2.flatMap(_.takeNBest(n)).toSeq.sortBy(_._1).take(n) + val method = methods.takeNBest(n) + StreamUtils.cartesianProduct(Seq(left.toStream, right.toStream, method.toStream)).take(n).map{ + case Seq((leftScore, l), (rightScore, r), (mScore, m)) => weighted(SubStr(vi, l.asInstanceOf[Position], r.asInstanceOf[Position], m.asInstanceOf[SubStrFlag])) + } take n + } } def isCommonSeparator(s: String) = s match { @@ -247,6 +304,14 @@ object ProgramSet { def takeBestRaw = SpecialConversion(s.takeBest.asInstanceOf[SubStr], converters.toList.sortBy(weight(_)(true)).head) private var corresponding_string: (String, String, Int, Int) = ("", "", 0, -1) def setPos(from: String, s: String, start: Int, end: Int) = corresponding_string = (from, s, start, end) + + override def takeNBestRaw(n: Int): Seq[(Int, AtomicExpr)] = { + val sbest = s.takeNBest(n).toStream + val converterBest = converters.toList.map(x => (-weight(x)(true), x)).sortBy(_1).take(n).toStream + StreamUtils.cartesianProduct(Seq(sbest, converterBest)).take(n).map { + case Seq((sScore, s), (convScore, converter)) => weighted(SpecialConversion(s.asInstanceOf[SubStr], converter.asInstanceOf[SpecialConverter])) + } + } } @@ -273,6 +338,12 @@ object ProgramSet { } def takeBestRaw = if(step == 0) IntLiteral(start) else IntLiteral(start+step*((max-start)/step)) def apply(elem: Int): Boolean = elem >= start && elem <= max && (step == 0 && start == elem || step != 0 && (elem-start)%step == 0) + + override def takeNBestRaw(n: Int): Seq[(Int, IntLiteral)] = { + if(step == 0) Seq((0, IntLiteral(start))) else { + (start to max by step).reverse.zipWithIndex.map{ x => weighted(IntLiteral(x._1))} take n + } + } } /*case class SAnyInt(default: Int) extends SInt { def map[T](f: IntLiteral => T): Stream[T] = { @@ -305,6 +376,11 @@ object ProgramSet { for(pp1: AtomicExpr <- a; l <- length) f(NumberMap(pp1.asInstanceOf[SubStr], l.k, offset)) } def takeBestRaw = NumberMap(a.takeBest.asInstanceOf[SubStr], length.takeBest.k, offset)//.withAlternative(this.toIterable) + override def takeNBestRaw(n: Int): Seq[(Int, AtomicExpr)] = { + StreamUtils.cartesianProduct(Seq(a.takeNBest(n).toStream, length.takeNBest(n).toStream)).take(n) map { + case Seq((aScore, a), (lengthScore, length)) => weighted(NumberMap(a.asInstanceOf[SubStr], length.asInstanceOf[IntLiteral].k, offset)) + } + } } /** @@ -340,6 +416,12 @@ object ProgramSet { for(l <- length.toStream; s: IntLiteral <- starts; step <- if(count == 0) Stream.from(1) else List((index - s.k)/count)) f(Counter(l.k, s.k, step)) } def takeBestRaw = Counter(length.takeBest.k, starts.takeBest.k, if(count == 0) 1 else (index - starts.takeBest.k)/count)//.withAlternative(this.toIterable) + override def takeNBestRaw(n: Int): Seq[(Int, AtomicExpr)] = { + StreamUtils.cartesianProduct(Seq(length.takeNBest(n).toStream, starts.takeNBest(n).toStream)).take(n) map { + case Seq((lengthScore, length), (startsScore, start)) => + weighted(Counter(length.k, start.k, if(count == 0) 1 else (index - start.k)/count)) + } + } } /** @@ -353,6 +435,8 @@ object ProgramSet { f(ConstStr(s)) } def takeBestRaw = ConstStr(s) + + override def takeNBestRaw(n: Int): Seq[(Int, AtomicExpr)] = Seq(weighted(takeBest)) } type SPosition = ProgramSet[Position] @@ -367,6 +451,8 @@ object ProgramSet { f(CPos(k)) } def takeBestRaw = CPos(k) + + override def takeNBestRaw(n: Int): Seq[(Int, Position)] = Seq(weighted(takeBest)) } /** * Set of regexp positions described in Programs.scala @@ -380,6 +466,14 @@ object ProgramSet { } def takeBestRaw = Pos(r1.takeBest, r2.takeBest, c.toList.sortBy(weight).head) //var index = 0 // Index at which this position was computed + override def takeNBestRaw(n: Int): Seq[(Int, Position)] = { + StreamUtils.cartesianProduct(Seq( + r1.takeNBest(n).toStream, + r2.takeNBest(n).toStream, + c.toList.sortBy(weight).toStream)) map { + case Seq((_, rr1), (_, rr2), cc: IntLiteral) => weighted(Pos(rr1.asInstanceOf[RegExp], rr2.asInstanceOf[RegExp], cc.k)) + } + } } type SRegExp = ProgramSet[RegExp] @@ -395,6 +489,12 @@ object ProgramSet { for(t <- combinations(s)) f(TokenSeq(t)) } def takeBestRaw = TokenSeq(s map (_.takeBest)) + + override def takeNBestRaw(n: Int): Seq[(Int, RegExp)] = { + StreamUtils.cartesianProduct(s.map(_.takeNBest(n).toStream)).take(n) map { + x => weighted(TokenSeq(x.map(_2))) + } + } } /** @@ -407,6 +507,8 @@ object ProgramSet { def iterator = Nil.toIterator override def toIterable = Nil override def isEmpty = true + + override def takeNBestRaw(n: Int): Seq[(Int, Nothing)] = Seq() } type SIntegerExpr = Set[IntegerExpr] @@ -466,6 +568,10 @@ object ProgramSet { def takeBestRaw = map((i: Token) => i).toList.sortBy(weight).head def contains(t: Token): Boolean = ((1L << l.indexOf(t)) & mask) != 0 override def toString = "SToken("+this.toList.mkString(",")+")" + + override def takeNBestRaw(n: Int): Seq[(Int, Token)] = { + map((i: Token) => i).toList.sortBy(weight).take(n).map(weighted) + } } /** @@ -501,6 +607,10 @@ object ProgramSet { override def isEmpty = mask == 0 def takeBestRaw = map((i: SubStrFlag) => i).toList.sortBy(weight).head override def toString = "SSubStrFlag("+this.toList.mkString(",")+")" + + override def takeNBestRaw(n: Int): Seq[(Int, SubStrFlag)] = { + map((i: SubStrFlag) => i).toList.sortBy(weight).take(n).map(weighted) + } } diff --git a/src/main/scala/ch/epfl/lara/synthesis/stringsolver/StreamUtils.scala b/src/main/scala/ch/epfl/lara/synthesis/stringsolver/StreamUtils.scala new file mode 100644 index 0000000..84bc00c --- /dev/null +++ b/src/main/scala/ch/epfl/lara/synthesis/stringsolver/StreamUtils.scala @@ -0,0 +1,157 @@ +package ch.epfl.lara.synthesis.stringsolver + +// Taken from github.com/epfl-lara/inox +object StreamUtils { + /** Interleaves a stream of streams, such that eventually all elements will be enumerated. */ + def interleaves[A](streams: Stream[Stream[A]]): Stream[A] = { + def rec(streams: Stream[Stream[A]], diag: Int): Stream[A] = { + if (streams.isEmpty) Stream() else { + val (take, leave) = streams.splitAt(diag) + val (nonEmpty, empty) = take partition (_.nonEmpty) + nonEmpty.map(_.head) #::: rec(nonEmpty.map(_.tail) ++ leave, diag + 1 - empty.size) + } + } + + rec(streams, 1) + } + + /** Applies the interleaving to a finite sequence of streams. */ + def interleaves[A](streams: Seq[Stream[A]]): Stream[A] = { + val nonEmptyStreams = streams.toStream.filter(_.nonEmpty) + if (nonEmptyStreams.nonEmpty) { + nonEmptyStreams.map(_.head) #::: interleaves(nonEmptyStreams.map(_.tail)) + } else Stream.empty + } + + /** Returns the unique number matching the pair (x, y) in cantor bijection */ + private def cantorPair(x: Int, y: Int): Int = { + val xpy = x + y + y + xpy * (xpy + 1) / 2 + } + + /** Returns the pair of numbers corresponding to the given number in cantor bijection. + * cantorPair.tupled(reverseCantorPair(z)) = z + * reverseCantorPair(cantorPair.tupled(t)) = t + * */ + def reverseCantorPair(z: Int): (Int, Int) = { + import Math._ + val t = floor((sqrt(8f * z + 1f) - 1) / 2f).toInt + val ttp1o2 = t * (t + 1) / 2 + (ttp1o2 + t - z, z - ttp1o2) + } + + /** Combines two streams into one using cantor's unpairing function. + * Ensures that the stream terminates if both streams terminate */ + def cartesianProduct[A, B](sa: Stream[A], sb: Stream[B]): Stream[(A, B)] = { + def rec(sa: Stream[A], sb: Stream[B])(i: Int): Stream[(A, B)] = { + val (x, y) = reverseCantorPair(i) + if (!sa.isDefinedAt(x) && !sb.isDefinedAt(y)) Stream.Empty + else if (sa.isDefinedAt(x) && sb.isDefinedAt(y)) (sa(x), sb(y)) #:: rec(sa, sb)(i + 1) + else rec(sa, sb)(i + 1) + } + + rec(sa, sb)(0) + } + + def cartesianProduct[A](streams: Seq[Stream[A]]): Stream[List[A]] = { + val dimensions = streams.size + val vectorizedStreams = streams.map(new VectorizedStream(_)) + + if (dimensions == 0) + return Stream.cons(Nil, Stream.empty) + + if (streams.exists(_.isEmpty)) + return Stream.empty + + val indices = diagCount(dimensions) + + var allReached: Boolean = false + val bounds: Array[Option[Int]] = for (s <- streams.toArray) yield { + if (s.hasDefiniteSize) { + Some(s.size) + } else { + None + } + } + + indices.takeWhile(_ => !allReached).flatMap { indexList => + var d = 0 + var continue = true + var is = indexList + var ss = vectorizedStreams.toList + + if ((indexList zip bounds).forall { + case (i, Some(b)) => i >= b + case _ => false + }) { + allReached = true + } + + var tuple: List[A] = Nil + + while (continue && d < dimensions) { + val i = is.head + if (bounds(d).exists(i > _)) { + continue = false + } else try { + // TODO can we speed up by caching the random access into + // the stream in an indexedSeq? After all, `i` increases + // slowly. + tuple = ss.head(i) :: tuple + is = is.tail + ss = ss.tail + d += 1 + } catch { + case e: IndexOutOfBoundsException => + bounds(d) = Some(i - 1) + continue = false + } + } + if (continue) Some(tuple.reverse) else None + } + } + + private def diagCount(dim: Int): Stream[List[Int]] = diag0(dim, 0) + + private def diag0(dim: Int, nextSum: Int): Stream[List[Int]] = summingTo(nextSum, dim).append(diag0(dim, nextSum + 1)) + + private def summingTo(sum: Int, n: Int): Stream[List[Int]] = { + // assert(sum >= 0) + if (sum < 0) { + Stream.empty + } else if (n == 1) { + Stream.cons(sum :: Nil, Stream.empty) + } else { + (0 to sum).toStream.flatMap(fst => summingTo(sum - fst, n - 1).map(fst :: _)) + } + } + + private class VectorizedStream[A](initial: Stream[A]) { + private def mkException(i: Int) = new IndexOutOfBoundsException("Can't access VectorizedStream at : " + i) + + private def streamHeadIndex: Int = indexed.size + + private var stream: Stream[A] = initial + private var indexed: Vector[A] = Vector.empty + + def apply(index: Int): A = { + if (index < streamHeadIndex) { + indexed(index) + } else { + val diff = index - streamHeadIndex // diff >= 0 + var i = 0 + while (i < diff) { + if (stream.isEmpty) throw mkException(index) + indexed = indexed :+ stream.head + stream = stream.tail + i += 1 + } + // The trick is *not* to read past the desired element. Leave it in the + // stream, or it will force the *following* one... + stream.headOption.getOrElse { + throw mkException(index) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/scala/ch/epfl/lara/synthesis/stringsolver/StringSolver.scala b/src/main/scala/ch/epfl/lara/synthesis/stringsolver/StringSolver.scala index c428a2d..cf374be 100644 --- a/src/main/scala/ch/epfl/lara/synthesis/stringsolver/StringSolver.scala +++ b/src/main/scala/ch/epfl/lara/synthesis/stringsolver/StringSolver.scala @@ -21,8 +21,8 @@ import scala.concurrent._ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ import scala.util.matching.Regex - import ProgramSet._ +import org.apache.commons.lang.StringEscapeUtils /** * An extension delivers start and end position to which it applies, @@ -43,6 +43,9 @@ NEW Triggers learning of a new program. Automatic first time. ("input", index) ==> "output" The variable TRANSFORM will contain the resulting program +After one or multiple TRANSFORM examples, you can invoke disambiguate(input1, input2...) to check for ambiguities. +For example: "MikaelM" ==> "MM"; disambiguate("MarionM", "LeonP") + Reduce (List("input1", ...), index) ==> "output" List("input1", ...) ==> "output" @@ -138,6 +141,56 @@ HELP Displays this help private var _currentFilterProgram: FilterProgram = null private var partitionExamples = List[PartitionExample]() + private def _disambiguate(programs: Seq[Program], inputs: Seq[String]): Unit = { + if(programs.isEmpty) return; + if(currentSolver.isVerbose) println("Found several programs to disambiguate, the first one is :") + if(currentSolver.isVerbose) println(programs.head) + object Ambiguous { + def unapply(i: (String, Int)): Option[Stream[(String, String)]] = { + val originalOutput = try { programs.head.apply(i._1, i._2) } catch { case e: Exception => "" } + val alternativeOutputs = programs.tail.toStream.flatMap{ + (p: Program) => + //println(s"Evaluating $p on $i") + try { + Some((p, p(i._1, i._2))) + } catch { + case e: Exception => None + } + } + val ambiguities = alternativeOutputs.filter(pp => pp._2 != originalOutput) + if(ambiguities.isEmpty) None else { + var seen: Set[String] = Set(originalOutput) + Some((i._1, originalOutput) #:: (ambiguities flatMap { + case (prog, output) if(!seen(output)) => + seen += output + Some((i._1, output)) + case _ => None + })) + } + } + } + inputs.zipWithIndex.collectFirst { case input@Ambiguous(originalAlternative) => + println("Ambiguity found. What is the right output? First is current") + originalAlternative.foreach { + case (original, alternative) => println("\"" + StringEscapeUtils.escapeJava(original) + "\" ==> \"" + StringEscapeUtils.escapeJava(alternative) + "\"") + } + }.getOrElse(println("No ambiguity found. Try to increase currentSolver.numBestPrograms = 10")) + } + + + def disambiguate(inputs: String*): Unit = disambiguate(inputs.toList) + def disambiguate(inputs: List[String]): Unit = { + currentType match { + case ALL => println("No example given. Type DOC to get the documentation") + case MAPTYPE => _currentSolver.solveNBest() match { + case Some(programs) => _disambiguate(programs, inputs) + case None => + println("No map/reduce program found. To cancel the last example, please type CANCEL. To reset, call NEW") + } + case _ => println("Disambiguation currently works only for TRANSFORM") + } + } + def solve(): Unit = currentType match { case ALL => println("No example given. Type DOC to get the documentation") case MAPTYPE => _currentSolver.solve() match { @@ -556,6 +609,7 @@ class StringSolver { private var ff = new StringSolverAlgorithms() private var currentPrograms: IndexedSeq[STraceExpr] = null private var singlePrograms = ArrayBuffer[IndexedSeq[STraceExpr]]() + private var multiPrograms = ArrayBuffer[IndexedSeq[STraceExpr]]() //private var previousOutputs: IndexedSeq[String] = Array[String]() private var inputList = List[List[String]]() @@ -566,6 +620,8 @@ class StringSolver { private var extra_time_to_resolve = 2f private var index_number = 0 + + var numBestPrograms = 5 def copy(): StringSolver= { // TODO: better copy method val d = new StringSolver() @@ -932,6 +988,8 @@ class StringSolver { def solveLasts(): List[Option[Program]] = for(i <- (0 until currentPrograms.length).toList) yield solveLast(i) def solve(): Option[Program] = solve(0) + + def solveNBest(): Option[Seq[Program]] = solveNBest(0) /** Returns the best solution to the problem for the whole output */ def solve(nth: Int): Option[Program] = if(currentPrograms != null) try { @@ -951,6 +1009,25 @@ class StringSolver { } None } else None + + /** Returns the best solution to the problem for the whole output */ + def solveNBest(nth: Int): Option[Seq[Program]] = if(currentPrograms != null) try { + val res = Some(currentPrograms(nth).takeNBest(numBestPrograms)) + if(debugActive) verifyCurrentState() + res.map(_.map(_._2)) + } catch { + case e: java.lang.Error => + if(isVerbose) { + println(e.getMessage()) + println(e.getStackTrace().mkString("\n")) + } + None + case e: Exception => if(isVerbose) { + println(e.getMessage()) + println(e.getStackTrace().mkString("\n")) + } + None + } else None /** Returns the best solution to the problem for the whole output */ def solveLast(nth: Int = 0): Option[Program] = if(singlePrograms != null) try { @@ -961,6 +1038,16 @@ class StringSolver { case _: java.lang.Error => None case _: Exception => None } else None + + /** Returns the best solution to the problem for the whole output */ + def solveNBestLast(nth: Int = 0): Option[Seq[Program]] = if(singlePrograms != null) try { + val res = Some(singlePrograms(singlePrograms.length - 1)(nth).takeNBest(numBestPrograms)) + if(debugActive) verifyCurrentState() + res.map(_.map(_._2)) + } catch { + case _: java.lang.Error => None + case _: Exception => None + } else None def takeBest[T <: Program](s: ProgramSet[T]): Program = s.takeBest