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.
482 lines
18 KiB
482 lines
18 KiB
//==============================================================================
|
|
//
|
|
// Copyright (c) 2013-
|
|
// Authors:
|
|
// * Ernst Moritz Hahn <emhahn@cs.ox.ac.uk> (University of Oxford)
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// 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 param;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import parser.State;
|
|
import parser.ast.ConstantList;
|
|
import parser.ast.Expression;
|
|
import parser.ast.ExpressionBinaryOp;
|
|
import parser.ast.ExpressionConstant;
|
|
import parser.ast.ExpressionFunc;
|
|
import parser.ast.ExpressionITE;
|
|
import parser.ast.ExpressionLiteral;
|
|
import parser.ast.ExpressionUnaryOp;
|
|
import parser.ast.ModulesFile;
|
|
import parser.visitor.ASTTraverseModify;
|
|
import prism.ModelType;
|
|
import prism.PrismComponent;
|
|
import prism.PrismException;
|
|
import prism.PrismLangException;
|
|
import prism.PrismSettings;
|
|
import prism.PrismNotSupportedException;
|
|
import explicit.IndexedSet;
|
|
import explicit.StateStorage;
|
|
|
|
/**
|
|
* Class to construct a parametric Markov model.
|
|
*
|
|
* @author Ernst Moritz Hahn <emhahn@cs.ox.ac.uk> (University of Oxford)
|
|
* @see ParamModel
|
|
*/
|
|
public final class ModelBuilder extends PrismComponent
|
|
{
|
|
/** {@code ModulesFile} to be transformed to a {@code ParamModel} */
|
|
private ModulesFile modulesFile;
|
|
/** parametric model constructed from {@code modulesFile} */
|
|
private ParamModel model;
|
|
/** function factory used in the constructed parametric model */
|
|
private FunctionFactory functionFactory;
|
|
/** names of parameters */
|
|
private String[] paramNames;
|
|
/** lower bounds of parameters */
|
|
private BigRational[] lower;
|
|
/** upper bounds of parameters */
|
|
private BigRational[] upper;
|
|
/** name of function type to use, as read from PRISM settings */
|
|
private String functionType;
|
|
/** maximal error probability of DAG function representation */
|
|
private double dagMaxError;
|
|
|
|
/** local storage made static for use in anonymous class */
|
|
private static Map<String,Expression> constExprs;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public ModelBuilder(PrismComponent parent) throws PrismException
|
|
{
|
|
super(parent);
|
|
// If present, initialise settings from PrismSettings
|
|
if (settings != null) {
|
|
functionType = settings.getString(PrismSettings.PRISM_PARAM_FUNCTION);
|
|
dagMaxError = settings.getDouble(PrismSettings.PRISM_PARAM_DAG_MAX_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transform PRISM expression to rational function.
|
|
* If successful, a function representing the given expression will be
|
|
* constructed. This is however not always possible, as not each PRISM
|
|
* expression can be represented as a rational function. In this case
|
|
* a {@code PrismException} will be thrown.
|
|
*
|
|
* @param factory function factory used to construct function
|
|
* @param expr PRISM expression to transform to rational function
|
|
* @return rational function representing the given PRISM expression
|
|
* @throws PrismException thrown if {@code expr} cannot be represented as rational function
|
|
*/
|
|
Function expr2function(FunctionFactory factory, Expression expr) throws PrismException
|
|
{
|
|
if (expr instanceof ExpressionLiteral) {
|
|
String exprString = ((ExpressionLiteral) expr).getString();
|
|
if (exprString == null || exprString.equals("")) {
|
|
throw new PrismException("cannot convert from literal " + "for which no string is set");
|
|
}
|
|
return factory.fromBigRational(new BigRational(exprString));
|
|
} else if (expr instanceof ExpressionConstant) {
|
|
String exprString = ((ExpressionConstant) expr).getName();
|
|
int index = modulesFile.getConstantList().getConstantIndex(exprString);
|
|
if (index != -1) {
|
|
Expression constExpr = modulesFile.getConstantList().getConstant(index);
|
|
if (constExpr != null) {
|
|
return expr2function(factory, constExpr);
|
|
} else {
|
|
return factory.getVar(exprString);
|
|
}
|
|
} else {
|
|
throw new PrismException("Invalid parametric constant definition used");
|
|
}
|
|
} else if (expr instanceof ExpressionBinaryOp) {
|
|
ExpressionBinaryOp binExpr = ((ExpressionBinaryOp) expr);
|
|
Function f1 = expr2function(factory, binExpr.getOperand1());
|
|
Function f2 = expr2function(factory, binExpr.getOperand2());
|
|
switch (binExpr.getOperator()) {
|
|
case ExpressionBinaryOp.PLUS:
|
|
return f1.add(f2);
|
|
case ExpressionBinaryOp.MINUS:
|
|
return f1.subtract(f2);
|
|
case ExpressionBinaryOp.TIMES:
|
|
return f1.multiply(f2);
|
|
case ExpressionBinaryOp.DIVIDE:
|
|
return f1.divide(f2);
|
|
default:
|
|
throw new PrismNotSupportedException("parametric analysis with rate/probability of " + expr + " not implemented");
|
|
}
|
|
} else if (expr instanceof ExpressionUnaryOp) {
|
|
ExpressionUnaryOp unExpr = ((ExpressionUnaryOp) expr);
|
|
Function f = expr2function(factory, unExpr.getOperand());
|
|
switch (unExpr.getOperator()) {
|
|
case ExpressionUnaryOp.MINUS:
|
|
return f.negate();
|
|
case ExpressionUnaryOp.PARENTH:
|
|
return f;
|
|
default:
|
|
throw new PrismNotSupportedException("parametric analysis with rate/probability of " + expr + " not implemented");
|
|
}
|
|
} else if (expr instanceof ExpressionITE){
|
|
ExpressionITE iteExpr = (ExpressionITE) expr;
|
|
// ITE expressions where the if-expression does not
|
|
// depend on a parametric constant are supported
|
|
if (iteExpr.getOperand1().isConstant()) {
|
|
try {
|
|
// non-parametric constants and state variable values have
|
|
// been already partially expanded, so if this evaluation
|
|
// succeeds there are no parametric constants involved
|
|
boolean ifValue = iteExpr.getOperand1().evaluateBoolean();
|
|
if (ifValue) {
|
|
return expr2function(factory, iteExpr.getOperand2());
|
|
} else {
|
|
return expr2function(factory, iteExpr.getOperand3());
|
|
}
|
|
} catch (PrismException e) {
|
|
// Most likely, a parametric constant occurred.
|
|
// Do nothing here, exception is thrown below
|
|
}
|
|
}
|
|
throw new PrismNotSupportedException("parametric analysis with rate/probability of " + expr + " not implemented");
|
|
} else if (expr instanceof ExpressionFunc) {
|
|
// functions (min, max, floor, ...) are supported if
|
|
// they don't refer to parametric constants in their arguments
|
|
// and can be exactly evaluated
|
|
try {
|
|
// non-parametric constants and state variable values have
|
|
// been already partially expanded, so if this evaluation
|
|
// succeeds there are no parametric constants involved
|
|
BigRational value = expr.evaluateExact();
|
|
return factory.fromBigRational(value);
|
|
} catch (PrismException e) {
|
|
// Most likely, a parametric constant occurred.
|
|
throw new PrismNotSupportedException("parametric analysis with rate/probability of " + expr + " not implemented");
|
|
}
|
|
} else {
|
|
throw new PrismNotSupportedException("parametric analysis with rate/probability of " + expr + " not implemented");
|
|
}
|
|
}
|
|
|
|
// setters and getters
|
|
|
|
/**
|
|
* Set modules file to be transformed to parametric Markov model.
|
|
*
|
|
* @param modulesFile modules file to be transformed to parametric Markov model
|
|
*/
|
|
public void setModulesFile(ModulesFile modulesFile)
|
|
{
|
|
this.modulesFile = modulesFile;
|
|
}
|
|
|
|
/**
|
|
* Set parameter informations.
|
|
* Obviously, all of {@code paramNames}, {@code lower}, {@code} upper
|
|
* must have the same length, and {@code lower} bounds of parameters must
|
|
* not be higher than {@code upper} bounds.
|
|
*
|
|
* @param paramNames names of parameters
|
|
* @param lower lower bounds of parameters
|
|
* @param upper upper bounds of parameters
|
|
*/
|
|
public void setParameters(String[] paramNames, String[] lower, String[] upper)
|
|
{
|
|
this.paramNames = paramNames;
|
|
this.lower = new BigRational[lower.length];
|
|
this.upper = new BigRational[upper.length];
|
|
for (int param = 0; param < lower.length; param++) {
|
|
this.lower[param] = new BigRational(lower[param]);
|
|
this.upper[param] = new BigRational(upper[param]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the parameter names as a list of strings.
|
|
* @return the parameter names
|
|
*/
|
|
public List<String> getParameterNames()
|
|
{
|
|
return Arrays.asList(paramNames);
|
|
}
|
|
|
|
/**
|
|
* Construct parametric Markov model.
|
|
* For this to work, module file, PRISM log, etc. must have been set
|
|
* beforehand.
|
|
*
|
|
* @throws PrismException in case the model cannot be constructed
|
|
*/
|
|
public void build() throws PrismException
|
|
{
|
|
if (functionType.equals("JAS")) {
|
|
functionFactory = new JasFunctionFactory(paramNames, lower, upper);
|
|
} else if (functionType.equals("JAS-cached")) {
|
|
functionFactory = new CachedFunctionFactory(new JasFunctionFactory(paramNames, lower, upper));
|
|
} else if (functionType.equals("DAG")) {
|
|
functionFactory = new DagFunctionFactory(paramNames, lower, upper, dagMaxError, false);
|
|
}
|
|
long time;
|
|
|
|
if (modulesFile.getModelType() == ModelType.PTA) {
|
|
throw new PrismNotSupportedException("You cannot build a PTA model explicitly, only perform model checking");
|
|
}
|
|
|
|
mainLog.print("\nBuilding model...\n");
|
|
|
|
// build model
|
|
time = System.currentTimeMillis();
|
|
// First, set values for any constants in the model
|
|
// (but do this *symbolically* - partly because some constants are parameters and therefore unknown,
|
|
// but also to keep values like 1/3 as expressions rather than being converted to doubles,
|
|
// resulting in a loss of precision)
|
|
ConstantList constantList = modulesFile.getConstantList();
|
|
constExprs = constantList.evaluateConstantsPartially(modulesFile.getUndefinedConstantValues(), null);
|
|
modulesFile = (ModulesFile) modulesFile.deepCopy();
|
|
modulesFile = (ModulesFile) modulesFile.accept(new ASTTraverseModify()
|
|
{
|
|
public Object visit(ExpressionConstant e) throws PrismLangException
|
|
{
|
|
Expression expr = constExprs.get(e.getName());
|
|
return (expr != null) ? expr.deepCopy() : e;
|
|
}
|
|
});
|
|
ParamModel modelExpl = constructModel(modulesFile);
|
|
time = System.currentTimeMillis() - time;
|
|
|
|
mainLog.print("\n"+modelExpl.infoStringTable());
|
|
|
|
mainLog.println("\nTime for model construction: " + time / 1000.0 + " seconds.");
|
|
model = modelExpl;
|
|
}
|
|
|
|
/**
|
|
* Returns the constructed parametric Markov model.
|
|
*
|
|
* @return constructed parametric Markov model
|
|
*/
|
|
public explicit.Model getModel()
|
|
{
|
|
return model;
|
|
}
|
|
|
|
/**
|
|
* Reserves memory needed for parametric model and reserves necessary space.
|
|
* Afterwards, transition probabilities etc. can be added.
|
|
*
|
|
* @param modulesFile modules file of which to explore states
|
|
* @param model model in which to reserve memory
|
|
* @param modelType type of the model to construct
|
|
* @param engine the engine used to compute state successors, etc.
|
|
* @param states list of states to be filled by this method
|
|
* @throws PrismException thrown if problems in underlying methods occur
|
|
*/
|
|
private void reserveMemoryAndExploreStates(ModulesFile modulesFile, ParamModel model, ModelType modelType, SymbolicEngine engine, StateStorage<State> states)
|
|
throws PrismException
|
|
{
|
|
boolean isNonDet = modelType == ModelType.MDP;
|
|
int numStates = 0;
|
|
int numTotalChoices = 0;
|
|
int numTotalSuccessors = 0;
|
|
|
|
LinkedList<State> explore = new LinkedList<State>();
|
|
|
|
State state = modulesFile.getDefaultInitialState();
|
|
states.add(state);
|
|
explore.add(state);
|
|
numStates++;
|
|
|
|
while (!explore.isEmpty()) {
|
|
state = explore.removeFirst();
|
|
TransitionList tranlist = engine.calculateTransitions(state, true); // Suppress warnings
|
|
int numChoices = tranlist.getNumChoices();
|
|
if (isNonDet) {
|
|
numTotalChoices += numChoices;
|
|
} else {
|
|
numTotalChoices += 1;
|
|
}
|
|
for (int choiceNr = 0; choiceNr < numChoices; choiceNr++) {
|
|
int numSuccessors = tranlist.getChoice(choiceNr).size();
|
|
numTotalSuccessors += numSuccessors;
|
|
for (int succNr = 0; succNr < numSuccessors; succNr++) {
|
|
State stateNew = tranlist.getChoice(choiceNr).computeTarget(succNr, state);
|
|
if (states.add(stateNew)) {
|
|
numStates++;
|
|
explore.add(stateNew);
|
|
states.add(stateNew);
|
|
}
|
|
}
|
|
}
|
|
if (numChoices == 0) {
|
|
if (isNonDet) {
|
|
numTotalChoices++;
|
|
}
|
|
numTotalSuccessors++;
|
|
}
|
|
}
|
|
|
|
model.reserveMem(numStates, numTotalChoices, numTotalSuccessors);
|
|
}
|
|
|
|
/**
|
|
* Construct model once function factory etc. has been allocated.
|
|
*
|
|
* @param modulesFile modules file of which to construct parametric model
|
|
* @return parametric model constructed
|
|
* @throws PrismException thrown if model cannot be constructed
|
|
*/
|
|
private ParamModel constructModel(ModulesFile modulesFile) throws PrismException
|
|
{
|
|
ModelType modelType;
|
|
|
|
if (modulesFile.getInitialStates() != null) {
|
|
throw new PrismNotSupportedException("Cannot do explicit-state reachability if there are multiple initial states");
|
|
}
|
|
|
|
boolean doProbChecks = getSettings().getBoolean(PrismSettings.PRISM_DO_PROB_CHECKS);
|
|
|
|
mainLog.print("\nComputing reachable states...");
|
|
mainLog.flush();
|
|
long timer = System.currentTimeMillis();
|
|
modelType = modulesFile.getModelType();
|
|
ParamModel model = new ParamModel();
|
|
model.setModelType(modelType);
|
|
if (modelType != ModelType.DTMC && modelType != ModelType.CTMC && modelType != ModelType.MDP) {
|
|
throw new PrismNotSupportedException("Unsupported model type: " + modelType);
|
|
}
|
|
SymbolicEngine engine = new SymbolicEngine(modulesFile, this, functionFactory);
|
|
|
|
if (modulesFile.getInitialStates() != null) {
|
|
throw new PrismNotSupportedException("Explicit model construction does not support multiple initial states");
|
|
}
|
|
|
|
boolean isNonDet = modelType == ModelType.MDP;
|
|
boolean isContinuous = modelType == ModelType.CTMC;
|
|
StateStorage<State> states = new IndexedSet<State>(true);
|
|
reserveMemoryAndExploreStates(modulesFile, model, modelType, engine, states);
|
|
int[] permut = states.buildSortingPermutation();
|
|
List<State> statesList = states.toPermutedArrayList(permut);
|
|
model.setStatesList(statesList);
|
|
model.addInitialState(permut[0]);
|
|
int stateNr = 0;
|
|
for (State state : statesList) {
|
|
TransitionList tranlist = engine.calculateTransitions(state, false);
|
|
int numChoices = tranlist.getNumChoices();
|
|
|
|
boolean computeSumOut = !isNonDet;
|
|
boolean checkChoiceSumEqualsOne = doProbChecks && model.getModelType().choicesSumToOne();
|
|
|
|
// sumOut = the sum over all outgoing choices from this state
|
|
Function sumOut = functionFactory.getZero();
|
|
for (int choiceNr = 0; choiceNr < numChoices; choiceNr++) {
|
|
ChoiceListFlexi choice = tranlist.getChoice(choiceNr);
|
|
int numSuccessors = choice.size();
|
|
|
|
// sumOutForChoice = the sum over all outgoing transitions for this choice
|
|
Function sumOutForChoice = functionFactory.getZero();
|
|
for (int succNr = 0; succNr < numSuccessors; succNr++) {
|
|
ChoiceListFlexi succ = tranlist.getChoice(choiceNr);
|
|
Function probFunc = succ.getProbability(succNr);
|
|
if (computeSumOut)
|
|
sumOut = sumOut.add(probFunc);
|
|
if (checkChoiceSumEqualsOne)
|
|
sumOutForChoice = sumOutForChoice.add(probFunc);
|
|
}
|
|
if (checkChoiceSumEqualsOne) {
|
|
if (!sumOutForChoice.equals(functionFactory.getOne())) {
|
|
if (sumOutForChoice.isConstant()) {
|
|
// as the sum is constant, we know that it is really not 1
|
|
throw new PrismLangException("Probabilities sum to " + sumOutForChoice.asBigRational() + " instead of 1 in state "
|
|
+ state.toString(modulesFile) + " for some command");
|
|
} else {
|
|
throw new PrismLangException("In state " + state.toString(modulesFile) + " the probabilities sum to "
|
|
+ sumOutForChoice + " for some command, which can not be determined to be equal to 1 (to ignore, use -noprobchecks option)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sumOut.isZero()) {
|
|
// set sumOut to 1 for deadlock state or if we are in nonDet model
|
|
sumOut = functionFactory.getOne();
|
|
}
|
|
for (int choiceNr = 0; choiceNr < numChoices; choiceNr++) {
|
|
ChoiceListFlexi choice = tranlist.getChoice(choiceNr);
|
|
int a = tranlist.getTransitionModuleOrActionIndex(tranlist.getTotalIndexOfTransition(choiceNr, 0));
|
|
String action = a < 0 ? null : modulesFile.getSynch(a - 1);
|
|
int numSuccessors = choice.size();
|
|
for (int succNr = 0; succNr < numSuccessors; succNr++) {
|
|
ChoiceListFlexi succ = tranlist.getChoice(choiceNr);
|
|
State stateNew = succ.computeTarget(succNr, state);
|
|
Function probFn = succ.getProbability(succNr);
|
|
// divide by sumOut
|
|
// for DTMC, this normalises over the choices
|
|
// for CTMC this builds the embedded DTMC
|
|
// for MDP this does nothing (sumOut is set to 1)
|
|
probFn = probFn.divide(sumOut);
|
|
model.addTransition(permut[states.get(stateNew)], probFn, action);
|
|
}
|
|
if (isNonDet) {
|
|
model.setSumLeaving(isContinuous ? sumOut : functionFactory.getOne());
|
|
model.finishChoice();
|
|
}
|
|
}
|
|
if (numChoices == 0) {
|
|
model.addDeadlockState(stateNr);
|
|
model.addTransition(stateNr, functionFactory.getOne(), null);
|
|
if (isNonDet) {
|
|
model.setSumLeaving(isContinuous ? sumOut : functionFactory.getOne());
|
|
model.finishChoice();
|
|
}
|
|
}
|
|
if (!isNonDet) {
|
|
model.setSumLeaving(isContinuous ? sumOut : functionFactory.getOne());
|
|
model.finishChoice();
|
|
}
|
|
model.finishState();
|
|
stateNr++;
|
|
}
|
|
model.setFunctionFactory(functionFactory);
|
|
|
|
mainLog.println();
|
|
|
|
mainLog.print("Reachable states exploration and model construction");
|
|
mainLog.println(" done in " + ((System.currentTimeMillis() - timer) / 1000.0) + " secs.");
|
|
|
|
return model;
|
|
}
|
|
}
|