//============================================================================== // // Copyright (c) 2002- // Authors: // * Dave Parker (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> clockLists; private List currentClockList; private Map clockMaxs; private Set allClockVals; private int scaleFactor; public ComputeClockInformation() { clockLists = new HashMap>(); clockMaxs = new HashMap(); allClockVals = new HashSet(); } private void updateMax(String clock, int val) { Integer i = clockMaxs.get(clock); if (i == null || val > i) clockMaxs.put(clock, val); } public List getClocksForModule(String module) { List list = clockLists.get(module); return (list == null) ? new ArrayList() : 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 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(); 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 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 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); } } } }