You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1478 lines
37 KiB

/* Written by Denis Oddoux, LIAFA, France *
* Copyright (c) 2001 Denis Oddoux *
* Modified by Paul Gastin, LSV, France *
* Copyright (c) 2007 Paul Gastin *
* Ported by Carlos Bederian, FaMAF, Argentina *
* Copyright (c) 2007 Carlos Bederian *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA*
* *
* Based on the translation algorithm by Gastin and Oddoux, *
* presented at the 13th International Conference on Computer Aided *
* Verification, CAV 2001, Paris, France. *
* Proceedings - LNCS 2102, pp. 53-65 *
* */
package jltl2ba;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import jltl2dstar.APMonom;
import jltl2dstar.NBA;
import prism.PrismException;
public class SimpleLTL {
public enum LTLType {
FALSE, TRUE,
AP,
NOT, NEXT,
OR, AND, EQUIV, IMPLIES,
UNTIL, RELEASE,
GLOBALLY, FINALLY
};
public SimpleLTL left;
public SimpleLTL right;
public LTLType kind;
public String ap;
public SimpleLTL(boolean v)
{
left = null;
right = null;
kind = v ? LTLType.TRUE : LTLType.FALSE;
ap = null;
}
public SimpleLTL(String prop)
{
left = null;
right = null;
kind = LTLType.AP;
ap = prop;
}
public SimpleLTL(LTLType type, SimpleLTL lft)
{
switch (type) {
case NOT:
case NEXT:
case GLOBALLY:
case FINALLY:
left = lft;
right = null;
kind = type;
ap = null;
break;
default:
// throw new PrismException("Trying to build invalid SimpleLTL");
}
}
public SimpleLTL(LTLType type, SimpleLTL lft, SimpleLTL rgt)
{
switch (type) {
case AND:
case OR:
case IMPLIES:
case EQUIV:
case UNTIL:
case RELEASE:
left = lft;
right = rgt;
kind = type;
ap = null;
break;
default:
// throw new PrismException("Trying to build invalid SimpleLTL");
}
}
public SimpleLTL(LTLType type, SimpleLTL lft, SimpleLTL rgt, String prop)
{
kind = type;
left = lft;
right = rgt;
ap = null;
if (prop != null) { ap = prop; }
}
public boolean equals(Object o)
{
if (o instanceof SimpleLTL) {
SimpleLTL other = (SimpleLTL) o;
if (kind == other.kind) {
switch (kind) {
case FALSE:case TRUE:
return true;
case NOT:case NEXT:case GLOBALLY:case FINALLY:
return left.equals(other.left);
case OR:case AND:case EQUIV:case IMPLIES:case UNTIL:case RELEASE:
return (left.equals(other.left) && right.equals(other.right));
case AP:
return ap.equals(other.ap);
default:
return false;
}
}
else return false;
}
else return false;
}
public APSet getAPs()
{
APSet rv;
switch (kind) {
case NOT:case NEXT:
case GLOBALLY:case FINALLY:
rv = left.getAPs();
break;
case OR:case AND:
case EQUIV:case IMPLIES:
case UNTIL:case RELEASE:
rv = left.getAPs();
for (String s : right.getAPs())
rv.addAP(s);
break;
// terminals
case FALSE:case TRUE:
rv = new APSet();
break;
case AP:
rv = new APSet();
rv.addAP(ap);
break;
default:
rv = new APSet();
break;
}
return rv;
}
public SimpleLTL clone()
{
SimpleLTL rv = new SimpleLTL(kind,
left != null ? left.clone() : null,
right != null ? right.clone() : null,
ap);
return rv;
}
private boolean implies(SimpleLTL b)
{
return
(this.equals(b) ||
b.kind == LTLType.TRUE ||
kind == LTLType.FALSE ||
(b.kind == LTLType.AND && this.implies(b.left) && this.implies(b.right)) ||
(kind == LTLType.OR && left.implies(b) && right.implies(b)) ||
(kind == LTLType.AND && (left.implies(b) || right.implies(b))) ||
(b.kind == LTLType.OR && (this.implies(b.left) || this.implies(b.right))) ||
(b.kind == LTLType.UNTIL && this.implies(b.right)) ||
(kind == LTLType.RELEASE && right.implies(b)) ||
(kind == LTLType.UNTIL && left.implies(b) && right.implies(b)) ||
(b.kind == LTLType.RELEASE && this.implies(b.left) && this.implies(b.right)) ||
((kind == LTLType.UNTIL || kind == LTLType.RELEASE) && kind == b.kind &&
left.implies(b.left) && right.implies(b.right)));
}
/**
* Construct simplified PNF form.
*
* Currently, this requires that the SimpleLTL formula is a tree, i.e., that
* subtrees are not shared. If this is not the case,
* an {@code IllegalArgumentException} is thrown.
*/
public SimpleLTL simplify()
{
if (!this.isTree()) {
throw new IllegalArgumentException("Implementation error: SimpleLTL.simplify() requires that the formula is a tree, not a DAG");
}
return this.simplified();
}
private SimpleLTL simplified()
{
SimpleLTL tmp, tmp2, a, b;
SimpleLTL rv = this;
switch (kind) {
case AND:
case OR:
case IMPLIES:
case EQUIV:
case UNTIL:
case RELEASE:
right = right.simplified();
left = left.simplified();
break;
case NOT:
case NEXT:
case FINALLY:
case GLOBALLY:
left = left.simplified();
break;
default:
// do nothing
}
switch (kind) {
case NOT:
tmp = this.pushNegation();
if (tmp.kind != LTLType.NOT)
rv = tmp.simplified();
else rv = tmp;
break;
case FINALLY:
if (left.kind == LTLType.TRUE || left.kind == LTLType.FALSE) {
rv = left;
break;
}
else if (left.kind == LTLType.UNTIL) {
if (left.left.kind == LTLType.TRUE) {
rv = left;
break;
}
left = left.right;
/* fall thru */
}
tmp = new SimpleLTL(LTLType.UNTIL, new SimpleLTL(true), left);
rv = tmp.simplified();
break;
case GLOBALLY:
if (left.kind == LTLType.FALSE || left.kind == LTLType.TRUE) {
rv = left;
break;
}
else if (left.kind == LTLType.RELEASE) {
if (left.left.kind == LTLType.FALSE) {
rv = left;
break;
}
left = left.right; /* [] (p V q) = [] q */
/* fall thru */
}
tmp = new SimpleLTL(LTLType.RELEASE, new SimpleLTL(false), left);
rv = tmp.simplified();
break;
case UNTIL:
if (right.kind == LTLType.TRUE
|| right.kind == LTLType.FALSE
|| left.kind == LTLType.FALSE) {
rv = right;
break;
}
if (left.implies(right)) {
rv = right;
break;
}
/* (p U q) U p = (q U p) */
if (left.kind == LTLType.UNTIL && left.left.equals(right)) {
left = left.right;
break;
}
if (right.kind == LTLType.UNTIL && left.implies(right.left)) {
rv = right;
break;
}
/* X p U X q == X (p U q) */
if (right.kind == LTLType.NEXT && left.kind == LTLType.NEXT) {
rv = new SimpleLTL(LTLType.NEXT,
new SimpleLTL(LTLType.UNTIL, left.left, right.left));
break;
}
/* F X p == X F p */
if (left.kind == LTLType.TRUE && right.kind == LTLType.NEXT) {
rv = new SimpleLTL(LTLType.NEXT,
new SimpleLTL(LTLType.UNTIL, new SimpleLTL(true), right.left));
break;
}
/* F G F p == G F p */
if (left.kind == LTLType.TRUE &&
right.kind == LTLType.RELEASE &&
right.left.kind == LTLType.FALSE &&
right.right.kind == LTLType.UNTIL &&
right.right.left.kind == LTLType.TRUE) {
rv = right;
break;
}
if (left.kind != LTLType.TRUE) {
tmp = new SimpleLTL(LTLType.NOT, right.clone());
if (tmp.pushNegation().implies(left))
left = new SimpleLTL(true);
break;
}
break;
case RELEASE:
if (right.kind == LTLType.FALSE
|| right.kind == LTLType.TRUE
|| left.kind == LTLType.TRUE) {
rv = right;
break;
}
/* p V p = p */
if (right.implies(left)) {
rv = right;
break;
}
/* F V (p V q) == F V q */
if (left.kind == LTLType.FALSE && right.kind == LTLType.RELEASE) {
right = right.right;
break;
}
/* G X p == X G p */
if (left.kind == LTLType.FALSE && right.kind == LTLType.NEXT) {
rv = new SimpleLTL(LTLType.NEXT,
new SimpleLTL(LTLType.RELEASE,
new SimpleLTL(false), right.left));
break;
}
/* G F G p == F G p */
if (left.kind == LTLType.FALSE &&
right.kind == LTLType.UNTIL &&
right.left.kind == LTLType.TRUE &&
right.right.kind == LTLType.RELEASE &&
right.right.left.kind == LTLType.FALSE) {
rv = right;
break;
}
if (right.kind == LTLType.RELEASE
&& right.left.implies(left)) {
rv = right;
break;
}
if (left.kind != LTLType.FALSE) {
tmp = new SimpleLTL(LTLType.NOT, right.clone());
if (left.implies(tmp.pushNegation()))
left = new SimpleLTL(false);
break;
}
break;
case NEXT:
if (left.kind == LTLType.TRUE || left.kind == LTLType.FALSE) {
rv = left;
break;
}
/* X G F p == G F p */
if (left.kind == LTLType.RELEASE &&
left.left.kind == LTLType.FALSE &&
left.right.kind == LTLType.UNTIL &&
left.right.left.kind == LTLType.TRUE) {
rv = left;
break;
}
/* X F G p == F G p */
if (left.kind == LTLType.UNTIL &&
left.left.kind == LTLType.TRUE &&
left.right.kind == LTLType.RELEASE &&
left.right.left.kind == LTLType.FALSE) {
rv = left;
break;
}
break;
case IMPLIES:
if (left.implies(right)) {
rv = new SimpleLTL(true);
break;
}
tmp = new SimpleLTL(LTLType.NOT, left);
tmp = new SimpleLTL(LTLType.OR, tmp.pushNegation(), right);
rv = tmp.rewrite();
break;
case EQUIV:
if (left.implies(right) &&
right.implies(left)) {
rv = new SimpleLTL(true);
break;
}
a = new SimpleLTL(LTLType.AND, left.clone(), right.clone());
tmp = new SimpleLTL(LTLType.NOT, left);
tmp2 = new SimpleLTL(LTLType.NOT, right);
b = new SimpleLTL(LTLType.AND, tmp.pushNegation(), tmp2.pushNegation());
rv = new SimpleLTL(LTLType.OR, a.rewrite(), b.rewrite());
rv = rv.rewrite();
break;
case AND:
/* p && (q U p) = p */
if (right.kind == LTLType.UNTIL
&& right.right.equals(left)) {
rv = left;
break;
}
if (left.kind == LTLType.UNTIL
&& left.right.equals(right)) {
rv = right;
break;
}
/* p && (q V p) == q V p */
if (right.kind == LTLType.RELEASE
&& right.right.equals(left)) {
rv = right;
break;
}
if (left.kind == LTLType.RELEASE
&& left.right.equals(right)) {
rv = left;
break;
}
/* (p U q) && (r U q) = (p && r) U q */
if (right.kind == LTLType.UNTIL
&& left.kind == LTLType.UNTIL
&& right.right.equals(left.right)) {
rv = new SimpleLTL(LTLType.UNTIL,
new SimpleLTL(LTLType.AND, left.left, right.left),
left.right);
break;
}
/* (p V q) && (p V r) = p V (q && r) */
if (right.kind == LTLType.RELEASE
&& left.kind == LTLType.RELEASE
&& right.left.equals(left.left)) {
rv = new SimpleLTL(LTLType.RELEASE,
right.left,
new SimpleLTL(LTLType.AND, left.right, right.right));
break;
}
/* X p && X q == X (p && q) */
if (right.kind == LTLType.NEXT && left.kind == LTLType.NEXT) {
rv = new SimpleLTL(LTLType.NEXT,
new SimpleLTL(LTLType.AND, left.left, right.left));
break;
}
/* (p V q) && (r U q) == p V q */
if (right.kind == LTLType.UNTIL
&& left.kind == LTLType.RELEASE
&& left.right.equals(right.right)) {
rv = left;
break;
}
if (left.equals(right) /* (p && p) == p */
||right.kind == LTLType.FALSE /* (p && F) == F */
|| left.kind == LTLType.TRUE /* (T && p) == p */
|| right.implies(left)) { /* NEW */
rv = right;
break;
}
if (right.kind == LTLType.TRUE /* (p && T) == p */
|| left.kind == LTLType.FALSE /* (F && p) == F */
|| left.implies(right)) { /* NEW */
rv = left;
break;
}
/* F G p && F G q == F G (p && q) */
if (left.kind == LTLType.UNTIL &&
left.left.kind == LTLType.TRUE &&
left.right.kind == LTLType.RELEASE &&
left.right.left.kind == LTLType.FALSE &&
right.kind == LTLType.UNTIL &&
right.left.kind == LTLType.TRUE &&
right.right.kind == LTLType.RELEASE &&
right.right.left.kind == LTLType.FALSE) {
rv = new SimpleLTL(LTLType.UNTIL,
new SimpleLTL(true),
new SimpleLTL(LTLType.RELEASE,
new SimpleLTL(false),
new SimpleLTL(LTLType.AND,
left.right.right,
right.right.right)));
break;
}
tmp = new SimpleLTL(LTLType.NOT, right.clone());
if (left.implies(tmp.pushNegation())) {
rv = new SimpleLTL(false);
break;
}
tmp = new SimpleLTL(LTLType.NOT, left.clone());
if (right.implies(tmp.pushNegation())) {
rv = new SimpleLTL(false);
break;
}
break;
case OR:
/* p || (q U p) == q U p */
if (right.kind == LTLType.UNTIL
&& right.right.equals(left)) {
rv = right;
break;
}
/* p || (q V p) == p */
if (right.kind == LTLType.RELEASE
&& right.right.equals(left)) {
rv = left;
break;
}
/* (p U q) || (p U r) = p U (q || r) */
if (right.kind == LTLType.UNTIL
&& left.kind == LTLType.UNTIL
&& right.left.equals(left.left)) {
rv = new SimpleLTL(LTLType.UNTIL,
right.left,
new SimpleLTL(LTLType.OR, left.right, right.right));
break;
}
if (left.equals(right) /* (p || p) == p */
||right.kind == LTLType.FALSE /* (p || F) == p */
|| left.kind == LTLType.TRUE /* (T || p) == T */
|| right.implies(left)) {
rv = left;
break;
}
if (right.kind == LTLType.TRUE /* (p || T) == T */
|| left.kind == LTLType.FALSE /* (F || p) == p */
|| left.implies(right)) {
rv = right;
break;
}
/* (p V q) || (r V q) = (p || r) V q */
if (right.kind == LTLType.RELEASE
&& left.kind == LTLType.RELEASE
&& left.right.equals(right.right)) {
rv = new SimpleLTL(LTLType.RELEASE,
new SimpleLTL(LTLType.OR, left.left, right.left),
right.right);
break;
}
/* (p V q) || (r U q) == r U q */
if (right.kind == LTLType.UNTIL
&& left.kind == LTLType.RELEASE
&& left.right.equals(right.right)) {
rv = right;
break;
}
/* G F p || G F q == G F (p || q) */
if (left.kind == LTLType.RELEASE &&
left.left.kind == LTLType.FALSE &&
left.right.kind == LTLType.UNTIL &&
left.right.left.kind == LTLType.TRUE &&
right.kind == LTLType.RELEASE &&
right.left.kind == LTLType.FALSE &&
right.right.kind == LTLType.UNTIL &&
right.right.left.kind == LTLType.TRUE) {
rv = new SimpleLTL(LTLType.RELEASE,
new SimpleLTL(false),
new SimpleLTL(LTLType.UNTIL,
new SimpleLTL(true),
new SimpleLTL(LTLType.OR,left.right.right,right.right.right)));
break;
}
tmp = new SimpleLTL(LTLType.NOT, right.clone());
if (tmp.pushNegation().implies(left)) {
rv = new SimpleLTL(true);
break;
}
tmp = new SimpleLTL(LTLType.NOT, left.clone());
if (tmp.pushNegation().implies(right)) {
rv = new SimpleLTL(true);
break;
}
break;
}
return rv;
}
/**
* Returns an equivalent SimpleLTL formula with a
* basic set of operators:
* AP, TRUE, FALSE, AND, OR, NOT, UNTIL, FINALLY, GLOBALLY, NEXT
*/
public SimpleLTL toBasicOperators() {
switch (kind) {
case AP:
case TRUE:
case FALSE:
return this;
case AND:
case OR:
case UNTIL:
return new SimpleLTL(kind, left.toBasicOperators(), right.toBasicOperators());
case FINALLY:
case GLOBALLY:
case NEXT:
case NOT:
return new SimpleLTL(kind, left.toBasicOperators());
case EQUIV: {
SimpleLTL newLeft = left.toBasicOperators();
SimpleLTL newRight = right.toBasicOperators();
SimpleLTL bothTrue = new SimpleLTL(LTLType.AND, newLeft, newRight);
SimpleLTL bothFalse = new SimpleLTL(LTLType.AND,
new SimpleLTL(LTLType.NOT, newLeft),
new SimpleLTL(LTLType.NOT, newRight));
return new SimpleLTL(LTLType.OR, bothTrue, bothFalse);
}
case IMPLIES: {
SimpleLTL newLeft = new SimpleLTL(LTLType.NOT, left.toBasicOperators());
return new SimpleLTL(LTLType.OR, newLeft, right.toBasicOperators());
}
case RELEASE: {
SimpleLTL newLeft = new SimpleLTL(LTLType.NOT, left.toBasicOperators());
SimpleLTL newRight = new SimpleLTL(LTLType.NOT, right.toBasicOperators());
return new SimpleLTL(LTLType.UNTIL, newLeft, newRight);
}
}
throw new UnsupportedOperationException("Unknown operator in SimpleLTL");
}
public SimpleLTL negate() {
return new SimpleLTL(LTLType.NOT, this);
}
public SimpleLTL pushNegation()
{
SimpleLTL m;
boolean pushBothOperands = false;
if (kind != LTLType.NOT)
return this;
// throw new PrismException("No NOT to push!");
switch (left.kind) {
case TRUE:
left = null;
kind = LTLType.FALSE;
break;
case FALSE:
left = null;
kind = LTLType.TRUE;
break;
case NOT:
m = left.left;
left = m.left;
right = m.right;
kind = m.kind;
if (kind == LTLType.AP) { ap = m.ap; }
break;
case RELEASE:
kind = LTLType.UNTIL;
pushBothOperands = true;
break;
case UNTIL:
kind = LTLType.RELEASE;
pushBothOperands = true;
break;
case NEXT:
kind = LTLType.NEXT;
left.kind = LTLType.NOT;
left = left.pushNegation();
break;
case AND:
kind = LTLType.OR;
pushBothOperands = true;
break;
case OR:
kind = LTLType.AND;
pushBothOperands = true;
break;
case IMPLIES:
kind = LTLType.AND;
m = new SimpleLTL(LTLType.NOT, left.right);
right = m.pushNegation();
left = left.left;
break;
case EQUIV:
kind = LTLType.OR;
m = new SimpleLTL(LTLType.NOT, left.left.clone());
right = new SimpleLTL(LTLType.AND, m.pushNegation(), left.right.clone());
left.kind = LTLType.AND;
m = new SimpleLTL(LTLType.NOT, left.right);
left.right = m.pushNegation();
break;
case FINALLY:
kind = LTLType.GLOBALLY;
left.kind = LTLType.NOT;
left = left.pushNegation();
break;
case GLOBALLY:
kind = LTLType.FINALLY;
left.kind = LTLType.NOT;
left = left.pushNegation();
break;
case AP:
return this;
}
if (pushBothOperands) {
m = new SimpleLTL(LTLType.NOT, left.right);
right = m.pushNegation();
left.kind = LTLType.NOT;
left.right = null;
left = left.pushNegation();
}
return this.rewrite();
}
public SimpleLTL rewrite()
{
return this.rightLinked().canonical();
}
public SimpleLTL rightLinked()
{
SimpleLTL tmp;
SimpleLTL n = this;
if (n.kind == LTLType.AND || n.kind == LTLType.OR) {
while (n.left != null && n.left.kind == n.kind) {
tmp = n.left;
n.left = tmp.right;
tmp.right = n;
n = tmp;
}
}
if (n.left != null)
n.left = n.left.rightLinked();
if (n.right != null)
n.right = n.right.rightLinked();
return n;
}
public SimpleLTL canonical()
{
return this; // No caches here, yet
/* SimpleLTL m; // assumes input is right_linked
if ((m = in_cache(n)) != ZN)
return m;
n->rgt = canonical(n->rgt);
n->lft = canonical(n->lft);
return cached(n);
*/
}
public int countNodes()
{
switch (kind) {
case AND:
case OR:
case IMPLIES:
case EQUIV:
case UNTIL:
case RELEASE:
return left.countNodes() + right.countNodes() + 1;
case NEXT:
case FINALLY:
case GLOBALLY:
case NOT:
return left.countNodes() + 1;
default:
return 1;
}
}
public int countPredicates()
{
switch (kind) {
case AND:
case OR:
case IMPLIES:
case EQUIV:
case UNTIL:
case RELEASE:
return left.countPredicates() + right.countPredicates();
case NEXT:
case FINALLY:
case GLOBALLY:
case NOT:
return left.countPredicates();
case AP:
return 1;
default:
return 0;
}
}
public String toString()
{
String rv = "";
switch (kind) {
case OR:
rv = "(" + left.toString() + " || " + right.toString() + ")";
break;
case AND:
rv = "(" + left.toString() + " && " + right.toString() + ")";
break;
case UNTIL:
rv = "(" + left.toString() + " U " + right.toString() + ")";
break;
case RELEASE:
rv = "(" + left.toString() + " V " + right.toString() + ")";
break;
case IMPLIES:
rv = "(" + left.toString() + " -> " + right.toString() + ")";
break;
case EQUIV:
rv = "(" + left.toString() + " <-> " + right.toString() + ")";
break;
case NEXT:
rv = "X " + left.toString();
break;
case FINALLY:
rv = "F " + left.toString();
break;
case GLOBALLY:
rv = "G " + left.toString();
break;
case NOT:
rv = "! " + left.toString();
break;
case FALSE:
rv = "false";
break;
case TRUE:
rv = "true";
break;
case AP:
rv = ap;
break;
default:
rv = null;
}
return rv;
}
// ltl2dstar stuff
public boolean isCoSafe()
{
switch(kind) {
case RELEASE:
case GLOBALLY:
return false;
default:
if (left != null && !left.isCoSafe()) {
return false;
}
if (right != null && !right.isCoSafe()) {
return false;
}
return true;
}
}
public boolean hasNextStep()
{
if (kind == LTLType.NEXT) {
return true;
}
if (left != null && left.hasNextStep()) {
return true;
}
if (right != null && right.hasNextStep()) {
return true;
}
return false;
}
/**
* Returns true if this SimpleLTL structure is a tree, i.e., not a
* directed acyclical graph (DAG) where sharing of subtrees is allowed.
*/
public boolean isTree()
{
// use an IdentityHashMap with (formula,formula) pairs indicating that formula
// has already been seen (due to lack of IdentityHashSet)
IdentityHashMap<SimpleLTL, SimpleLTL> seen = new IdentityHashMap<SimpleLTL, SimpleLTL>();
return isTree(seen);
}
/**
* Recursive function for checking if this SimpleLTL structure is a tree, i.e., not a
* directed acyclical graph (DAG) where sharing of subtrees is allowed.
*
* The already seen SimpleLTL subtrees are tracked in {@code seen}.
*/
private boolean isTree(IdentityHashMap<SimpleLTL,SimpleLTL> seen)
{
if (seen.containsKey(this)) {
return false;
}
seen.put(this, this);
if (left != null && !left.isTree(seen)) {
// left child exists and is not a tree
return false;
}
if (right != null && !right.isTree(seen)) {
// right child exists and is not a tree
return false;
}
return true;
}
/**
* Transform LTL formula by extend each<br>
* X phi<br>
* node in the syntax tree to<br>
* X (label & phi).
* <br>
* The LTL formula has to consist only of basic operators (see toBasicOperators()).
* @param label the label of the atomic proposition
* @return the transformed LTL formula
*/
public SimpleLTL extendNextStepWithAP(String label)
{
switch (kind) {
case TRUE:
case FALSE:
case AP:
return this.clone();
case NOT: {
SimpleLTL newLeft = left.extendNextStepWithAP(label);
return new SimpleLTL(kind, newLeft);
}
case NEXT: {
SimpleLTL newLeft = left.extendNextStepWithAP(label);
// transform X phi to X ("label" & phi)
return new SimpleLTL(LTLType.NEXT,
new SimpleLTL(LTLType.AND, new SimpleLTL(label), newLeft));
}
case OR:
case AND:
case RELEASE:
case UNTIL: {
SimpleLTL newLeft = left.extendNextStepWithAP(label);
SimpleLTL newRight = right.extendNextStepWithAP(label);
return new SimpleLTL(kind, newLeft, newRight);
}
case FINALLY:
case GLOBALLY: {
SimpleLTL newLeft = left.extendNextStepWithAP(label);
return new SimpleLTL(kind, newLeft);
}
case EQUIV:
case IMPLIES:
throw new UnsupportedOperationException("Extending Next operator with AP not supported for " + kind.toString() + " operator, only for basic operators: " + this);
}
throw new UnsupportedOperationException();
}
public SimpleLTL toDNF() throws PrismException
{
switch (kind) {
case TRUE:
return new SimpleLTL(true);
case FALSE:
return new SimpleLTL(false);
case NOT:
return new SimpleLTL(LTLType.NOT, left.toDNF());
case AP:
return new SimpleLTL(ap);
case OR:
return new SimpleLTL(LTLType.OR, left.toDNF(), right.toDNF());
case AND:
SimpleLTL l = left.toDNF();
SimpleLTL r = right.toDNF();
if (l.kind == LTLType.OR) {
SimpleLTL a, b;
a = l.left;
b = l.right;
if (r.kind == LTLType.OR) {
SimpleLTL c, d;
c=r.left;
d=r.right;
SimpleLTL a_c = new SimpleLTL(LTLType.AND, a, c);
SimpleLTL b_c = new SimpleLTL(LTLType.AND, b, c);
SimpleLTL a_d = new SimpleLTL(LTLType.AND, a, d);
SimpleLTL b_d = new SimpleLTL(LTLType.AND, b, d);
return new SimpleLTL(LTLType.OR,
(new SimpleLTL(LTLType.OR, a_c, b_c)).toDNF(),
(new SimpleLTL(LTLType.OR, a_d, b_d)).toDNF());
} else {
SimpleLTL a_c = new SimpleLTL(LTLType.AND, a, r);
SimpleLTL b_c = new SimpleLTL(LTLType.AND, b, r);
return new SimpleLTL(LTLType.OR, a_c.toDNF(), b_c.toDNF());
}
} else if (r.kind == LTLType.OR) {
SimpleLTL a, b;
a = r.left;
b = r.right;
SimpleLTL a_c = new SimpleLTL(LTLType.AND, l, a);
SimpleLTL b_c = new SimpleLTL(LTLType.AND, l, b);
return new SimpleLTL(LTLType.OR, a_c.toDNF(), b_c.toDNF());
} else {
return new SimpleLTL(LTLType.AND, l, r);
}
default:
throw new PrismException("Illegal operator for DNF!");
}
}
/** Returns an APMonom representing the formula rooted at
* this node. Formula has to be in DNF. */
public APMonom toMonom(APSet apset) throws PrismException
{
APMonom result = new APMonom(true);
switch (kind) {
case AND: {
APMonom l = left.toMonom(apset);
APMonom r = right.toMonom(apset);
result = l.and(r);
return result;
}
case NOT:
switch (left.kind) {
case AP:
result.setValue(apset.indexOf(left.ap), false);
return result;
case FALSE:
return new APMonom(true);
case TRUE:
return new APMonom(false);
default:
throw new PrismException("Formula not in DNF!");
}
case AP:
result.setValue(apset.indexOf(ap), true);
return result;
case FALSE:
return new APMonom(false);
case TRUE:
return new APMonom(true);
default:
throw new PrismException("Formula not in DNF!");
}
}
/**
* Renames the atomic propositions apparing in the formula that
* start with {@code prefixFrom}. For these, the prefix is replaced
* by prefixTo. For example, with prefixFrom = "L" and prefixTo = "p",
* "L3" will be renamed to "p3", but "T2" will be left as-is.
*/
public void renameAP(String prefixFrom, String prefixTo)
{
switch (kind) {
case AP:
if (ap.startsWith(prefixFrom)) {
ap = prefixTo + ap.substring(prefixFrom.length());
}
return;
case AND:
case OR:
case EQUIV:
case IMPLIES:
case RELEASE:
case UNTIL:
left.renameAP(prefixFrom, prefixTo);
right.renameAP(prefixFrom, prefixTo);
return;
case FINALLY:
case GLOBALLY:
case NEXT:
case NOT:
left.renameAP(prefixFrom, prefixTo);
return;
case TRUE:
case FALSE:
return;
}
throw new UnsupportedOperationException("Unknown operator in SimpleLTL formula: "+this);
}
/**
* Render this LTL formula in LBT syntax, i.e., in prefix notation.
*/
public String toStringLBT()
{
String rv = "";
switch (kind) {
case OR:
rv = "| " + left.toStringLBT() + " " + right.toStringLBT();
break;
case AND:
rv = "& " + left.toStringLBT() + " " + right.toStringLBT();
break;
case UNTIL:
rv = "U " + left.toStringLBT() + " " + right.toStringLBT();
break;
case RELEASE:
rv = "V " + left.toStringLBT() + " " + right.toStringLBT();
break;
case IMPLIES:
rv = "i " + left.toStringLBT() + " " + right.toStringLBT();
break;
case EQUIV:
rv = "e " + left.toStringLBT() + " " + right.toStringLBT();
break;
case NEXT:
rv = "X " + left.toStringLBT();
break;
case FINALLY:
rv = "F " + left.toStringLBT();
break;
case GLOBALLY:
rv = "G " + left.toStringLBT();
break;
case NOT:
rv = "! " + left.toStringLBT();
break;
case FALSE:
rv = "f";
break;
case TRUE:
rv = "t";
break;
case AP:
rv = ap;
break;
default:
rv = null;
}
return rv;
}
/**
* Render this LTL formula in Spin syntax.
*/
public String toStringSpin()
{
String rv = "";
switch (kind) {
case OR:
rv = "(" + left.toStringSpin() + ") || (" + right.toStringSpin() + ")";
break;
case AND:
rv = "(" + left.toStringSpin() + ") && (" + right.toStringSpin() + ")";
break;
case UNTIL:
rv = "(" + left.toStringSpin() + ") U (" + right.toStringSpin() + ")";
break;
case RELEASE:
rv = "(" + left.toStringSpin() + ") V (" + right.toStringSpin() + ")";
break;
case IMPLIES:
rv = "(" + left.toStringSpin() + ") -> (" + right.toStringSpin() + ")";
break;
case EQUIV:
rv = "(" + left.toStringSpin() + ") <-> (" + right.toStringSpin() + ")";
break;
case NEXT:
rv = "X (" + left.toStringSpin() + ")";
break;
case FINALLY:
rv = "<> (" + left.toStringSpin() + ")";
break;
case GLOBALLY:
rv = "[] (" + left.toStringSpin() + ")";
break;
case NOT:
rv = "! (" + left.toStringSpin() + ")";
break;
case FALSE:
rv = "false";
break;
case TRUE:
rv = "true";
break;
case AP:
rv = ap;
break;
default:
rv = null;
}
return rv;
}
/**
* Render this LTL formula in Spot syntax.
*/
public String toStringSpot()
{
String rv = "";
switch (kind) {
case OR:
rv = "(" + left.toStringSpot() + ") | (" + right.toStringSpot() + ")";
break;
case AND:
rv = "(" + left.toStringSpot() + ") & (" + right.toStringSpot() + ")";
break;
case UNTIL:
rv = "(" + left.toStringSpot() + ") U (" + right.toStringSpot() + ")";
break;
case RELEASE:
rv = "(" + left.toStringSpot() + ") R (" + right.toStringSpot() + ")";
break;
case IMPLIES:
rv = "(" + left.toStringSpot() + ") -> (" + right.toStringSpot() + ")";
break;
case EQUIV:
rv = "(" + left.toStringSpot() + ") <-> (" + right.toStringSpot() + ")";
break;
case NEXT:
rv = "X (" + left.toStringSpot() + ")";
break;
case FINALLY:
rv = "F (" + left.toStringSpot() + ")";
break;
case GLOBALLY:
rv = "G (" + left.toStringSpot() + ")";
break;
case NOT:
rv = "! (" + left.toStringSpot() + ")";
break;
case FALSE:
rv = "false";
break;
case TRUE:
rv = "true";
break;
case AP:
rv = ap;
break;
default:
rv = null;
}
return rv;
}
/** Parse a formula in LBT (prefix format) */
public static SimpleLTL parseFormulaLBT(String formula) throws Exception
{
formula=formula.trim(); // remove leading, trailing spaces
String[] split = formula.split("[ ]+");
List<String> formulaList = new ArrayList<String>();
for (String s : split) formulaList.add(s);
// set up operator -> SimpleLTL.LTLType mapping for the standard operators
Map<String, SimpleLTL.LTLType> unaryOps = new HashMap<String, SimpleLTL.LTLType>();
Map<String, SimpleLTL.LTLType> binaryOps = new HashMap<String, SimpleLTL.LTLType>();
unaryOps.put("!", SimpleLTL.LTLType.NOT);
unaryOps.put("F", SimpleLTL.LTLType.FINALLY);
unaryOps.put("G", SimpleLTL.LTLType.GLOBALLY);
unaryOps.put("X", SimpleLTL.LTLType.NEXT);
binaryOps.put("|", SimpleLTL.LTLType.OR);
binaryOps.put("&", SimpleLTL.LTLType.AND);
binaryOps.put("i", SimpleLTL.LTLType.IMPLIES);
binaryOps.put("e", SimpleLTL.LTLType.EQUIV);
binaryOps.put("U", SimpleLTL.LTLType.UNTIL);
binaryOps.put("V", SimpleLTL.LTLType.RELEASE);
SimpleLTL result = parseFormulaLBT(formulaList, unaryOps, binaryOps);
if (formulaList.size()>0) {
String remainingFormula = "";
for (String op : formulaList) remainingFormula += " "+op;
throw new RuntimeException("Malformed formula, extra information after end of formula: "+remainingFormula);
}
return result;
}
/**
* Parse a formula in LBT format, where the formula is already split into a list of operators / APs
* @param formulaList sequence of operators / APs
* @param unaryOps map for the standard unary ops
* @param binaryOps map for the standard binary ops
* @throws RuntimeException on parse error
*/
private static SimpleLTL parseFormulaLBT(List<String> formulaList,
Map<String, SimpleLTL.LTLType> unaryOps,
Map<String, SimpleLTL.LTLType> binaryOps) throws RuntimeException
{
if (formulaList.size() == 0) {
throw new RuntimeException("Malformed formula, premature ending");
}
String current = formulaList.get(0);
formulaList.remove(0);
if (current.equals("t")) {
return new SimpleLTL(true);
} else if (current.equals("f")) {
return new SimpleLTL(false);
} else if (unaryOps.containsKey(current)) {
// standard unary op
SimpleLTL operand = parseFormulaLBT(formulaList, unaryOps, binaryOps);
return new SimpleLTL(unaryOps.get(current), operand);
} else if (binaryOps.containsKey(current)) {
// standard binary op
SimpleLTL operand1 = parseFormulaLBT(formulaList, unaryOps, binaryOps);
SimpleLTL operand2 = parseFormulaLBT(formulaList, unaryOps, binaryOps);
return new SimpleLTL(binaryOps.get(current), operand1, operand2);
} else if (current.equals("W")) {
// a W b == !(a&!b U !a&!b)
SimpleLTL operand1 = parseFormulaLBT(formulaList, unaryOps, binaryOps);
SimpleLTL operand2 = parseFormulaLBT(formulaList, unaryOps, binaryOps);
SimpleLTL aAndNotb =
new SimpleLTL(SimpleLTL.LTLType.AND, operand1.clone(),
new SimpleLTL(SimpleLTL.LTLType.NOT, operand2.clone()));
SimpleLTL NotaAndNotb =
new SimpleLTL(SimpleLTL.LTLType.AND,
new SimpleLTL(SimpleLTL.LTLType.NOT, operand1.clone()),
new SimpleLTL(SimpleLTL.LTLType.NOT, operand2.clone()));
return new SimpleLTL(SimpleLTL.LTLType.NOT,
new SimpleLTL(SimpleLTL.LTLType.UNTIL, aAndNotb, NotaAndNotb));
} else if (current.equals("^")) {
// a xor b == !(a equiv b)
SimpleLTL operand1 = parseFormulaLBT(formulaList, unaryOps, binaryOps);
SimpleLTL operand2 = parseFormulaLBT(formulaList, unaryOps, binaryOps);
return new SimpleLTL(SimpleLTL.LTLType.NOT,
new SimpleLTL(SimpleLTL.LTLType.EQUIV, operand1, operand2));
} else if (current.equals("M") || current.equals("B")) {
throw new RuntimeException("Operator "+current+" currently not supported.");
} else if (current.matches("[a-zA-Z].*")) {
// atomic proposition
return new SimpleLTL(current);
} else {
throw new RuntimeException("Illegal/unsupported operator: "+current);
}
}
public NBA toNBA(APSet apset) throws PrismException
{
Alternating a = new Alternating(this, apset);
// a.print(System.out);
Generalized g = new Generalized(a);
// g.print(System.out, apset);
Buchi b = new Buchi(g);
// b.print_spin(System.out, apset);
NBA nba = b.toNBA(apset);
// nba.print(System.out);
// jltl2ba should never produce disjoint NBA,
// i.e., where some states are not reachable
// from the intial state, so we want to fail
// later if the NBA is discovered to be disjoint:
nba.setFailIfDisjoint(true);
return nba;
}
public NBA toNBA() throws PrismException
{
return this.toNBA(new APSet());
}
/** Print a DOT representation of the syntax tree of this SimpleLTL formula */
public void toDot(PrintStream out) {
IdentityHashMap<SimpleLTL, String> map = new IdentityHashMap<SimpleLTL, String>();
out.println("digraph {");
toDot(out, map);
out.println("}");
}
/**
* Print a DOT representation of the syntax tree of this SimpleLTL formula.
*
* @param out the output print stream
* @param seen a map storing an identifier for each subformula that has already been seen / printed
*/
private String toDot(PrintStream out, IdentityHashMap<SimpleLTL, String> seen)
{
String id = seen.get(this);
if (id != null) {
return id;
}
id = Integer.toString(seen.size());
seen.put(this, id);
out.println(id + " [label=\""+toStringLBT()+"\"]");
switch (kind) {
case AND:
case EQUIV:
case OR:
case UNTIL:
case RELEASE:
case IMPLIES: {
String leftID = left.toDot(out, seen);
String rightID = right.toDot(out, seen);
out.println(id + " -> " + leftID);
out.println(id + " -> " + rightID);
break;
}
case FINALLY:
case GLOBALLY:
case NEXT:
case NOT: {
String leftID = left.toDot(out, seen);
out.println(id + " -> " + leftID);
break;
}
case AP:
case TRUE:
case FALSE:
break;
default:
break;
}
return id;
}
}