//============================================================================== // // Copyright (c) 2013- // Authors: // * Dave Parker (University of Oxford) // * Frits Dannenberg (University of Oxford) // * Ernst Moritz Hahn (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 explicit; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.ListIterator; import java.util.Map; import parser.ast.Expression; import parser.ast.ExpressionIdent; import parser.ast.LabelList; import parser.ast.RewardStruct; import parser.type.TypeDouble; import parser.Values; import parser.State; import prism.*; import prism.Model; /* * TODO * - add options for state removal, e.g. * - by delta (as current) * - by max probability loss per iteration (requires sort by prob) * - by max number of states (requires sort by prob) * - compress states to Bitset (memory waste currently excessive e.g. for mapk) * - do not delete states immediately but only after they have been below * delta for a specified number of iterations to avoid deleting and exploring * the same states over and over again * - dynamic adaption of interval width (with of 1 seems to work best, however) * - improve birth process - only worth it if we find case study where it makes a * difference, but could contribute to publicability then * - plot number of active states over time for mapk to get idea of model behaviour * - in gen-dat.pl, mark runs as dead if we can derive that they cannot succeed * - discuss public interface with Dave * - make stop of deletion after half of Birth threshold reached optional * - check whether it's worth storing incoming transitions rather than outgoing * would be faster, but edges are more costly to remove * - if we reach a point where we only delete states and don't add new ones, it * might make sense to switch to array representation and ignore the fact that * we could delete further states */ /** * Implementation of fast adaptive uniformisation (FAU). */ public final class FastAdaptiveUniformisation extends PrismComponent { /** * Stores properties of states needed for fast adaptive method. * This includes the current-step probability, next-state probability, * and the transient probability (sum of step probabilities weighted * with birth process distributions). It also contains the list of successor * states and the rates to them, the number of incoming transitions * (references) and a flag whether the state has a significant probability * mass (alive). */ private final static class StateProp { /** current-step probability. * should contain initial probability before actual analysis is started. * will contain transient probability after analysis. */ private double prob; /** next-state probability */ private double nextProb; /** sum probability weighted with birth process distribution */ private double sum; /** reward of this state */ private double reward; /** rates to successor states */ private double[] succRates; /** successor states */ private StateProp[] succStates; /** number of incoming transitions of relevant states */ private int references; /** true if and only if state probability above relevance threshold */ private boolean alive; /** * Constructs a new state property object. */ StateProp() { prob = 0.0; nextProb = 0.0; sum = 0.0; reward = 0.0; succRates = null; succStates = null; references = 0; alive = true; } /** * Set current state probability. * * @param prob current state probability to set */ void setProb(double prob) { this.prob = prob; } /** * Gets current state probability. * * @return current state probability */ double getProb() { return prob; } /** * Sets next state probability. * * @param nextProb next state probability to set */ void setNextProb(double nextProb) { this.nextProb = nextProb; } /** * Adds value to next state probability. * * @param add value to add to next state probability */ void addToNextProb(double add) { this.nextProb += add; } /** * Sets weighted sum probability. * * @param sum weighted sum probability to set. */ void setSum(double sum) { this.sum = sum; } /** * Adds current probability times {@code poisson} to weighted sum probability. * * @param poisson this value times current probability will be added to sum probability */ void addToSum(double poisson) { sum += poisson * prob; } /** * Gets weighted sum probability. * * @return weighted sum probability */ double getSum() { return sum; } /** * Prepares next iteration step. * Sets current probability to next probability, and sets next * probability to zero. */ void prepareNextIteration() { prob = nextProb; nextProb = 0.0; } /** * Set state reward. * * @param reward state reward to set */ void setReward(double reward) { this.reward = reward; } /** * Get state reward. * * @return state reward */ double getReward() { return reward; } /** * Sets rates to successor states. * Expects an array of rates, so that the rate to the successor * state set by {@setSuccStates} is the one given by the corresponding * index. The value {@code null} is allowed here. * * @param succRates rates to successor states. */ void setSuccRates(double[] succRates) { this.succRates = succRates; } /** * Sets successor states. * Expects an array of successor states, so that the rate set by * {@setSuccRates} is the one given by the corresponding index. * The value {@code null} is allowed here. * * @param succStates successor states */ void setSuccStates(StateProp[] succStates) { this.succStates = succStates; if (succStates != null) { for (int succNr = 0; succNr < succStates.length; succNr++) { succStates[succNr].incReferences(); } } } /** * Returns number of successor states of this state. * * @return number of successor states */ int getNumSuccs() { if (succRates == null) { return 0; } else { return succRates.length; } } /** * Gets successor rates. * * @return successor rates */ double[] getSuccRates() { return succRates; } /** * Gets successor states. * * @return successor states */ StateProp[] getSuccStates() { return succStates; } /** * Sets whether state is alive. * * @param alive whether state should be set to being alive */ void setAlive(boolean alive) { this.alive = alive; } /** * Checks whether state is alive. * * @return true iff state is alive */ boolean isAlive() { return alive; } /** * Increments the number of references of this state. * The number of references should correspond to the number of alive * states which have this state as successor state. */ void incReferences() { references++; } /** * Decrements the number of references of this state. * The number of references should correspond to the number of alive * states which have this state as successor state. */ void decReferences() { references--; } /** * Deletes this state. * This means basically removing all of its successors. Beforehand, * their reference counter is decreased, because this state does no * longer count as a model state. It is left in the model however, * because it might still be the successor state of some alive state. */ void delete() { if (null != succStates) { for (int succNr = 0; succNr < succStates.length; succNr++) { succStates[succNr].decReferences(); } } succStates = null; succRates = null; alive = false; prob = 0.0; nextProb = 0.0; } /** * Checks whether this state can be removed. * This is only the case if its probability is below the threshold * specified, and then only if there are no transitions from alive * states into this state. * * @return true if and only if this state can be removed */ boolean canRemove() { return !alive && (0 == references); } /** * Checks whether this state has successors or not. * Will be true if and only if successor state array is nonnull. * * @return whether this state has successors or not */ boolean hasSuccs() { return succStates != null; } /** * Returns the sum of all rates leaving to successor states. * * @return sum of all rates leaving to successor states */ double sumRates() { if (null == succRates) { return 0.0; } double sumRates = 0.0; for (int rateNr = 0; rateNr < succRates.length; rateNr++) { sumRates += succRates[rateNr]; } return sumRates; } } /** * Enum to store type of analysis to perform. */ public enum AnalysisType { /** transient probability distribution */ TRANSIENT, /** reachability, for "F" or "U" PCTL operators */ REACH, /** instantaneous rewards */ REW_INST, /** cumulative rewards */ REW_CUMUL } /** model exploration component to generate new states */ private ModelGenerator modelGen; /** probability allowed to drop birth process */ private double epsilon; /** probability threshold when to drop states in discrete-time process */ private double delta; /** number of intervals to divide time into */ private int numIntervals; /** iterations after which switch to sparse matrix if no new/dropped states */ private int arrayThreshold; /** reward structure to use for analysis */ private RewardStruct rewStruct = null; /** result value of analysis */ private double value; /** model constants */ private Values constantValues = null; /** maps from state (assignment of variable values) to property object */ private LinkedHashMap states; /** states for which successor rates are to be computed */ private ArrayList addDistr; /** states which are to be deleted */ private ArrayList deleteStates; /** initial size of state hash map */ private final int initSize = 3000; /** maximal total leaving rate of all states alive */ private double maxRate = 0.0; /** target state set - used for reachability (until or finally properties) */ private Expression target; /** number of consecutive iterations without new states are state drops */ private int itersUnchanged; /** sum of probabilities in stages of birth process seen so far */ private double birthProbSum; /** birth process used for time discretisation */ private BirthProcess birthProc; /** states which fulfill this will be made absorbing - for until props */ private Expression sink; /** if true, don't drop further states. * Used to avoid excessive probability loss in some cases. */ private boolean keepSumProb; /** maximal number of states ever stored during analysis */ private int maxNumStates; /** list of special labels we need to maintain, like "init", "deadlock", etc. */ private LabelList specialLabels; /** set of initial states of the model */ private HashSet initStates; /** type of analysis to perform */ private AnalysisType analysisType; /** total loss of probability in discrete-time process */ private double totalProbLoss; /** probability mass intentionally set to zero */ private double totalProbSetZero; /** * Constructor. */ public FastAdaptiveUniformisation(PrismComponent parent, ModelGenerator modelGen) throws PrismException { super(parent); this.modelGen = modelGen; maxNumStates = 0; epsilon = settings.getDouble(PrismSettings.PRISM_FAU_EPSILON); delta = settings.getDouble(PrismSettings.PRISM_FAU_DELTA); numIntervals = settings.getInteger(PrismSettings.PRISM_FAU_INTERVALS); arrayThreshold = settings.getInteger(PrismSettings.PRISM_FAU_ARRAYTHRESHOLD); analysisType = AnalysisType.TRANSIENT; rewStruct = null; target = Expression.False(); sink = Expression.False(); specialLabels = new LabelList(); specialLabels.addLabel(new ExpressionIdent("deadlock"), new ExpressionIdent("deadlock")); specialLabels.addLabel(new ExpressionIdent("init"), new ExpressionIdent("init")); } /** * Sets analysis type to perform. * * @param analysisType analysis type to perform */ public void setAnalysisType(AnalysisType analysisType) { this.analysisType = analysisType; } /** * Sets values for model constants. * * @param constantValues values for model constants */ public void setConstantValues(Values constantValues) { this.constantValues = constantValues; } /** * Sets reward structure to use. * * @param rewStruct reward structure to use */ public void setRewardStruct(RewardStruct rewStruct) { this.rewStruct = rewStruct; } /** * * @param target */ public void setTarget(Expression target) { this.target = target; } /** * Returns maximal number of states used during analysis. * * @return maximal number of states used during analysis */ public int getMaxNumStates() { return maxNumStates; } /** * Returns the value of the analysis. * For reachability analyses, this is the probability to reach state in * the reach set, for instantaneous reward properties this is the * instantaneous reward and for cumulative reward analysis it is the * cumulative reward. For the computation of transient probabilities * without doing model checking, this value is not significant. * * @return value of the analysis */ public double getValue() { return value; } /** * Sets which states shall be treated as sink states. * To be used for properties like "a U<=T b" where states "b || !a" have * to be made absorbing. * * @param sink expressing stating which states are sink states * @throws PrismException thrown if problems in underlying function occurs */ public void setSink(Expression sink) throws PrismException { this.sink = sink; if (states != null) { for (Map.Entry statePair : states.entrySet()) { State state = statePair.getKey(); StateProp prop = statePair.getValue(); modelGen.exploreState(state); specialLabels.setLabel(0, modelGen.getNumTransitions() == 0 ? Expression.True() : Expression.False()); specialLabels.setLabel(1, initStates.contains(state) ? Expression.True() : Expression.False()); Expression evSink = sink.deepCopy(); evSink = (Expression) evSink.expandLabels(specialLabels); if (evSink.evaluateBoolean(constantValues, state)) { double[] succRates = new double[1]; StateProp[] succStates = new StateProp[1]; succRates[0] = 1.0; succStates[0] = states.get(state); prop.setSuccRates(succRates); prop.setSuccStates(succStates); } } } } /** * Get the number of states in the current window. */ public int getNumStates() { return states.size(); } /** * Compute transient probability distribution (forwards). * Start from initial state (or uniform distribution over multiple initial states). */ public StateValues doTransient(double time) throws PrismException { return doTransient(time, (StateValues) null); } /** * Compute transient probability distribution (forwards). * Optionally, use the passed in file initDistFile to give the initial probability distribution (time 0). * If null, start from initial state (or uniform distribution over multiple initial states). * @param t Time point * @param initDistFile File containing initial distribution * @param currentModel */ public StateValues doTransient(double t, File initDistFile, Model model) throws PrismException { StateValues initDist = null; if (initDistFile != null) { int numValues = countNumStates(initDistFile); initDist = new StateValues(TypeDouble.getInstance(), numValues); initDist.readFromFile(initDistFile); } return doTransient(t, initDist); } /** * Counts number of states in a file. * We need this function because the functions to read values into * StateValues object expect that these objects have been initialised with * the right number of states. * * @param file file to count states of * @return number of states in file * @throws PrismException thrown in case of I/O errors */ private int countNumStates(File file) throws PrismException { BufferedReader in; String s; int lineNum = 0, count = 0; try { // open file for reading in = new BufferedReader(new FileReader(file)); // read remaining lines s = in.readLine(); lineNum++; while (s != null) { s = s.trim(); if (!("".equals(s))) { count++; } s = in.readLine(); lineNum++; } // close file in.close(); } catch (IOException e) { throw new PrismException("File I/O error reading from \"" + file + "\""); } catch (NumberFormatException e) { throw new PrismException("Error detected at line " + lineNum + " of file \"" + file + "\""); } return count; } /** * Compute transient probability distribution (forwards). * Use the passed in vector initDist as the initial probability distribution (time 0). * In case initDist is null starts at the default initial state with prob 1. * * @param time Time point * @param initDist Initial distribution */ public StateValues doTransient(double time, StateValues initDist) throws PrismException { if (!modelGen.hasSingleInitialState()) throw new PrismException("Fast adaptive uniformisation does not yet support models with multiple initial states"); mainLog.println("\nComputing probabilities (fast adaptive uniformisation)..."); if (initDist == null) { initDist = new StateValues(); initDist.type = TypeDouble.getInstance(); initDist.size = 1; initDist.valuesD = new double[1]; initDist.statesList = new ArrayList(); initDist.valuesD[0] = 1.0; initDist.statesList.add(modelGen.getInitialState()); } /* prepare fast adaptive uniformisation */ addDistr = new ArrayList(); deleteStates = new ArrayList(); states = new LinkedHashMap(initSize); value = 0.0; initStates = new HashSet(); ListIterator it = initDist.statesList.listIterator(); double[] values = initDist.getDoubleArray(); maxRate = 0.0; for (int stateNr = 0; stateNr < initDist.size; stateNr++) { State initState = it.next(); addToModel(initState); } it = initDist.statesList.listIterator(); for (int stateNr = 0; stateNr < initDist.size; stateNr++) { State initState = it.next(); computeStateRatesAndRewards(initState); states.get(initState).setProb(values[stateNr]); maxRate = Math.max(maxRate, states.get(initState).sumRates() * 1.02); } /* run fast adaptive uniformisation */ computeTransientProbsAdaptive(time); /* prepare and return results */ ArrayList statesList = new ArrayList(states.size()); double[] probsArr = new double[states.size()]; int probsArrEntry = 0; for (Map.Entry statePair : states.entrySet()) { statesList.add(statePair.getKey()); probsArr[probsArrEntry] = statePair.getValue().getProb(); probsArrEntry++; } StateValues probs = new StateValues(); probs.type = TypeDouble.getInstance(); probs.size = probsArr.length; probs.valuesD = probsArr; probs.statesList = statesList; mainLog.println("\nTotal probability lost is : " + getTotalDiscreteLoss()); mainLog.println("Maximal number of states stored during analysis : " + getMaxNumStates()); return probs; } /** * Compute transient probabilities using fast adaptive uniformisation * Compute the probability of being in each state at time {@code t}. * If corresponding options are set, also computes cumulative rewards. * For space efficiency, the initial distribution vector will be modified and values over-written, * so if you wanted it, take a copy. * @param time time point */ public void computeTransientProbsAdaptive(double time) throws PrismException { if (addDistr == null) { addDistr = new ArrayList(); deleteStates = new ArrayList(); states = new LinkedHashMap(initSize); value = 0.0; prepareInitialDistribution(); } double initIval = settings.getDouble(PrismSettings.PRISM_FAU_INITIVAL); if (time - initIval < 0.0) { initIval = 0.0; } if (initIval != 0.0) { iterateAdaptiveInterval(initIval); for (StateProp prop : states.values()) { prop.setProb(prop.getSum()); prop.setSum(0.0); prop.setNextProb(0.0); } updateStates(); } for (int ivalNr = 0; ivalNr < numIntervals; ivalNr++) { double interval = (time - initIval) / numIntervals; iterateAdaptiveInterval(interval); for (StateProp prop : states.values()) { prop.setProb(prop.getSum()); prop.setSum(0.0); prop.setNextProb(0.0); } updateStates(); } if (AnalysisType.REW_INST == analysisType) { for (StateProp prop : states.values()) { value += prop.getProb() * prop.getReward(); } } else { for (Map.Entry statePair : states.entrySet()) { State state = statePair.getKey(); StateProp prop = statePair.getValue(); modelGen.exploreState(state); specialLabels.setLabel(0, modelGen.getNumTransitions() == 0 ? Expression.True() : Expression.False()); specialLabels.setLabel(1, initStates.contains(state) ? Expression.True() : Expression.False()); Expression evTarget = target.deepCopy(); evTarget = (Expression) evTarget.expandLabels(specialLabels); if (AnalysisType.REACH == analysisType) { value += prop.getProb() * (evTarget.evaluateBoolean(constantValues, state) ? 1.0 : 0.0); } } } } /** * Performs fast adaptive uniformisation for a single time interval. * * @param interval duration of time interval * @throws PrismException */ private void iterateAdaptiveInterval(double interval) throws PrismException { birthProc = new BirthProcess(); birthProc.setTime(interval); birthProc.setEpsilon(epsilon); int iters = 0; birthProbSum = 0.0; itersUnchanged = 0; keepSumProb = false; while (birthProbSum < (1 - epsilon)) { if (birthProbSum >= epsilon/2) { keepSumProb = true; } if ((itersUnchanged == arrayThreshold)) { iters = arrayIterate(iters); } else { long birthProcTimer = System.currentTimeMillis(); double prob = birthProc.calculateNextProb(maxRate); birthProcTimer = System.currentTimeMillis() - birthProcTimer; birthProbSum += prob; collectValuePostIter(prob, birthProbSum); for (StateProp prop : states.values()) { prop.addToSum(prob); } mvMult(maxRate); updateStates(); iters++; } } computeTotalDiscreteLoss(); } /** * Transforms the current submodel to array form. * In case there are no further changes in the states discovered, or * further states only become relevant after a large number of * iterations, this allows the analysis to be performed much faster. * After the analysis has finished or after it has to be terminated as * formerly irrelevant states become relevant, results are mapped back * to the original data structure. The method returns the current * iteration. * * In case border states become * relevant, this data structure can * * @param iters current iteration number * @return current iteration after termination of this method * @throws PrismException thrown if problems in underlying methods occur */ private int arrayIterate(int iters) throws PrismException { /* build backwards matrix and map values */ int numStates = states.size(); int numTransitions = 0; for (StateProp prop : states.values()) { numTransitions += prop.getNumSuccs() + 1; } int stateNr = 0; HashMap stateToNumber = new HashMap(numStates); StateProp[] numberToState = new StateProp[numStates]; for (StateProp prop : states.values()) { if (prop.isAlive()) { stateToNumber.put(prop, stateNr); numberToState[stateNr] = prop; stateNr++; } } int numAlive = stateNr; for (StateProp prop : states.values()) { if (!prop.isAlive()) { stateToNumber.put(prop, stateNr); numberToState[stateNr] = prop; stateNr++; } } double[] inProbs = new double[numTransitions]; int[] rows = new int[numStates + 1]; int[] cols = new int[numTransitions]; double[] outRates = new double[numStates]; for (StateProp prop : states.values()) { StateProp[] succStates = prop.getSuccStates(); if (succStates != null) { for (StateProp succ : succStates) { rows[stateToNumber.get(succ) + 1]++; } } rows[stateToNumber.get(prop) + 1]++; } for (stateNr = 0; stateNr < numStates; stateNr++) { rows[stateNr + 1] += rows[stateNr]; } for (StateProp prop : states.values()) { int stateNumber = stateToNumber.get(prop); StateProp[] succStates = prop.getSuccStates(); double[] succRates = prop.getSuccRates(); if (succStates != null) { for (int i = 0; i < succStates.length; i++) { StateProp succState = succStates[i]; int succStateNumber = stateToNumber.get(succState); double succRate = succRates[i]; cols[rows[succStateNumber]] = stateNumber; inProbs[rows[succStateNumber]] = succRate / maxRate; rows[succStateNumber]++; outRates[stateNumber] += succRate; } } } for (stateNr = 0; stateNr < numStates; stateNr++) { cols[rows[stateNr]] = stateNr; inProbs[rows[stateNr]] = (maxRate - outRates[stateNr]) / maxRate; } Arrays.fill(rows, 0); for (StateProp prop : states.values()) { StateProp[] succStates = prop.getSuccStates(); if (succStates != null) { for (StateProp succ : succStates) { rows[stateToNumber.get(succ) + 1]++; } } rows[stateToNumber.get(prop) + 1]++; } for (stateNr = 0; stateNr < numStates; stateNr++) { rows[stateNr + 1] += rows[stateNr]; } double[] rewards = new double[numStates]; double[] probs = new double[numStates]; double[] nextProbs = new double[numStates]; double[] sum = new double[numStates]; for (stateNr = 0; stateNr < numberToState.length; stateNr++) { StateProp prop = numberToState[stateNr]; if (analysisType == AnalysisType.REW_CUMUL) { rewards[stateNr] = prop.getReward(); } probs[stateNr] = prop.getProb(); sum[stateNr] = prop.getSum(); } /* iterate using matrix */ boolean canArray = true; while (birthProbSum < (1 - epsilon) && canArray) { // timer2 = System.currentTimeMillis(); double prob = birthProc.calculateNextProb(maxRate); birthProbSum += prob; double mixed = (1.0 - birthProbSum) / maxRate; for (stateNr = 0; stateNr < numStates; stateNr++) { value += probs[stateNr] * mixed * rewards[stateNr]; sum[stateNr] += prob * probs[stateNr]; nextProbs[stateNr] = 0.0; for (int succNr = rows[stateNr]; succNr < rows[stateNr+1]; succNr++) { nextProbs[stateNr] += inProbs[succNr] * probs[cols[succNr]]; } if ((stateNr < numAlive) != (nextProbs[stateNr] > delta)) { canArray = false; } else if (stateNr >= numAlive) { nextProbs[stateNr] = 0.0; } } double[] swap = probs; probs = nextProbs; nextProbs = swap; iters++; } /* map back, update states and return current iteration */ for (stateNr = 0; stateNr < numberToState.length; stateNr++) { StateProp prop = numberToState[stateNr]; prop.setProb(probs[stateNr]); prop.setSum(sum[stateNr]); } updateStates(); return iters; } /** * Update analysis value after iteration. * For certain analyses (currently cumulative rewards) we have to modify * the analysis value after each iteration. * * @param prob * @param probSum */ private void collectValuePostIter(double prob, double probSum) { switch (analysisType) { case TRANSIENT: // nothing to do here, we're just computing distributions break; case REACH: // nothing to do here, we're collecting values later on break; case REW_INST: // nothing to do here, we're collecting rewards later on break; case REW_CUMUL: double mixed = (1.0 - probSum) / maxRate; for (StateProp prop : states.values()) { value += prop.getProb() * mixed * prop.getReward(); } break; } } /** * Updates state values once a transient analysis of time interval finished. * Deletes states which can be deleted according to their current * probability and the threshold. Computes new maximal rate for remaining * states. Computes transitions to successors of states which have become * alive to to probability threshold only after a transient analysis has * finished. * * @throws PrismException thrown if something goes wrong */ private void updateStates() throws PrismException { maxRate = 0.0; addDistr.clear(); for (Map.Entry statePair : states.entrySet()) { State state = statePair.getKey(); StateProp prop = statePair.getValue(); if (prop.getProb() > delta) { prop.setAlive(true); if (!prop.hasSuccs()) { itersUnchanged = 0; addDistr.add(state); } else { maxRate = Math.max(maxRate, prop.sumRates()); } } else { prop.delete(); } } for (int stateNr = 0; stateNr < addDistr.size(); stateNr++) { computeStateRatesAndRewards(addDistr.get(stateNr)); maxRate = Math.max(maxRate, states.get(addDistr.get(stateNr)).sumRates()); } maxRate *= 1.02; removeDeletedStates(); } /** * Removes all states subject to removal. * This affects states which both have a present-state probability below * the given threshold, and do not have incoming transitions from states * with a relevant probability mass. */ private void removeDeletedStates() { boolean unchanged = true; for (Map.Entry statePair : states.entrySet()) { State state = statePair.getKey(); StateProp prop = statePair.getValue(); if (prop.canRemove()) { deleteStates.add(state); unchanged = false; } } if (!keepSumProb) { for (int i = 0; i < deleteStates.size(); i++) { states.remove(deleteStates.get(i)); } } if (unchanged) { itersUnchanged++; } else { itersUnchanged = 0; } deleteStates.clear(); } /** * Prepares initial distribution for the case of a single initial state. * * @throws PrismException */ private void prepareInitialDistribution() throws PrismException { initStates = new HashSet(); State initState = modelGen.getInitialState(); initStates.add(initState); addToModel(initState); computeStateRatesAndRewards(initState); states.get(initState).setProb(1.0); maxRate = states.get(initState).sumRates() * 1.02; } /** * Computes total sum of lost probabilities. * * @return total probability still in model */ public void computeTotalDiscreteLoss() { double totalProb = 0; for (StateProp prop : states.values()) { totalProb += prop.getSum(); } totalProb += totalProbSetZero; totalProbLoss = 1.0 - totalProb; } /** * Returns the total probability loss. * * @return */ public double getTotalDiscreteLoss() { return totalProbLoss; } /** * Sets the probability of sink states to zero. * @throws PrismException */ public void clearSinkStates() throws PrismException { for (Map.Entry statePair : states.entrySet()) { State state = statePair.getKey(); StateProp prop = statePair.getValue(); modelGen.exploreState(state); specialLabels.setLabel(0, modelGen.getNumTransitions() == 0 ? Expression.True() : Expression.False()); specialLabels.setLabel(1, initStates.contains(state) ? Expression.True() : Expression.False()); Expression evSink = sink.deepCopy(); evSink = (Expression) evSink.expandLabels(specialLabels); if (evSink.evaluateBoolean(constantValues, state)) { totalProbSetZero += prop.getProb(); prop.setProb(0.0); } } } /** * Adds @a state to model. * Computes reward for this states, creates entry in map of states, * and updates number of states * * @param state state to add * @throws PrismException thrown if something wrong happens in underlying methods */ private void addToModel(State state) throws PrismException { StateProp prop = new StateProp(); prop.setReward(computeRewards(state)); states.put(state, prop); maxNumStates = Math.max(maxNumStates, states.size()); } /** * Computes successor rates and rewards for a given state. * Rewards computed depend on the reward structure set by * {@code setRewardStruct}. * * @param state state to compute successor rates and rewards for * @throws PrismException thrown if something goes wrong */ private void computeStateRatesAndRewards(State state) throws PrismException { double[] succRates; StateProp[] succStates; modelGen.exploreState(state); specialLabels.setLabel(0, modelGen.getNumTransitions() == 0 ? Expression.True() : Expression.False()); specialLabels.setLabel(1, initStates.contains(state) ? Expression.True() : Expression.False()); Expression evSink = sink.deepCopy(); evSink = (Expression) evSink.expandLabels(specialLabels); if (evSink.evaluateBoolean(constantValues, state)) { succRates = new double[1]; succStates = new StateProp[1]; succRates[0] = 1.0; succStates[0] = states.get(state); } else { int ntAll = modelGen.getNumTransitions(); if (ntAll > 0) { succRates = new double[ntAll]; succStates = new StateProp[ntAll]; int t = 0; for (int i = 0, nc = modelGen.getNumChoices(); i < nc; i++) { for (int j = 0, ntChoice = modelGen.getNumTransitions(i); j < ntChoice; j++) { State succState = modelGen.computeTransitionTarget(i, j); StateProp succProp = states.get(succState); if (null == succProp) { addToModel(succState); succProp = states.get(succState); // re-explore state, as call to addToModel may have explored succState modelGen.exploreState(state); } succRates[t] = modelGen.getTransitionProbability(i, j); succStates[t] = succProp; t++; } } } else { succRates = new double[1]; succStates = new StateProp[1]; succRates[0] = 1.0; succStates[0] = states.get(state); } } states.get(state).setSuccRates(succRates); states.get(state).setSuccStates(succStates); } /** * Perform a single matrix-vector multiplication. * * @param maxRate maximal total leaving rate sum in living states */ private void mvMult(double maxRate) { for (StateProp prop : states.values()) { double[] succRates = prop.getSuccRates(); StateProp[] succStates = prop.getSuccStates(); double stateProb = prop.getProb(); if (null != succStates) { double sumRates = 0.0; for (int succ = 0; succ < succStates.length; succ++) { double rate = succRates[succ]; sumRates += rate; succStates[succ].addToNextProb((rate / maxRate) * stateProb); } prop.addToNextProb(((maxRate - sumRates) / maxRate) * prop.getProb()); } } for (StateProp prop : states.values()) { prop.prepareNextIteration(); } } /** * Checks if rewards are needed for analysis. * * @return true if and only if rewards are needed */ private boolean isRewardAnalysis() { return (analysisType == AnalysisType.REW_INST) || (analysisType == AnalysisType.REW_CUMUL); } /** * Computes the reward for a given state. * In case a cumulative reward analysis is to be performed, transition * rewards are transformed into equivalent state rewards. * * @param state the state to compute the reward of * @return the reward for state @a state * @throws PrismException thrown if problems occur in PRISM functions called */ private double computeRewards(State state) throws PrismException { if (!isRewardAnalysis()) { return 0.0; } int numChoices = 0; if (AnalysisType.REW_CUMUL == analysisType) { modelGen.exploreState(state); numChoices = modelGen.getNumChoices(); } double sumReward = 0.0; int numStateItems = rewStruct.getNumItems(); for (int i = 0; i < numStateItems; i++) { Expression guard = rewStruct.getStates(i); if (guard.evaluateBoolean(constantValues, state)) { double reward = rewStruct.getReward(i).evaluateDouble(constantValues, state); String action = rewStruct.getSynch(i); if (action != null) { if (AnalysisType.REW_CUMUL == analysisType) { for (int j = 0; j < numChoices; j++) { int numTransitions = modelGen.getNumTransitions(j); for (int k = 0; k < numTransitions; k++) { Object tAction = modelGen.getTransitionAction(j, k); if (tAction == null) { tAction = ""; } if (tAction.toString().equals(action)) { sumReward += reward * modelGen.getTransitionProbability(j, k); } } } } } else { sumReward += reward; } } } return sumReward; } }