Commit b410e6bf authored by Christian Müller's avatar Christian Müller

castdown

parent cd075ba9
import com.microsoft.z3.Context;
import com.microsoft.z3.Quantifier;
import com.microsoft.z3.Sort;
import com.microsoft.z3.Symbol;
import java.util.HashMap;
public class BrokenDowncastMWE {
public static void main(String[] args) {
var cfg = new HashMap<String, String>();
var ctx = new Context(cfg);
var simp = ctx.mkTactic("tseitin-cnf");
var goal = ctx.mkGoal(false, false, false);
var names = new Symbol[1];
names[0] = ctx.mkSymbol("x");
var sorts = new Sort[1];
sorts[0] = ctx.mkUninterpretedSort("T");
var fundecl = ctx.mkFuncDecl("fun", sorts, ctx.getBoolSort());
var boundvar = ctx.mkBound(0, sorts[0]);
var application = fundecl.apply(boundvar);
var expr = ctx.mkForall(sorts, names, application, 0, null, null, null, null);
goal.add(expr);
var ar = simp.apply(goal);
var sub = ar.getSubgoals();
var resultexpr = sub[0].AsBoolExpr();
System.out.println(expr);
System.out.println(expr.isQuantifier());
System.out.println(expr.getClass().getName());
System.out.println(resultexpr);
System.out.println(resultexpr.isQuantifier());
System.out.println(resultexpr.getClass().getName());
((Quantifier) resultexpr).getBody();
}
}
......@@ -98,7 +98,7 @@ object Utils extends LazyLogging {
s"""Name: $name
Description: $desc
Invariant:
${inv.pretty().lines.map(s => " " + s).mkString("\n")}
${inv.pretty.lines.map(s => " " + s).mkString("\n")}
Model: $model
Result: ${if (!res) "not " else ""}inductive
WF size: $wfsize
......
......@@ -6,7 +6,7 @@ import de.tum.workflows.foltl.FOLTL._
import de.tum.workflows.blocks.Workflow._
import com.typesafe.scalalogging.LazyLogging
object WorkflowParser extends RegexParsers with LazyLogging {
object WorkflowParser extends RegexParsers with PackratParsers with LazyLogging {
def addChoicePredicates(blocks: List[Block]) = {
var i = -1; // start at 0
......@@ -125,17 +125,22 @@ object WorkflowParser extends RegexParsers with LazyLogging {
def ff = "False" ^^^ False
def AND = "(" ~> (repsep(TERM, "∧") | repsep(TERM, "&&")) <~ ")" ^^ (ts => And.make(ts))
def OR = "(" ~> (repsep(TERM, "∨") | repsep(TERM, "||")) <~ ")" ^^ (ts => Or.make(ts))
def NEG = ("¬" | "!") ~ SIMPLETERM ^^ { case _ ~ t => Neg(t) }
def NEG = ("¬" | "!") ~ SIMPLETERM ^^ { case _ ~ t => Neg(t) }
def IMPLIES = "(" ~> TERM ~ "⟹" ~ (TERM <~ ")") ^^ { case t1 ~ _ ~ t2 => Implies(t1, t2) }
// temporals
def GLOBALLY = ("G" | "☐") ~> TERM ^^ (t => Globally(t))
def FINALLY = ("F" | "♢") ~> TERM ^^ (t => Finally(t))
def TUPLE = "(" ~> repsep(TYPEDVAR, ",") <~ ")"
def FUN = VAR ~ TUPLE ^^ { case f ~ tup => Fun(f.name, tup) }
def SIMPLETERM = (tt | ff | FUN )
def TERM:Parser[Formula] = (SIMPLETERM | GLOBALLY | FINALLY | AND | OR | NEG )
def EXISTS = "∃" ~> (repsep(TYPEDVAR, ",") <~ ("."?)) ~ TERM ^^ { case vs ~ t => Exists(vs, t) }
def FORALL = "∀" ~> (repsep(TYPEDVAR, ",") <~ ("."?)) ~ TERM ^^ { case vs ~ t => Forall(vs, t) }
def QUANTIFIER = EXISTS | FORALL
def SIMPLETERM = (tt | ff | FUN)
lazy val TERM:PackratParser[Formula] = (AND | OR | IMPLIES | NEG | SIMPLETERM | GLOBALLY | FINALLY | QUANTIFIER)
def ADD = TERM ~ ("→" | "->") ~ VAR ~ "+=" ~ TUPLE ^^ { case guard ~ _ ~ fun ~ _ ~ tup => Add(guard, fun.name, tup) }
def REM = TERM ~ ("→" | "->") ~ VAR ~ "-=" ~ TUPLE ^^ { case guard ~ _ ~ fun ~ _ ~ tup => Remove(guard, fun.name, tup) }
......
......@@ -35,9 +35,15 @@ object FOLTL {
case _ => this.toString()
}
// single arrow is for maps
def -->(f2: Formula) = Implies(this, f2)
def (f2: Formula) = Implies(this, f2)
def land(f2: Formula) = And(this, f2)
def /\(f2: Formula) = And(this, f2)
def (f2: Formula) = And(this, f2)
def lor(f2: Formula) = And(this, f2)
def (f2: Formula) = Or(this, f2)
def \/(f2: Formula) = Or(this, f2)
// TODO: better line breaking
lazy val pretty: String = {
......@@ -276,7 +282,14 @@ object FOLTL {
val opname = "→"
val make = Implies.apply
}
object Implies {
def make(terms: List[Formula]) = {
BinOp.makeL(Implies.apply, terms, True)
}
def make(terms: Formula*) = {
BinOp.makeL(Implies.apply, terms, True)
}
}
case class Neg(t: Formula) extends UnOp {
val make = Neg.apply
val opname = "¬"
......
......@@ -178,7 +178,7 @@ object InvariantChecker extends LazyLogging {
msg ++= s"Possibly unsafe: Block may not uphold invariant:\n\n${e.label}\n\n"
if (res == Status.SATISFIABLE) {
msg ++= "Satisfying model:\n"
msg ++= Z3LTL.printModel(solver.getModel()).lines.map(" " + _).mkString("\n")
msg ++= Z3FOEncoding.printModel(solver.getModel()).lines.map(" " + _).mkString("\n")
} else if (res == Status.UNKNOWN) {
msg ++= s"Z3 result: $res (${solver.getReasonUnknown()})\n"
}
......
package de.tum.workflows.toz3
import java.util
import com.microsoft.z3.{BoolExpr, Context, Expr, FuncDecl, Model, Sort, Symbol}
import com.typesafe.scalalogging.LazyLogging
import de.tum.workflows.foltl.FOLTL._
object Z3FOEncoding extends LazyLogging {
val TIMEOUT = 60000 // in milliseconds
def translate(f: Formula, ctx: Context) = {
// logger.info(s"Using formula:\n$f")
// TODO: allow constants
if (f.freeVars.nonEmpty) {
logger.error(s"Cannot check - formula contains free variables ${f.freeVars}")
}
fun_ctx.clear()
val expr = toBoolZ3(ctx, f, Map())
// logger.info(s"Built Z3 expression:\n$expr")
expr
}
def translate(f: Formula, ctx: Context, varctx: VarCtx) = {
// logger.info(s"Using formula:\n${f.pretty()}")
if (f.freeVars.nonEmpty) {
logger.error(s"Cannot check - formula contains free variables ${f.freeVars}")
}
fun_ctx.clear()
val expr = toBoolZ3(ctx, f, varctx)
// logger.info(s"Built Z3 expression:\n$expr")
expr
}
def toBoolZ3(ctx: Context, f: Formula, var_ctx: VarCtx): BoolExpr = {
f match {
case Fun(name, ind, params) => {
val pi = if (ind.isDefined) "_" + ind.get else ""
val indexedname = name + pi
var fdecl = fun_ctx.get(indexedname)
if (fdecl == null) {
val pi = if (ind.isDefined) "_" + ind.get else ""
val sorts = params.map(x => var_ctx(x)._3)
fdecl = ctx.mkFuncDecl(indexedname, sorts.toArray, ctx.getBoolSort())
fun_ctx.put(indexedname, fdecl)
}
// all variables should be quantified, so they should be part of var_ctx
// TODO: support constants/free variables
val all_args = params.map(x => var_ctx(x)._2)
fdecl.apply(all_args: _*).asInstanceOf[BoolExpr]
}
case Exists(vars, f1) => {
val names: Array[Symbol] = vars.map(v => ctx.mkSymbol(v.name)).toArray
val newctx = buildVarCtx(ctx, var_ctx, vars)
val sorts = vars.map(newctx(_)._3).toArray
val e1 = toBoolZ3(ctx, f1, newctx)
ctx.mkExists(sorts, names, e1, 0, null, null, null, null)
}
case Forall(vars, f1) => {
val names: Array[Symbol] = vars.map(v => ctx.mkSymbol(v.name)).toArray
val newctx = buildVarCtx(ctx, var_ctx, vars)
val sorts = vars.map(newctx(_)._3).toArray
val e1 = toBoolZ3(ctx, f1, newctx)
ctx.mkForall(sorts, names, e1, 0, null, null, null, null)
}
case And(f1, f2) => {
val e1 = toBoolZ3(ctx, f1, var_ctx)
val e2 = toBoolZ3(ctx, f2, var_ctx)
ctx.mkAnd(e1, e2)
}
case Eq(f1, f2) => {
val e1 = toSortedZ3(ctx, f1, var_ctx)
val e2 = toSortedZ3(ctx, f2, var_ctx)
ctx.mkEq(e1, e2)
}
case Or(f1, f2) => {
val e1 = toBoolZ3(ctx, f1, var_ctx)
val e2 = toBoolZ3(ctx, f2, var_ctx)
ctx.mkOr(e1, e2)
}
case Implies(f1, f2) => {
val e1 = toBoolZ3(ctx, f1, var_ctx)
val e2 = toBoolZ3(ctx, f2, var_ctx)
ctx.mkImplies(e1, e2)
}
case Neg(f1) => {
val e = toBoolZ3(ctx, f1, var_ctx)
ctx.mkNot(e)
}
case True => {
ctx.mkTrue()
}
case False => {
ctx.mkFalse()
}
}
}
def toSortedZ3(ctx: Context, f: Formula, var_ctx: VarCtx): Expr = {
f match {
case v:Var => {
var_ctx(v)._2
}
case _ => {
toBoolZ3(ctx, f, var_ctx)
}
}
}
type VarCtx = Map[Var, (Option[Int], Expr, Sort)]
// TODO: how to not make this static?
val fun_ctx = new util.HashMap[String, FuncDecl]()
def buildVarCtx(ctx: Context, var_ctx: VarCtx, vars: List[Var]): VarCtx = {
val indices = (vars.size - 1) to 0 by -1
val newexprs = (for ((v, i) <- vars.zip(indices)) yield {
// count = count - 1;
val sort = if (v.typ.equals("Int")) {
ctx.getIntSort()
} else if (v.typ.equals("Bool")) {
ctx.getBoolSort()
} else {
ctx.mkUninterpretedSort(v.typ) // TODO: finite domain? sort size?
}
v -> (Some(i), ctx.mkBound(i, sort), sort)
}) toMap
// if the index is defined, increment, otherwise use the expr (which is a constant f.e.)
val oldvars = for ((v, (i, e, s)) <- var_ctx) yield {
if (i.isDefined) {
val newi = i.get + vars.size
val newbound = ctx.mkBound(newi, s)
v -> (Some(newi), newbound, s)
} else {
v -> (i, e, s)
}
}
newexprs ++ oldvars
}
// def mapback(e: Expr) = {
// mapback(e)(Nil);
// }
def mapbackToVar(e:Expr)(implicit bindings:List[com.microsoft.z3.Symbol]): Var = {
e match {
case f2 if f2.isVar() => {
Var(bindings(e.getIndex).toString, f2.getSort.getName.toString)
}
case f2 if f2.isConst() => {
Var("c", f2.getSort.getName.toString)
}
}
}
def mapback(e: Expr)(implicit bindings:List[com.microsoft.z3.Symbol] = Nil): Formula = {
e match {
case f2 if f2.isAnd() => And.make(f2.getArgs.map(mapback).toList)
case f2 if f2.isOr() => Or.make(f2.getArgs.map(mapback).toList)
case f2 if f2.isImplies() => Implies.make(f2.getArgs.map(mapback).toList)
case f2 if f2.isEq() => Eq.make(f2.getArgs.map(mapback).toList)
case f2 if f2.isNot() => Neg(mapback(f2.getArgs().head))
case f2 if f2.isTrue() => True
case f2 if f2.isFalse() => False
case f2 if f2.isQuantifier() => {
val q = f2.asInstanceOf[com.microsoft.z3.Quantifier]
val varnames = q.getBoundVariableNames
val vartypes = q.getBoundVariableSorts
val vars = varnames.zip(vartypes).map({
case (name, typ) => Var(name.toString, typ.getName.toString)
}).toList
val inner = mapback(q.getBody)(q.getBoundVariableNames.toList ++ bindings)
if (q.isExistential) {
Exists(vars, inner)
} else {
Forall(vars, inner)
}
}
case f2 if f2.isApp() => {
val name = f2.getFuncDecl().getName.toString
val params = f2.getArgs().map(mapbackToVar).toList
Fun(name, params)
}
case f2 if f2.isVar() => {
mapbackToVar(f2)
}
case f2 if f2.isConst() => {
mapbackToVar(f2)
}
case x => {
logger.error(s"Error mapping expression back from Z3: Could not parse $x")
Var("unknown")
}
}
}
def printModel(model: Model) = {
val sb = new StringBuilder()
val consts = model.getConstDecls()
val vals = consts.map(_.apply())
val typedvals = vals.groupBy(_.getSort)
sb ++= "Universe:\n"
for ((k, v) <- typedvals) {
sb ++= s"Type $k: ${v.mkString(",")}\n"
}
sb ++= "Relations:\n"
val sortedConsts = model.getConstDecls().sortBy(_.getName.toString())
val (l1, l2) = sortedConsts.partition(s => {
s.getName.toString() match {
case FunFromVar(_) => true
case _ => false
}
})
val funs = l1.map(s => {
val interp = model.getConstInterp(s)
(FunFromVar.unapply(s.getName.toString).get, interp.toString)
}) toList
val grouped = funs.groupBy(f => (f._1.name, f._1.ind))
// sort by name, path
val entries = grouped.iterator.toList.sortBy(_._1)
for (g <- entries) {
sb ++= g._1._1 + "(" + g._1._2 + "):\n"// name
// sort by value, variables
val tuples = g._2 sortBy(e => (e._2, e._1.params.mkString(",") ))
for ((fun, v) <- tuples) {
sb ++= fun.params.mkString("(",",",")") + " = " + v + "\n"
}
sb ++= "\n"
}
sb ++= "\nNon-Relations:\n"
// Rest of the consts
for (f <- l2) {
val interp = model.getConstInterp(f)
val name = f.getName.toString match {
case FunFromVar(fun) => fun
case _ => f.getName.toString
}
sb ++= f.getName + " = " + interp.toString() + "\n"
}
val sortedFuns = model.getFuncDecls().sortBy(_.getName.toString())
for (f <- sortedFuns) {
sb ++= f.getName + f.getDomain.mkString("(",", ",")") + "\n"
val interp = model.getFuncInterp(f)
val entries = interp.getEntries
for (e <- entries) {
val args = for (arg <- e.getArgs.toList) yield {
arg.getSort + " " + arg
}
sb ++= f.getName + args.mkString("(", ", ", ")") + " = " + e.getValue() + "\n"
}
val emptyargs = List.fill(f.getArity)("_")
sb ++= f.getName + emptyargs.mkString("(", ", ", ")") + " = " + interp.getElse() + "\n\n"
}
sb.toString()
}
}
......@@ -253,80 +253,4 @@ object Z3LTL extends LazyLogging {
expr
}
def printModel(model: Model) = {
val sb = new StringBuilder()
// val consts = model.getConstDecls()
//
// val vals = consts.map(_.apply())
// val typedvals = vals.groupBy(_.getSort)
//
// sb ++= "Universe:\n"
// for ((k, v) <- typedvals) {
// sb ++= s"Type $k: ${v.mkString(",")}\n"
// }
sb ++= "Relations:\n"
val sortedConsts = model.getConstDecls().sortBy(_.getName.toString())
val (l1, l2) = sortedConsts.partition(s => {
s.getName.toString() match {
case FunFromVar(_) => true
case _ => false
}
})
val funs = l1.map(s => {
val interp = model.getConstInterp(s)
(FunFromVar.unapply(s.getName.toString).get, interp.toString)
}) toList
val grouped = funs.groupBy(f => (f._1.name, f._1.ind))
// sort by name, path
val entries = grouped.iterator.toList.sortBy(_._1)
for (g <- entries) {
sb ++= g._1._1 + "(" + g._1._2 + "):\n"// name
// sort by value, variables
val tuples = g._2 sortBy(e => (e._2, e._1.params.mkString(",") ))
for ((fun, v) <- tuples) {
sb ++= fun.params.mkString("(",",",")") + " = " + v + "\n"
}
sb ++= "\n"
}
sb ++= "\nNon-Relations:\n"
// Rest of the consts
for (f <- l2) {
val interp = model.getConstInterp(f)
val name = f.getName.toString match {
case FunFromVar(fun) => fun
case _ => f.getName.toString
}
sb ++= f.getName + " = " + interp.toString() + "\n"
}
val sortedFuns = model.getFuncDecls().sortBy(_.getName.toString())
for (f <- sortedFuns) {
sb ++= f.getName + f.getDomain.mkString("(",", ",")") + "\n"
val interp = model.getFuncInterp(f)
val entries = interp.getEntries
for (e <- entries) {
val args = for (arg <- e.getArgs.toList) yield {
arg.getSort + " " + arg
}
sb ++= f.getName + args.mkString("(", ", ", ")") + " = " + e.getValue() + "\n"
}
val emptyargs = List.fill(f.getArity)("_")
sb ++= f.getName + emptyargs.mkString("(", ", ", ")") + " = " + interp.getElse() + "\n\n"
}
sb.toString()
}
}
......@@ -14,9 +14,6 @@ import de.tum.workflows.foltl.FormulaFunctions
object Z3QFree extends LazyLogging {
val TIMEOUT = 60000 // in milliseconds
/**
* Computes an interpolant using Z3
*
......@@ -29,28 +26,31 @@ object Z3QFree extends LazyLogging {
val (time, res) = Utils.time {
// Set up Z3
val cfg = new util.HashMap[String, String]()
cfg.put("timeout", TIMEOUT.toString())
cfg.put("timeout", Z3FOEncoding.TIMEOUT.toString())
// cfg.put("unsat_core", "true")
val ctx = InterpolationContext.mkContext()
// val ctx = InterpolationContext.mkContext()
val ctx = new Context(cfg)
val params = ctx.mkParams()
val f1s = FOTransformers.eliminatePredicates(f1)
val f2s = FOTransformers.eliminatePredicates(f2)
val f1i = ctx.MkInterpolant(Z3QFree.translate(f1s, ctx))
val f2i = ctx.MkInterpolant(Z3QFree.translate(f2s, ctx))
val itp = ctx.ComputeInterpolant(ctx.mkAnd(f1i, f2i), params)
if (itp.status == Z3_lbool.Z3_L_FALSE) {
logger.debug("Unsat, interpolating")
Some(mapback(itp.interp.head))
} else {
logger.info("Sat instead")
logger.info(Z3LTL.printModel(itp.model))
None
}
// FIXME: Where have the interpolants gone?
// val f1i = ctx.MkInterpolant(Z3QFree.translate(f1s, ctx))
// val f2i = ctx.MkInterpolant(Z3QFree.translate(f2s, ctx))
// val itp = ctx.ComputeInterpolant(ctx.mkAnd(f1i, f2i), params)
// if (itp.status == Z3_lbool.Z3_L_FALSE) {
// logger.debug("Unsat, interpolating")
// Some(mapback(itp.interp.head))
// } else {
// logger.info("Sat instead")
// logger.info(Z3FOEncoding.printModel(itp.model))
// None
// }
None
}
if (time > 1000) {
logger.debug(s"Z3 call took $time ms")
......@@ -63,7 +63,7 @@ object Z3QFree extends LazyLogging {
val (time, res) = Utils.time {
// Set up Z3
val cfg = new util.HashMap[String, String]()
cfg.put("timeout", TIMEOUT.toString())
cfg.put("timeout", Z3FOEncoding.TIMEOUT.toString())
val ctx = new Context(cfg)
val s = ctx.mkSolver()
......@@ -103,7 +103,7 @@ object Z3QFree extends LazyLogging {
val (time, res) = Utils.time {
// Set up Z3
val cfg = new util.HashMap[String, String]()
cfg.put("timeout", TIMEOUT.toString())
cfg.put("timeout", Z3FOEncoding.TIMEOUT.toString())
val ctx = new Context(cfg)
val s = ctx.mkSolver()
......
......@@ -30,7 +30,7 @@ class ParserTest extends FlatSpec {
val pwf = parsed.get
pwf.sig should be(wf.sig)
if (!wf.steps.isEmpty) {
if (wf.steps.nonEmpty) {
pwf.steps should be(wf.steps)
}
}
......@@ -46,7 +46,7 @@ class ParserTest extends FlatSpec {
val pwf = parsed.get.w
pwf.sig should be(spec.w.sig)
if (!spec.w.steps.isEmpty) {
if (spec.w.steps.nonEmpty) {
pwf.steps should be(spec.w.steps)
}
......@@ -217,6 +217,7 @@ class ParserTest extends FlatSpec {
val st = Var("s","T")
val map = Map("O" -> (List(xt,st), True))
testSpecResult(s, Spec(w, map, t, List()))
}
......@@ -231,4 +232,22 @@ class ParserTest extends FlatSpec {
val f2 = FunFromVar.unapply(s2)
f2 should be(Some(Fun("R1", None, List("a", "b"))))
}
it should "parse quantified stuff" in {
val f1 = "∀x. ∃y. fun(x, y)"
val r1 = Forall("x", Exists("y", Fun("fun",List("x","y"))))
WorkflowParser.parseTerm(f1).get should be (r1)
val f2 = "∀x ∃y (fun(x, y) ∨ ¬fun(y,x))"
val r2 = Forall("x", Exists("y", Fun("fun",List("x","y")) \/ Neg(Fun("fun", List("y","x")))))
val parsed = WorkflowParser.parseTerm(f2)
parsed.get should be (r2)
val f3 = "∃y. (fun(y) ⟹ fun(y))"
val r3 = Exists("y", Fun("fun", "y") --> Fun("fun", "y"))
WorkflowParser.parseTerm(f3).get should be (r3)
}
}
\ No newline at end of file
......@@ -3,14 +3,13 @@ package de.tum.workflows.tests
import org.scalatest._
import org.scalatest.Matchers._
import org.scalatest.Inspectors._
import com.microsoft.z3.Status
import com.typesafe.scalalogging.LazyLogging
import de.tum.workflows.Implicits._
import de.tum.workflows.WorkflowParser
import de.tum.workflows.foltl.FOLTL
import de.tum.workflows.foltl.FOLTL._
import de.tum.workflows.toz3.Z3BSFO
import de.tum.workflows.toz3.{Z3BSFO, Z3QFree}
class Z3BSFOTest extends FlatSpec with LazyLogging {
......@@ -53,11 +52,36 @@ class Z3BSFOTest extends FlatSpec with LazyLogging {
it should "check tautological parameterized functions" in {
val easyForm = Neg(Exists(List("x"), And(Fun("p", List("x")), Neg(Fun("p", List("x"))))))
val easyForm = Neg(Exists("x", And(Fun("p", "x"), Neg(Fun("p", "x")))))
val (s, solver) = Z3BSFO.checkAE(easyForm)
s should be (Status.UNSATISFIABLE) // trivially unsat
}
it should "simplify first order stuff" in {
val easyForm = Neg(Exists("x", Fun("p", "x") /\ Neg(Fun("p", "x"))))
val simped = Z3BSFO.simplifyBS(easyForm)
val simpedq = Z3QFree.simplifyUniv(easyForm)
simped should be (True)
simped should be (simpedq)
val form2 = WorkflowParser.parseTerm("(f1() ∧ ∃y. ∀x. (fun(x, y) ⟹ ¬fun(y,x)))").get
val simped2 = Z3BSFO.simplifyBS(form2)
println(simped2)
}