From 89ad6a65988d29bf08ffbb18a473094cf89077db Mon Sep 17 00:00:00 2001
From: Etienne Kneuss <ekneuss@gmail.com>
Date: Tue, 27 Nov 2012 18:33:55 +0100
Subject: [PATCH] Implement a Z3-4.3-friendly API to Solver and consequently to
 Z3 Solvers

Solvers now supports:
- pop(lvl)
- push
- check
- assertCnstr (assert would collapse with the usual assert())
- checkAssumptions
- getModel
- getUnsatCore

This API is supported naively by all non-z3 solvers
---
 src/main/scala/leon/solvers/Solver.scala      |  42 +++++++
 .../leon/solvers/z3/AbstractZ3Solver.scala    |  28 ++++-
 .../scala/leon/solvers/z3/FairZ3Solver.scala  | 118 ++++++++++++------
 3 files changed, 149 insertions(+), 39 deletions(-)

diff --git a/src/main/scala/leon/solvers/Solver.scala b/src/main/scala/leon/solvers/Solver.scala
index f6c546306..7afbff9b2 100644
--- a/src/main/scala/leon/solvers/Solver.scala
+++ b/src/main/scala/leon/solvers/Solver.scala
@@ -55,5 +55,47 @@ abstract class Solver(val context : LeonContext) {
   }
 
   protected def forceStop = _forceStop
+
+  // New Solver API
+  // Moslty for z3 solvers since z3 4.3
+  private var stack = List[List[Expr]](Nil)
+  def push() {
+    stack = Nil :: stack
+  }
+
+  def pop(lvl: Int = 1) {
+    stack = stack.drop(lvl)
+  }
+
+  def assertCnstr(expression: Expr) {
+    stack = (expression :: stack.head) :: stack.tail
+  }
+
+  private def allConstraints() = stack.flatten
+
+  def check: Option[Boolean] = {
+    solveSAT(And(allConstraints()))._1
+  }
+
+  private var unsatCore = Set[Expr]()
+
+  def checkAssumptions(assumptions: Seq[Expr]): Option[Boolean] = {
+    solveSAT(And(assumptions ++ allConstraints()))._1 match {
+      case Some(true) =>
+        unsatCore = Set[Expr]()
+        Some(true)
+      case r =>
+        unsatCore = assumptions.toSet
+        r
+    }
+  }
+
+  def getModel: Map[Identifier, Expr] = {
+    Map[Identifier, Expr]()
+  }
+
+  def getUnsatCore: Set[Expr] = {
+    unsatCore
+  }
 }
 
diff --git a/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala b/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala
index e0de253ce..aae1dcc84 100644
--- a/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala
+++ b/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala
@@ -24,7 +24,8 @@ trait AbstractZ3Solver {
 
   class CantTranslateException(t: Z3AST) extends Exception("Can't translate from Z3 tree: " + t)
 
-  protected[leon] var z3 : Z3Context = null
+  protected[leon] var z3 : Z3Context    = null
+  protected[leon] var solver : Z3Solver = null
   protected[leon] var program : Program = null
   protected[leon] val z3cfg : Z3Config
 
@@ -95,14 +96,35 @@ trait AbstractZ3Solver {
     }
   }
 
+  override def push() {
+    solver.push
+  }
+
+  override def pop(lvl: Int = 1) {
+    solver.pop(lvl)
+  }
+
+  override def assertCnstr(expression: Expr) {
+    solver.assertCnstr(toZ3Formula(expression).get)
+  }
+
+  override def check: Option[Boolean] = {
+    solver.check
+  }
+
+  override def checkAssumptions(assumptions: Seq[Expr]): Option[Boolean] = {
+    solver.checkAssumptions(assumptions.map(toZ3Formula(_).get) : _*)
+  }
+
   protected[leon] def restartZ3 : Unit = {
     counter = 0
     if (neverInitialized) {
       neverInitialized = false
+      z3 = new Z3Context(z3cfg)
+      solver = z3.mkSolver
     } else {
-      z3.delete
+      solver = z3.mkSolver
     }
-    z3 = new Z3Context(z3cfg)
 
     exprToZ3Id = Map.empty
     z3IdToExpr = Map.empty
diff --git a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
index a6125b193..4001d5345 100644
--- a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
+++ b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
@@ -114,7 +114,7 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
           // println("I'll assert now an axiom: " + axiom)
           // println("Case axiom:")
           // println(axiom)
-          z3.assertCnstr(axiom)
+          solver.assertCnstr(axiom)
         }
 
         if(fd.hasPostcondition) {
@@ -129,7 +129,7 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
           val postAxiom = z3.mkForAll(0, List(pattern), nameTypePairs, z3IzedPost)
           //println("Post axiom:")
           //println(postAxiom)
-          z3.assertCnstr(postAxiom)
+          solver.assertCnstr(postAxiom)
         }
 
         axiomatizedFunctions += fd
