Commit 19d48092 authored by Christian Müller's avatar Christian Müller

add metrics

parent c712d7e2
......@@ -8,16 +8,16 @@ forall x:A,p:P,r:R
(Assign(x,p) ∧ O(x,p,r)) → Review += (x,p,r)
loop {
forall xa:A,xb:A,p:P,r:R (Assign(xa,p) ∧ Review(xb,p,r)) → Read += (xa,p,r)
forallmay x:A,p:P,r:R (Assign(x,p) ∧ O(x,p,r)) → Review += (x,p,r)
forallmay x:A,p:P,r:R (Assign(x,p)) → Review += (x,p,r)
}
Declassify
O(x:A,p:P,r:R): ¬ Conf(at:A,p:P)
O(x:A,p:P,r:R): ¬ Conf(xat:A,p:P)
Target
Read(at:A, bt:A, pt:P, rt:R)
Read(xat:A, pt:P, rt:R)
Causality
......
Workflow
forallmay x:X,p:P
True -> Conf += (x,p)
forall x:X,p:P,r:R
(¬ Conf(x,p) ∧ O(x,p,r)) -> Read += (x,p,r)
forallmay y:X,x:X,p:P
(¬ Conf(x,p) ∧ ¬ Conf(y,p)) -> Comm += (x,y,p)
Declassify
O(x:X,p:P,r:R): ¬ Conf(xt:X,p:P)
Target
Comm(xt:X, yt:X, pt:P)
Causality
a:X
......@@ -29,7 +29,7 @@ object MainInvariants extends App with LazyLogging {
val (t, (res, dot)) = time {
// InvariantChecker.checkInvariantOnce(spec.w, inv(spec), true)
InvariantChecker.checkInvariantFP(spec, invariant(spec), true)
InvariantChecker.checkInvariantFP(spec, invariant(spec))
}
val msg = s"Invariant was ${if (res) "" else "not "}proven (took $t ms)\n"
......
......@@ -9,55 +9,52 @@ import de.tum.workflows.foltl.FormulaFunctions._
import de.tum.workflows.foltl.FormulaFunctions
import de.tum.workflows.ltl.FOTransformers
import de.tum.workflows.foltl.Properties._
import de.tum.workflows.toz3.InvProperties
object Preconditions extends LazyLogging {
def elaborateSteps(b:SimpleBlock, s:Spec) = {
val newsteps = (for (stmt <- b.steps) yield {
val first = stmt.tuple.head
if (first.typ == s.target.params.head.typ) {
val fun = Fun(stmt.fun, stmt.tuple)
// TODO
val guard = Neg(Eq(fun.in(T1), fun.in(T2)))
List(stmt, Add(guard, INFNAME, List(first)))
} else List(stmt)
def elaborateSteps(b: SimpleBlock, s: Spec) = {
val newsteps = (for (stmt <- b.steps if stmt.tuple.head.typ == s.target.params.head.typ) yield {
val fun = Fun(stmt.fun, stmt.tuple)
// TODO
val guard = Neg(Eq(fun.in(T1), fun.in(T2)))
ForallBlock(stmt.tuple, List(Add(guard, INFNAME, List(stmt.tuple.head))))
})
newsteps.flatten
b :: newsteps
}
def elaborateOraclyBlock(b:SimpleBlock, s:Spec) = {
def elaborateOraclyBlock(b: SimpleBlock, s: Spec) = {
if (b.isoracly) {
val stmt = b.steps.head // can only be one
def fixguard(f:Formula) = {
def fixguard(f: Formula) = {
f.everywhere {
case f:Fun if f.isOracle() => True
case f: Fun if f.isOracle() => True
}
}
def findOracle(f:Formula) = {
def findOracle(f: Formula) = {
f.collect {
case f:Fun if (f.isOracle()) => List(f.name)
case f: Fun if (f.isOracle()) => List(f.name)
}
}
val name = findOracle(stmt.guard).head
val newstmt = stmt match {
case Add(guard, fun, tuple) => Add(fixguard(guard).simplify(), fun, tuple)
case Add(guard, fun, tuple) => Add(fixguard(guard).simplify(), fun, tuple)
case Remove(guard, fun, tuple) => Remove(fixguard(guard).simplify(), fun, tuple)
}
ForallMayBlock(b.agents, name, List(newstmt))
} else b
}
def subst(f:Formula, updates: List[(String, (List[FOLTL.Var], FOLTL.Formula))], b:SimpleBlock) = {
def subst(f: Formula, updates: List[(String, (List[FOLTL.Var], FOLTL.Formula))], b: SimpleBlock) = {
updates.foldRight(f)((update, f) => {
val (repname, (vars, form)) = update
f everywhere {
case Fun(name, ind, params) if name == repname => {
val renamed = form.parallelRename(vars, params)
// Annotate the precondition with the index
if (ind.isDefined) {
FormulaFunctions.annotate(renamed, ind.get)
......@@ -69,93 +66,110 @@ object Preconditions extends LazyLogging {
})
}
def weakestPrecondition(f: Formula, outerb: SimpleBlock, spec:Spec, stubborn:Boolean) = {
val b = elaborateOraclyBlock(outerb, spec)
// elaborate only if non-stubborn
val steps = if (!stubborn) elaborateSteps(b, spec) else b.steps
val choice = b.pred.map(n => Fun(n, b.agents))
val updates = steps.map(s => {
s.fun -> (s.tuple, {
val frees = s.guard.freeVars() -- s.tuple.toSet
// val list = List(s.guard) ++ choice.toList
// val guard = And.make(list)
val first = s.tuple.head
// Trace related substitution for informedness
// TODO: make switch on/off etc
val con = if (outerb.isoracly) {
val decl = spec.declass.getOrElse(b.pred.get, (List(), False))._2
// FIXME: substitutions?
// FIXME: decl in T2?
Or(And(decl.in(T1), choice.get.in(T1)), And(Neg(decl.in(T1)), choice.get))
} else if (choice.isDefined) {
val inner = if (first.typ == spec.target.params.head.typ) {
val inf = Fun(INFNAME, List(s.tuple.head))
if (stubborn) {
choice.get.in(T1)
} else {
Or(And(Neg(inf.in(T1)), choice.get.in(T1)), And(inf.in(T1), choice.get))
}
def getUpdate(s:Statement, b:SimpleBlock, choice:Option[Fun], isOracly:Boolean, spec:Spec, properties:InvProperties) = {
val frees = s.guard.freeVars() -- s.tuple.toSet
// val list = List(s.guard) ++ choice.toList
// val guard = And.make(list)
val first = s.tuple.head
// Trace related substitution for informedness
val con = if (isOracly) {
val decl = spec.declass.getOrElse(b.pred.get, (List(), False))._2
// FIXME: substitutions?
// FIXME: decl in T2?
Or(And(decl.in(T1), choice.get.in(T1)), And(Neg(decl.in(T1)), choice.get))
} else if (choice.isDefined) {
// is "normal" may block
val inner = if (first.typ == spec.target.params.head.typ) {
val inf = Fun(INFNAME, List(b.agents.head))
if (properties.stubborn) {
choice.get.in(T1)
} else {
choice.get
Or(And(Neg(inf.in(T1)), choice.get.in(T1)), And(inf.in(T1), choice.get))
}
inner
} else {
True
choice.get
}
val guard = And(s.guard, con).simplify()
// val guard = True
inner
} else {
True
}
val form = s match {
case Add(_, _, _) => {
Or(Fun(s.fun, s.tuple), Exists(frees.toList, guard)).simplify()
}
case Remove(_, _, _) => {
And(Fun(s.fun, s.tuple), Forall(frees.toList, Neg(guard))).simplify()
}
val guard = And(s.guard, con).simplify()
// val guard = True
val form = s match {
case Add(_, _, _) => {
Or(Fun(s.fun, s.tuple), Exists(frees.toList, guard)).simplify()
}
form
})
case Remove(_, _, _) => {
And(Fun(s.fun, s.tuple), Forall(frees.toList, Neg(guard))).simplify()
}
}
form
}
def weakestPrecondition(post: Formula, outerb: SimpleBlock, spec: Spec, properties: InvProperties) = {
// elaborate only if non-stubborn
val steps = if (!properties.stubborn) elaborateSteps(outerb, spec) else List(outerb)
val precond = steps.foldRight(post)((b, f) => {
val innerb = if (b.isoracly) {
elaborateOraclyBlock(b, spec)
} else {
b
}
val innerprecond = weakestPreconditionSingle(f, innerb, b.isoracly, spec, properties)
innerprecond
})
precond
}
def weakestPreconditionSingle(f: Formula, b: SimpleBlock, isOracly:Boolean, spec: Spec, properties: InvProperties) = {
val choice = b.pred.map(n => Fun(n, b.agents))
val updates = for (s <- b.steps) yield {
s.fun -> (s.tuple, {
getUpdate(s, b, choice, isOracly, spec, properties)
})
}
// Replace literals with their precondition
val replaced = subst(f, updates, b)
// TODO: have we proven this correct?
val removed = FOTransformers.eliminateDoubleQuantifiers(replaced)
removed
}
def abstractPrecondition(f:Formula, b:SimpleBlock, stubborn:Boolean, s:Spec) = {
val precond = weakestPrecondition(f, b, s, stubborn)
def abstractPrecondition(f: Formula, b: SimpleBlock, s: Spec, properties: InvProperties) = {
val precond = weakestPrecondition(f, b, s, properties)
// Stubborn agents -> remove trace variable from choice predicate
// Not neccessary anymore
// val stubprecond = if (stubborn) {
// precond.everywhere {
// case Fun(f, i, p) if b.may && f == b.pred.get => Fun(f, p)
// }
// } else precond
// Abstract away inner existentials
val universalinv = if (b.isomitting) {
FOTransformers.abstractExistentials(precond).simplify()
// val stubprecond = if (stubborn) {
// precond.everywhere {
// case Fun(f, i, p) if b.may && f == b.pred.get => Fun(f, p)
// }
// } else precond
// Abstract away inner existentials - always if causal agents
val universalinv = if (!properties.stubborn || b.isomitting) {
FOTransformers.abstractExistentials(precond)
} else {
precond
}
// Abstract away auxilliaries
val auxless = if (b.pred.isDefined) {
FOTransformers.eliminateAuxiliaryPredicate(universalinv, b.pred.get).simplify()
val auxless = if (b.pred.isDefined && properties.eliminateAux) {
FOTransformers.eliminateAuxiliaryPredicate(universalinv, b.pred.get)
} else {
universalinv
}
auxless
auxless.simplify()
}
}
\ No newline at end of file
......@@ -17,32 +17,31 @@ object FOLTL {
def collect[T](coll: PartialFunction[Formula, List[T]]) = FormulaFunctions.collect(coll, this)
def hasSubFormula(coll: PartialFunction[Formula, Boolean]) = FormulaFunctions.hasSubFormula(coll, this)
def opsize() = FormulaFunctions.opsize(this)
def toPrenex() = FormulaFunctions.toPrenex(this)
def toNegNf() = FormulaFunctions.toNegNf(this)
def toCNF() = FormulaFunctions.toCNF(this)
def isBS() = FormulaFunctions.isBS(this)
def removeOTQuantifiers() = FormulaFunctions.removeOTQuantifiers(this)
def parallelRename(vars:List[Var], newvars:List[Var]) = FormulaFunctions.parallelRename(this, vars, newvars)
def parallelRename(vars: List[Var], newvars: List[Var]) = FormulaFunctions.parallelRename(this, vars, newvars)
def bracketed(): String = this match {
case _: BinOp => "(" + this + ")"
case _ => this.toString()
}
def (f2:Formula) = Implies(this, f2)
def (f2:Formula) = And(this, f2)
def (f2:Formula) = Or(this, f2)
def (f2: Formula) = Implies(this, f2)
def (f2: Formula) = And(this, f2)
def (f2: Formula) = Or(this, f2)
// TODO: better line breaking
def pretty(): String = {
val remeq = this.everywhere {
case Eq(t1, t2) if FormulaFunctions.annotateOverwrite(t1, Properties.T1, Properties.T2) == t2 =>
case Eq(t1, t2) if FormulaFunctions.annotateOverwrite(t1, Properties.T1, Properties.T2) == t2 =>
Eq(t1, Fun("eq", List()))
}
val s = remeq.toString
val broken = s replace ("∧ ", "∧\n") replace ("∨ ", "∨\n")
......@@ -65,7 +64,7 @@ object FOLTL {
object Var {
val DEFAULT_TYPE = "T"
}
case class Var(name: String, typ:String = Var.DEFAULT_TYPE) extends Formula {
case class Var(name: String, typ: String = Var.DEFAULT_TYPE) extends Formula {
override def toString() = s"$name"
def withType() = s"$name:$typ"
}
......@@ -74,27 +73,40 @@ object FOLTL {
def apply(name: String, params: List[Var]): Fun = Fun(name, None, params)
}
object FunFromVar {
val Predicate = "([a-zA-Z0-9]+)(#([a-zA-Z0-9]+))?((_[a-zA-Z0-9]+)*)".r
def unapply(s:String):Option[Fun] = {
val Predicate = "([a-zA-Z0-9]+)(#([a-zA-Z0-9]+))?((_[a-zA-Z0-9]+#[a-zA-Z0-9]+)*)".r
def getVars(s: String) = {
val split = s split ("_")
if (split.length > 1) {
split.drop(1).map(str => {
val Array(name, typ) = str.split("#")
Var(name, typ)
}) toList
} else {
List()
}
}
def unapply(s: String): Option[Fun] = {
s match {
case Predicate(name, null, _, tup, _*) => Some(Fun(name, None, tup split("_") drop(1) map (Var(_)) toList))
case Predicate(name, _, ind, tup, _*) => Some(Fun(name, Some(ind), tup split("_") drop(1) map (Var(_)) toList))
case _ => None
case Predicate(name, null, _, tup, _*) => Some(Fun(name, None, getVars(tup)))
case Predicate(name, _, ind, tup, _*) => Some(Fun(name, Some(ind), getVars(tup)))
case _ => None
}
}
}
case class Fun(name: String, ind: Option[String], params: List[Var]) extends Formula {
override def toString() = name + Utils.mkString(ind, "(", "", ")") + params.map(_.name).mkString("(", ",", ")")
def updatedParams(index: Int, p:Var) = Fun(name, ind, params.updated(index, p))
def updatedParams(index: Int, p: Var) = Fun(name, ind, params.updated(index, p))
def updatedParams(list: List[Var]) = Fun(name, ind, list)
def isOracle() = name.startsWith("O")
def encodeToVar() = {
val pi = if (ind.isDefined) "#" + ind.get else ""
Var(name + pi + Utils.mkString(params, "_", "_", ""))
val pi = if (ind.isDefined) "#" + ind.get else ""
val tup = params.map(p => p.name + "#" + p.typ)
Var(name + pi + Utils.mkString(tup, "_", "_", ""))
}
}
......@@ -122,11 +134,11 @@ object FOLTL {
val qname = "∀"
val qmake = Forall.apply _
}
case class ForallOtherThan(vars: List[Var], otherthan:List[Var], t: Formula) extends Quantifier {
case class ForallOtherThan(vars: List[Var], otherthan: List[Var], t: Formula) extends Quantifier {
val qname = "∀"
val qmake = ForallOtherThan.apply(_:List[Var], otherthan, _:Formula)
val qmake = ForallOtherThan.apply(_: List[Var], otherthan, _: Formula)
override def toString() = {
s"$qname ${vars.map(_.withType()).mkString(",")} ∉ {${otherthan.map(_.withType()).mkString(",")}}. ${t.bracketed()}"
s"$qname ${vars.map(_.withType()).mkString(",")} ∉ {${otherthan.map(_.withType()).mkString(",")}}. ${t.bracketed()}"
}
}
......@@ -150,9 +162,9 @@ object FOLTL {
val make = Until.apply _
val opname = "U"
}
object WUntil {
def apply(t1: Formula, t2: Formula) = Or(Until(t1, t2), Globally(t1))
def apply(t1: Formula, t2: Formula) = Or(Until(t1, t2), Globally(t1))
}
trait UnOp extends Formula {
......@@ -199,7 +211,7 @@ object FOLTL {
}
}
}
object Or {
def make(terms: List[Formula]) = {
BinOp.makeL(Or.apply _, terms, False)
......@@ -208,12 +220,12 @@ object FOLTL {
BinOp.makeL(Or.apply _, terms, False)
}
}
case class Or(t1: Formula, t2: Formula) extends BinOp {
val opname = "∨"
val make = Or.apply _
}
object And {
def make(terms: List[Formula]) = {
BinOp.makeL(And.apply _, terms, True)
......@@ -222,17 +234,25 @@ object FOLTL {
BinOp.makeL(And.apply _, terms, True)
}
}
case class And(t1: Formula, t2: Formula) extends BinOp {
val opname = "∧"
val make = And.apply _
}
case class Eq(t1: Formula, t2: Formula) extends BinOp {
val opname = "↔"
val make = Eq.apply _
}
object Eq {
def make(terms: List[Formula]) = {
BinOp.makeL(Eq.apply _, terms, True)
}
def make(terms: Formula*) = {
BinOp.makeL(Eq.apply _, terms, True)
}
}
case class Implies(t1: Formula, t2: Formula) extends BinOp {
val opname = "→"
val make = Implies.apply _
......
......@@ -4,6 +4,7 @@ import FOLTL._
import com.typesafe.scalalogging.LazyLogging
import de.tum.workflows.blocks.Workflow
import scala.annotation.tailrec
import de.tum.workflows.ltl.FOTransformers
object FormulaFunctions extends LazyLogging {
......@@ -54,6 +55,10 @@ object FormulaFunctions extends LazyLogging {
case And(t, True) => t
case Implies(False, t) => True
case Implies(True, t) => t
case Or(Neg(t1), t2) if t1 == t2 => True
case And(Neg(t1), t2) if t1 == t2 => False
case Or(t1, Neg(t2)) if t1 == t2 => True
case And(t1, Neg(t2)) if t1 == t2 => False
// left-trees
case And(And(t1, t2), t3) => And(t1, And(t2, t3))
......@@ -416,7 +421,7 @@ object FormulaFunctions extends LazyLogging {
}).simplify()
}
def toCNF(f: Formula) = {
def toCNFClauses(f: Formula) = {
@tailrec
def toCNFSub(f: Formula): Formula = {
val (changed, sub) = everywhereFP({
......@@ -431,7 +436,27 @@ object FormulaFunctions extends LazyLogging {
// normalized is now Quantifiers, then only And/Or with Negation inside
// no simplification here
toCNFSub(normalized)
val cnf = toCNFSub(normalized)
val quantifiers = FormulaFunctions.collectQuantifiers(cnf)
// Inspect clauses
val clauses = FOTransformers.collectClauses(cnf)
// TODO improve runtime
val simpedset = clauses.toSet
val simped = simpedset.filterNot(c => simpedset.exists(c2 => {
// check if c is superset of c2
c != c2 && c.startsWith(c2)
}))
(quantifiers, clauses)
}
def toCNF(f:Formula) = {
val (quantifiers, clauses) = toCNFClauses(f)
val form = And.make(clauses.map(Or.make _))
val newform = quantifiers.foldRight(form)((q, inner) => if(q._1) Exists(q._2, inner) else Forall(q._2, inner))
newform
}
/**
......
......@@ -49,16 +49,13 @@ object FOTransformers extends LazyLogging {
case Quantifier(make, vars, inner) => make(vars.filterNot(bound.contains).distinct, elimSub(inner, bound ++ vars))
}
}
elimSub(f, Set())
elimSub(f.toNegNf(), Set())
}
def eliminateAuxiliaryPredicate(f:Formula, AUX:String) = {
val cnf = f.toCNF()
val quantifiers = FormulaFunctions.collectQuantifiers(cnf)
val (quantifiers, clauses) = FormulaFunctions.toCNFClauses(f)
// Inspect clauses
val clauses = FOTransformers.collectClauses(cnf)
val simped =
for (c <- clauses;
removeclause = (for (c1 <- c if (c.contains(Neg(c1)))) yield true)
......@@ -67,15 +64,8 @@ object FOTransformers extends LazyLogging {
c
}
// TODO improve runtime
val simp2 = simped.filterNot(c => simped.exists(c2 => {
// check if c is superset of c2
c != c2 && c.startsWith(c2)
}))
val form = And.make(simp2.map(Or.make _))
// TODO: improve
val form = And.make(simped.map(Or.make _))
val newform = quantifiers.foldRight(form)((q, inner) => if(q._1) Exists(q._2, inner) else Forall(q._2, inner))
// TODO: add equalities?
......
......@@ -19,6 +19,11 @@ import de.tum.workflows.ltl.FOTransformers
import de.tum.workflows.Utils
import de.tum.workflows.foltl.Properties
case class InvProperties(stubborn:Boolean, eliminateAux:Boolean) { }
object InvProperties {
val DEFAULT = InvProperties(true, true)
}
object InvariantChecker extends LazyLogging {
val TIMEOUT = 60000 // in milliseconds
......@@ -65,25 +70,32 @@ object InvariantChecker extends LazyLogging {
res
}
def checkBlockInvariant(spec:Spec, b: SimpleBlock, pre: Formula, post: Formula, stubborn: Boolean, isfirst: Boolean) = {
val precond = Preconditions.abstractPrecondition(post, b, stubborn, spec)
def checkBlockInvariant(spec:Spec, b: SimpleBlock, pre: Formula, post: Formula, isfirst: Boolean, properties:InvProperties) = {
val precond = Preconditions.abstractPrecondition(post, b, spec, properties)
// TODO: do we do this here?
val firstprecond = if (isfirst) {
precond.assumeEmpty(Properties.INFNAME :: spec.w.sig.preds.map(_.name).toList)
precond.assumeEmpty(spec.w.sig.preds.map(_.name).toList)
} else { precond }
val noinfprecond = if (isfirst) {
firstprecond.assumeEmpty(List(Properties.INFNAME))
} else { firstprecond }
val z3simpednewinv = Z3QFree.simplifyUniv(noinfprecond)
val gained = noinfprecond.toString().length() - z3simpednewinv.toString().length()
logger.info(s"Invariant shrunken by $gained chars")
// val test1 = inv.toPrenex()
// val test2 = stubprecond.toPrenex()
val f = Implies(pre, firstprecond)
val f = Implies(pre, z3simpednewinv)
logger.info(s"Checking invariant implication for $b")
checkOnZ3(f.simplify())
(checkOnZ3(f.simplify()), z3simpednewinv)
}
def checkInvariantFP(spec: Spec, inv: Formula, stubborn: Boolean) = {
def checkInvariantFP(spec: Spec, inv: Formula, properties:InvProperties = InvProperties.DEFAULT) = {
val graph = Encoding.toGraph(spec.w)
type Node = graph.NodeT
type Edge = graph.EdgeT
......@@ -111,7 +123,7 @@ object InvariantChecker extends LazyLogging {
val pre = labels(next._1)
val post = labels(next._2)
val isfirst = !next._1.hasPredecessors
val (status, solver) = checkBlockInvariant(spec, next, pre, post, stubborn, isfirst)
val ((status, solver), strengthened) = checkBlockInvariant(spec, next, pre, post, isfirst, properties)
if (status == Status.UNSATISFIABLE) {
// Negation of implication unsat
......@@ -126,27 +138,21 @@ object InvariantChecker extends LazyLogging {
logger.info(s"Invariant not inductive, strengthening")
if (!isfirst) {
// check if strengthened -> old_inv else use conjunction
val strengthened = Preconditions.abstractPrecondition(post, next, stubborn, spec)
// val newinv = if (checkOnZ3(Implies(labels(next._1), strengthened))._1 == Status.SATISFIABLE) {
// And(labels(next._1), strengthened).simplify()
// } else {
// strengthened.simplify()
// }
// val strengthened = Preconditions.abstractPrecondition(post, next, spec, properties)
val newinv = And(labels(next._1), strengthened).simplify()
val (status2, solver2) = checkOnZ3(Implies(newinv, False))
val nostrangebindings = FormulaFunctions.checkBindings(newinv)
if (!nostrangebindings) {
logger.error("New invariant binds variables more than once")
logger.error(s"Invariant would be $newinv")
}
val newlabels = labels.updated(next._1, newinv)
val newlabels = labels.updated(next._1, strengthened)
val invalidated = proven.filter(_._2 == next._1)
val newproven = (proven -- invalidated) + next
val (status2, solver2) = checkOnZ3(Implies(newinv, False))
if (status2 == Status.SATISFIABLE) {
// Negation of newinv still sat, newinv does not imply false)
logger.info(s"Relabeling ${next._1}. Continuing.")
......@@ -174,7 +180,7 @@ object InvariantChecker extends LazyLogging {
(result, dot2)