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.
 
 
 
 
 
 

552 lines
17 KiB

//==============================================================================
//
// Copyright (c) 2002-
// Authors:
// * Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford, formerly University of Birmingham)
//
//------------------------------------------------------------------------------
//
// This file is part of PRISM.
//
// PRISM 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.
//
// PRISM 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 PRISM; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//==============================================================================
package pta;
import java.util.*;
import parser.*;
import parser.ast.*;
import parser.type.*;
import parser.visitor.*;
import prism.*;
/**
* Class that converts a PRISM modelling language description
* of a PTA into a PRISM model of a PTA, through digital clocks
*/
public class DigitalClocks
{
// Prism object
private Prism prism;
// Log
private PrismLog mainLog;
// Model to be converted
private ModulesFile modulesFile;
// Properties to be converted
private PropertiesFile propertiesFile;
// Constants from model
private Values constantValues;
// Variable list for model
private VarList varList;
// Flags + settings
private boolean doScaling = true;
// Object for computing max clock constraints
private ComputeClockInformation cci;
// String to be used for time action
private String timeAction;
// Some invariant info
private Expression allInVariants = null;
// Translated model file
private ModulesFile mf;
// Translated properties file
private PropertiesFile pf;
/**
* Constructor.
*/
public DigitalClocks(Prism prism)
{
this.prism = prism;
mainLog = prism.getMainLog();
mf = null;
pf = null;
}
/**
* Get translated model file.
*/
public ModulesFile getNewModulesFile()
{
return mf;
}
/**
* Get translated properties file.
*/
public PropertiesFile getNewPropertiesFile()
{
return pf;
}
/**
* Main method - translate.
*/
public void translate(ModulesFile modulesFile, PropertiesFile propertiesFile, Expression propertyToCheck) throws PrismLangException
{
int i, n;
ASTElement ast;
ASTTraverseModify asttm;
mainLog.println("\nPerforming digital clocks translation...");
// Store model/properties files
this.modulesFile = modulesFile;
this.propertiesFile = propertiesFile;
constantValues = modulesFile.getConstantValues();
// TODO: need property constants too?
varList = modulesFile.createVarList();
// Check that model does not contain any closed clock constraints
ast = findAStrictClockConstraint(modulesFile);
if (ast != null)
throw new PrismLangException("Strict clock constraints are not allowed when using the digital clocks method", ast);
// Check that model does not contain any diagonal clock constraints
// (for now; should be able to relax this later)
ast = findADiagonalClockConstraint(modulesFile);
if (ast != null)
throw new PrismLangException("Diagonal clock constraints are not allowed when using the digital clocks method", ast);
// Check for any references to clocks in rewards structures - not allowed.
for (RewardStruct rs : modulesFile.getRewardStructs()) {
rs.accept(new ASTTraverseModify()
{
public Object visit(ExpressionVar e) throws PrismLangException
{
if (e.getType() instanceof TypeClock) {
throw new PrismLangException("Reward structures cannot contain references to clocks", e);
} else {
return e;
}
}
});
}
// Check that the property is suitable for checking with digital clocks
if (propertyToCheck != null)
checkProperty(propertyToCheck);
// Choose a new action label to represent time
timeAction = "time";
while (modulesFile.getSynchs().contains(timeAction)) {
timeAction += "_";
}
// Extract information about clocks from the model
cci = new ComputeClockInformation();
modulesFile.accept(cci);
mainLog.println("Computed clock maximums: " + cci.clockMaxs);
if (doScaling)
mainLog.println("Computed GCD: " + cci.getScaleFactor());
// Take a copy of the whole model/properties file before translation
mf = (ModulesFile) modulesFile.deepCopy();
pf = (propertiesFile == null) ? null : (PropertiesFile) propertiesFile.deepCopy();
// Change the model type
mf.setModelType(ModelType.MDP);
// Change all clock variable declarations to bounded integers
mf = (ModulesFile) mf.accept(new ASTTraverseModify()
{
public Object visit(Declaration e) throws PrismLangException
{
if (e.getDeclType() instanceof DeclarationClock) {
int cMax = cci.getScaledClockMax(e.getName());
if (cMax < 0)
throw new PrismLangException("Clock " + e.getName() + " is unbounded since there are no references to it in the model");
DeclarationType declType = new DeclarationInt(Expression.Int(0), Expression.Int(cMax + 1));
Declaration decl = new Declaration(e.getName(), declType);
return decl;
} else {
return e;
}
}
});
// Add time command based on invariant for each module
// Also build an "invariants" label, as we go
allInVariants = null;
mf = (ModulesFile) mf.accept(new ASTTraverseModify()
{
public Object visit(Module e) throws PrismLangException
{
Command timeCommand;
Updates ups;
Update up;
int cMax;
Expression invar;
ExpressionFunc expr;
// Get (clock) invariant for module; create default if none
invar = e.getInvariant();
invar = (invar == null) ? Expression.True() : invar.deepCopy();
// Collect invariant for "invariants" label
if (!Expression.isTrue(invar))
allInVariants = (allInVariants == null) ? invar.deepCopy() : Expression.And(allInVariants, invar.deepCopy());
// Replace all clocks x with x+1 in invariant
invar = (Expression) invar.accept(new ASTTraverseModify()
{
public Object visit(ExpressionVar e) throws PrismLangException
{
if (e.getType() instanceof TypeClock) {
return Expression.Plus(e, Expression.Int(1));
} else {
return e;
}
}
});
// Construct command representing progression of time
timeCommand = new Command();
timeCommand.setSynch(timeAction);
// Guard comes from invariant
timeCommand.setGuard(invar);
// Update is constructed from clocks
up = new Update();
for (String x : cci.getClocksForModule(e.getName())) {
// Get clock max value
cMax = cci.getScaledClockMax(x);
// Build expression min(x+1,cMax)
expr = new ExpressionFunc("min");
expr.addOperand(Expression.Plus(new ExpressionVar(x, TypeInt.getInstance()), Expression.Int(1)));
expr.addOperand(Expression.Int(cMax + 1));
// Add to update
up.addElement(new ExpressionIdent(x), expr);
}
ups = new Updates();
ups.addUpdate(Expression.Double(1.0), up);
timeCommand.setUpdates(ups);
e.addCommand(timeCommand);
// Finally, remove invariant info
e.setInvariant(null);
// Return modified module
return e;
}
});
// Add "invariants" label
mf.getLabelList().addLabel(new ExpressionIdent("invariants"), allInVariants == null ? Expression.True() : allInVariants);
// Change the type of any clock variable references to int
// and scale the variable appropriately, if required
// (in both model and properties list)
asttm = new ASTTraverseModify()
{
// Resets
public Object visit(Update e) throws PrismLangException
{
int i, n;
ExpressionFunc exprFunc;
n = e.getNumElements();
for (i = 0; i < n; i++) {
if (e.getType(i) instanceof TypeClock) {
// Don't actually need to set the type here since
// will be done in subsequent call to tidyUp() but do it anyway.
e.setType(i, TypeInt.getInstance());
// Scaling is done with division here, rather than multiplying clock like elsewhere
if (cci.getScaleFactor() > 1) {
exprFunc = new ExpressionFunc("floor");
exprFunc.addOperand(Expression.Divide(e.getExpression(i), Expression.Int(cci.getScaleFactor())));
e.setExpression(i, (Expression) exprFunc.simplify());
}
}
}
return e;
}
// Variable accesses
public Object visit(ExpressionVar e) throws PrismLangException
{
if (e.getType() instanceof TypeClock) {
e.setType(TypeInt.getInstance());
if (!doScaling || cci.getScaleFactor() == 1)
return e;
return Expression.Times(e, Expression.Int(cci.getScaleFactor()));
}
return e;
}
};
mf = (ModulesFile) mf.accept(asttm);
if (pf != null)
pf = (PropertiesFile) pf.accept(asttm);
// Change state rewards in reward structures to use time action)
// (transition rewards can be left unchanged)
// Note: only cumulative (F) properties supported currently.
for (RewardStruct rs : mf.getRewardStructs()) {
n = rs.getNumItems();
for (i = 0; i < n; i++) {
RewardStructItem rsi = rs.getRewardStructItem(i);
// Convert state rewards
if (!rsi.isTransitionReward()) {
// Scale reward by clock GCD
Expression rew = rsi.getReward().deepCopy();
if (cci.getScaleFactor() > 1) {
rew = Expression.Times(rew, Expression.Int(cci.getScaleFactor()));
}
rsi = new RewardStructItem(timeAction, rsi.getStates().deepCopy(), rew);
rs.setRewardStructItem(i, rsi);
}
}
}
// Re-do type checking etc. on the model/properties
mf.tidyUp();
if (pf != null)
pf.tidyUp();
}
/**
* Check that a property is checkable with the digital clocks method.
* Throw an explanatory exception if not.
*/
public void checkProperty(Expression propertyToCheck) throws PrismLangException
{
ASTElement ast;
// Bounded properties not yet handled.
try {
propertyToCheck.accept(new ASTTraverse()
{
public void visitPost(ExpressionTemporal e) throws PrismLangException
{
if (e.getLowerBound() != null || e.getUpperBound() != null)
throw new PrismLangException("The digital clocks method does not yet support bounded properties");
}
});
} catch (PrismLangException e) {
e.setASTElement(propertyToCheck);
throw e;
}
// Check that there are no nested probabilistic operators
if (propertyToCheck.computeProbNesting() > 1) {
throw new PrismLangException("Nested P operators are not allowed when using the digital clocks method", propertyToCheck);
}
// Check for presence of strict clock constraints
ast = findAStrictClockConstraint(propertyToCheck);
if (ast != null)
throw new PrismLangException("Strict clock constraints are not allowed when using the digital clocks method", ast);
// Check for presence of diagonal clock constraints
// (for now; should be able to relax this later)
ast = findADiagonalClockConstraint(modulesFile);
if (ast != null)
throw new PrismLangException("Diagonal clock constraints are not allowed when using the digital clocks method", ast);
// TODO: also need to look in any required properties file labels
// (currently, these cannot even contain clocks so not an issue)
}
/**
* Look for a strict clock constraint. If found return the offending element. Else, return null.
*/
public ASTElement findAStrictClockConstraint(ASTElement ast) throws PrismLangException
{
try {
ASTTraverse astt = new ASTTraverse()
{
// Clock constraints
public void visitPost(ExpressionBinaryOp e) throws PrismLangException
{
if (e.getOperand1().getType() instanceof TypeClock) {
if (e.getOperator() == ExpressionBinaryOp.GT || e.getOperator() == ExpressionBinaryOp.LT)
throw new PrismLangException("Found one", e);
} else if (e.getOperand2().getType() instanceof TypeClock) {
if (e.getOperator() == ExpressionBinaryOp.GT || e.getOperator() == ExpressionBinaryOp.LT)
throw new PrismLangException("Found one", e);
}
}
};
ast.accept(astt);
} catch (PrismLangException e) {
return e.getASTElement();
}
return null;
}
/**
* Look for a diagonal clock constraint. If found return the offending element. Else, return null.
*/
public ASTElement findADiagonalClockConstraint(ASTElement ast) throws PrismLangException
{
try {
ASTTraverse astt = new ASTTraverse()
{
// Clock constraints
public void visitPost(ExpressionBinaryOp e) throws PrismLangException
{
if (e.getOperand1().getType() instanceof TypeClock) {
if (e.getOperand2().getType() instanceof TypeClock) {
throw new PrismLangException("Found one", e);
}
}
}
};
modulesFile.accept(astt);
} catch (PrismLangException e) {
return e.getASTElement();
}
return null;
}
/**
* Class to extract information about clocks:
* - list of clocks for each module;
* - maximum value that each clock is compared against or set to;
* - g.c.d. of all integers used in clock comparisons or assignments.
*/
class ComputeClockInformation extends ASTTraverse
{
private Map<String, List<String>> clockLists;
private List<String> currentClockList;
private Map<String, Integer> clockMaxs;
private Set<Integer> allClockVals;
private int scaleFactor;
public ComputeClockInformation()
{
clockLists = new HashMap<String, List<String>>();
clockMaxs = new HashMap<String, Integer>();
allClockVals = new HashSet<Integer>();
}
private void updateMax(String clock, int val)
{
Integer i = clockMaxs.get(clock);
if (i == null || val > i)
clockMaxs.put(clock, val);
}
public List<String> getClocksForModule(String module)
{
List<String> list = clockLists.get(module);
return (list == null) ? new ArrayList<String>() : list;
}
public int getClockMax(String clock)
{
Integer i = clockMaxs.get(clock);
return (i == null) ? -1 : i;
}
/**
* Get the maximum value of a clock, scaled wrt. GCD (if required)
* @param clock
* @return
*/
public int getScaledClockMax(String clock)
{
Integer i = clockMaxs.get(clock);
return (i == null) ? -1 : doScaling ? i / scaleFactor : i;
}
public int getScaleFactor()
{
return scaleFactor;
}
/**
* Compute greatest common divisor of several non-negative ints
*/
private int computeGCD(Iterable<Integer> ints)
{
int gcd = 0;
for (int x : ints) {
gcd = computeGCD(gcd, x);
}
if (gcd == 0) {
// For the case where clock set is empty or all zeros
gcd = 1;
}
return gcd;
}
/**
* Compute greatest common divisor of 2 non-negative ints
*/
private int computeGCD(int x, int y)
{
return (y == 0) ? x : computeGCD(y, x % y);
}
public void visitPost(ModulesFile e) throws PrismLangException
{
// When have traversed the model, compute GCDs and scale factor
scaleFactor = computeGCD(allClockVals);
}
public void visitPre(Module e) throws PrismLangException
{
// Create new array to store clocks for this module
currentClockList = new ArrayList<String>();
clockLists.put(e.getName(), currentClockList);
}
public void visitPost(Declaration e) throws PrismLangException
{
// Detect clock variable and store info
if (e.getDeclType() instanceof DeclarationClock) {
currentClockList.add(e.getName());
}
}
// Resets
public Object visit(Update e) throws PrismLangException
{
int i, n;
String clock;
int maxVal;
Collection<Integer> allVals;
n = e.getNumElements();
for (i = 0; i < n; i++) {
if (e.getType(i) instanceof TypeClock) {
clock = e.getVar(i);
maxVal = ParserUtils.findMaxForIntExpression(e.getExpression(i), varList, constantValues);
updateMax(clock, maxVal);
allVals = ParserUtils.findAllValsForIntExpression(e.getExpression(i), varList, constantValues);
allClockVals.addAll(allVals);
}
}
return e;
}
// Clock constraints
public void visitPost(ExpressionBinaryOp e) throws PrismLangException
{
// If this is a clock constraint, get and store max value
// (only look at x ~ c or c ~ x)
String clock;
int maxVal;
Collection<Integer> allVals;
if (e.getOperand1().getType() instanceof TypeClock) {
if (!(e.getOperand2().getType() instanceof TypeClock)) {
clock = ((ExpressionVar) e.getOperand1()).getName();
maxVal = ParserUtils.findMaxForIntExpression(e.getOperand2(), varList, constantValues);
updateMax(clock, maxVal);
allVals = ParserUtils.findAllValsForIntExpression(e.getOperand2(), varList, constantValues);
allClockVals.addAll(allVals);
}
} else if (e.getOperand2().getType() instanceof TypeClock) {
clock = ((ExpressionVar) e.getOperand2()).getName();
maxVal = ParserUtils.findMaxForIntExpression(e.getOperand1(), varList, constantValues);
updateMax(clock, maxVal);
allVals = ParserUtils.findAllValsForIntExpression(e.getOperand1(), varList, constantValues);
allClockVals.addAll(allVals);
}
}
}
}