@@ -160,6 +160,19 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
     }
   }
 
+  override def getModel = {
+    modelToMap(solver.getModel, varsInVC)
+  }
+
+  override def getUnsatCore = {
+    solver.getUnsatCore.map(ast => fromZ3Formula(null, ast, None) match {
+      case n @ Not(Variable(_)) => n
+      case v @ Variable(_) => v
+      case x => scala.sys.error("Impossible element extracted from core: " + ast + " (as Leon tree : " + x + ")")
+    }).toSet
+  }
+
+
   override def solveSATWithCores(expression: Expr, assumptions: Set[Expr]): (Option[Boolean], Map[Identifier, Expr], Set[Expr]) = {
     restartZ3
     decideWithModel(expression, false, None, Some(assumptions))
@@ -224,7 +237,7 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
     val (clauses, guards) = unrollingBank.initialUnrolling(expandedVC)
 
     val cc = toZ3Formula(And(clauses)).get
-    z3.assertCnstr(cc)
+    solver.assertCnstr(cc)
 
     // these are the optional sequence of assumption literals
     val assumptionsAsZ3: Option[Seq[Z3AST]] = assumptions.map(set => set.toSeq.map(toZ3Formula(_).get))
@@ -242,26 +255,38 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
       // println("Blocking set : " + blockingSet)
       blocking2Z3Stopwatch.stop
 
-      z3.push
+      solver.push
       if(!blockingSetAsZ3.isEmpty)
-        z3.assertCnstr(z3.mkAnd(blockingSetAsZ3 : _*))
+        solver.assertCnstr(z3.mkAnd(blockingSetAsZ3 : _*))
 
       reporter.info(" - Running Z3 search...")
       val answerModelCore : (Option[Boolean], Z3Model, Set[Expr]) = {
         z3SearchStopwatch.start
-        val (a, m, c) = assumptionsAsZ3 match {
-          case Some(as) => z3.checkAssumptions(as: _*)
-          case None => val res = z3.checkAndGetModel(); (res._1, res._2, Seq.empty[Z3AST])
+
+        val res = assumptionsAsZ3 match {
+          case Some(as) => solver.checkAssumptions(as: _*)
+          case None     => solver.check()
+        }
+
+        val z3model = res match {
+          case Some(true) => solver.getModel
+          case _          => null
+        }
+
+        val z3core: Seq[Z3AST] = (res, assumptionsAsZ3) match {
+          case (Some(false), ass) if ass.isDefined => solver.getUnsatCore.toSeq
+          case _                                    => Seq()
         }
+
         z3SearchStopwatch.stop
 
-        val core: Set[Expr] = c.map(ast => fromZ3Formula(m, ast, Some(BooleanType)) match {
+        val core: Set[Expr] = z3core.map(ast => fromZ3Formula(z3model, ast, Some(BooleanType)) match {
           case n @ Not(Variable(_)) => n
           case v @ Variable(_) => v
           case x => scala.sys.error("Impossible element extracted from core: " + ast + " (as Leon tree : " + x + ")")
         }).toSet
 
-        (a, m, core)
+        (res, z3model, core)
       }
 
       val (answer, model, core) = answerModelCore // to work around the stupid type inferer
@@ -278,7 +303,8 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
 
       (reinterpretedAnswer, model) match {
         case (None, m) => { // UNKNOWN
-          reporter.warning("Z3 doesn't know because: " + z3.getSearchFailure.message)
+          // reporter.warning("Z3 doesn't know because: " + z3.getSearchFailure.message)
+          reporter.warning("Z3 doesn't know because ??")
           foundAnswer(None)
           m.delete
         }
@@ -305,7 +331,7 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
         }
         case (Some(false), m) if blockingSet.isEmpty => {
           foundAnswer(Some(true), core = core)
-          z3.pop(1)
+          solver.pop(1)
         }
         // This branch is both for with and without unsat cores. The
         // distinction is made inside.
@@ -313,31 +339,43 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
           //m.delete
 
           if(!Settings.useCores)
-            z3.pop(1)
+            solver.pop(1)
             
           if (Settings.luckyTest && !forceStop) {
             // we need the model to perform the additional test
             reporter.info(" - Running search without blocked literals (w/ lucky test)")
             secondZ3SearchStopwatch.start
-            val (result2,m2,c2) = assumptionsAsZ3 match {
-              case Some(as) => z3.checkAssumptions(as: _*)
-              case None => val res = z3.checkAndGetModel(); (res._1, res._2, Seq.empty[Z3AST])
+
+            val res2 = assumptionsAsZ3 match {
+              case Some(as) => solver.checkAssumptions(as: _*)
+              case None     => solver.check()
+            }
+
+            val z3model2 = res2 match {
+              case Some(true) => solver.getModel
+              case _          => null
             }
+
+            val z3core2: Seq[Z3AST] = (res2, assumptionsAsZ3) match {
+              case (Some(false), ass) if ass.isDefined => solver.getUnsatCore.toSeq
+              case _                                    => Seq()
+            }
+
             secondZ3SearchStopwatch.stop
             reporter.info(" - Finished search without blocked literals")
 
-            if (result2 == Some(false)) {
-              val core: Set[Expr] = c2.map(ast => fromZ3Formula(m, ast, Some(BooleanType)) match {
+            if (res2 == Some(false)) {
+              val core2: Set[Expr] = z3core2.map(ast => fromZ3Formula(z3model2, ast, Some(BooleanType)) match {
                 case n @ Not(Variable(_)) => n
                 case v @ Variable(_) => v
                 case x => scala.sys.error("Impossible element extracted from core: " + ast + " (as Leon tree : " + x + ")")
               }).toSet
 
-              foundAnswer(Some(true), core = core)
+              foundAnswer(Some(true), core = core2)
             } else {
               luckyStopwatch.start
               // we might have been lucky :D
-              val (wereWeLucky, cleanModel) = validateAndDeleteModel(m2, toCheckAgainstModels, varsInVC, evaluator)
+              val (wereWeLucky, cleanModel) = validateAndDeleteModel(z3model2, toCheckAgainstModels, varsInVC, evaluator)
               if(wereWeLucky) {
                 foundAnswer(Some(false), cleanModel)
               } 
@@ -348,13 +386,21 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
             reporter.info(" - Running search without blocked literals (w/o lucky test)")
             secondZ3SearchStopwatch.start
             val result2 = assumptionsAsZ3 match {
-              case Some(as) => val res = z3.checkAssumptions(as: _*); res._1
-              case None => z3.check()
+              case Some(as) => solver.checkAssumptions(as: _*)
+              case None     => solver.check()
             }
             secondZ3SearchStopwatch.stop
             reporter.info(" - Finished search without blocked literals")
 
             if (result2 == Some(false)) {
+              val z3model = solver.getModel()
+
+              val core: Set[Expr] = solver.getUnsatCore.map(ast => fromZ3Formula(z3model, ast, Some(BooleanType)) match {
+                case n @ Not(Variable(_)) => n
+                case v @ Variable(_) => v
+                case x => scala.sys.error("Impossible element extracted from core: " + ast + " (as Leon tree : " + x + ")")
+              }).toSet
+
               foundAnswer(Some(true), core = core)
             }
           }
@@ -362,7 +408,7 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
           if(forceStop) {
             foundAnswer(None)
           }
-            
+
           if(!foundDefinitiveAnswer) { 
             reporter.info("- We need to keep going.")
 
@@ -378,29 +424,29 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
             if(Settings.pruneBranches) {
               for(ex <- blockingSet) ex match {
                 case Not(Variable(b)) => {
-                  z3.push
-                  z3.assertCnstr(toZ3Formula(Variable(b)).get)
-                  z3.check match {
+                  solver.push
+                  solver.assertCnstr(toZ3Formula(Variable(b)).get)
+                  solver.check match {
                     case Some(false) => {
                       //println("We had ~" + b + " in the blocking set. We now know it should stay there forever.")
-                      z3.pop(1)
-                      z3.assertCnstr(toZ3Formula(Not(Variable(b))).get)
+                      solver.pop(1)
+                      solver.assertCnstr(toZ3Formula(Not(Variable(b))).get)
                       fixedForEver = fixedForEver + ex
                     }
-                    case _ => z3.pop(1)
+                    case _ => solver.pop(1)
                   }
                 }
                 case Variable(b) => {
-                  z3.push
-                  z3.assertCnstr(toZ3Formula(Not(Variable(b))).get)
-                  z3.check match {
+                  solver.push
+                  solver.assertCnstr(toZ3Formula(Not(Variable(b))).get)
+                  solver.check match {
                     case Some(false) => {
                       //println("We had " + b + " in the blocking set. We now know it should stay there forever.")
-                      z3.pop(1)
-                      z3.assertCnstr(toZ3Formula(Variable(b)).get)
+                      solver.pop(1)
+                      solver.assertCnstr(toZ3Formula(Variable(b)).get)
                       fixedForEver = fixedForEver + ex
                     }
-                    case _ => z3.pop(1)
+                    case _ => solver.pop(1)
                   }
                 }
                 case _ => scala.sys.error("Should not have happened.")
@@ -424,7 +470,7 @@ class FairZ3Solver(context : LeonContext) extends Solver(context) with AbstractZ
               val (newClauses,newBlockers) = unrollingBank.unlock(id, !polarity)
 
               for(ncl <- newClauses) {
-                z3.assertCnstr(toZ3Formula(ncl).get)
+                solver.assertCnstr(toZ3Formula(ncl).get)
               }
               blockingSet = blockingSet ++ Set(newBlockers.map(p => if(p._2) Not(Variable(p._1)) else Variable(p._1)) : _*)
             }
-- 
GitLab