//============================================================================== // // Copyright (c) 2002- // Authors: // * Dave Parker (University of Oxford) // * Christian von Essen (Verimag, Grenoble) // //------------------------------------------------------------------------------ // // 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.BitSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import prism.PrismException; /** * Simple explicit-state representation of an MDP. * The implementation is far from optimal, both in terms of memory usage and speed of access. * The model is, however, easy to manipulate. For a static model (i.e. one that does not change * after creation), consider MDPSparse, which is more efficient. */ public class MDPSimple extends MDPExplicit implements NondetModelSimple { // Transition function (Steps) protected List> trans; // Action labels // (null list means no actions; null in element s means no actions for state s) protected List> actions; // Flag: allow duplicates in distribution sets? protected boolean allowDupes = false; // Other statistics protected int numDistrs; protected int numTransitions; protected int maxNumDistrs; protected boolean maxNumDistrsOk; // Constructors /** * Constructor: empty MDP. */ public MDPSimple() { initialise(0); } /** * Constructor: new MDP with fixed number of states. */ public MDPSimple(int numStates) { initialise(numStates); } /** * Copy constructor. */ public MDPSimple(MDPSimple mdp) { this(mdp.numStates); copyFrom(mdp); // Copy storage directly to avoid worrying about duplicate distributions (and for efficiency) for (int s = 0; s < numStates; s++) { List distrs = trans.get(s); for (Distribution distr : mdp.trans.get(s)) { distrs.add(new Distribution(distr)); } } if (mdp.actions != null) { actions = new ArrayList>(numStates); for (int s = 0; s < numStates; s++) actions.add(null); for (int s = 0; s < numStates; s++) { if (mdp.actions.get(s) != null) { int n = mdp.trans.get(s).size(); List list = new ArrayList(n); for (int i = 0; i < n; i++) { list.add(mdp.actions.get(s).get(i)); } actions.set(s, list); } } } // Copy flags/stats too allowDupes = mdp.allowDupes; numDistrs = mdp.numDistrs; numTransitions = mdp.numTransitions; maxNumDistrs = mdp.maxNumDistrs; maxNumDistrsOk = mdp.maxNumDistrsOk; } /** * Constructor: new MDP copied from an existing DTMC. */ public MDPSimple(DTMCSimple dtmc) { this(dtmc.getNumStates()); copyFrom(dtmc); for (int s = 0; s < numStates; s++) { // Note: DTMCSimple has no actions so can ignore these addChoice(s, new Distribution(dtmc.getTransitions(s))); } } /** * Construct an MDP from an existing one and a state index permutation, * i.e. in which state index i becomes index permut[i]. * Note: have to build new Distributions from scratch anyway to do this, * so may as well provide this functionality as a constructor. */ public MDPSimple(MDPSimple mdp, int permut[]) { this(mdp.numStates); copyFrom(mdp, permut); // Copy storage directly to avoid worrying about duplicate distributions (and for efficiency) // (Since permut is a bijection, all structures and statistics are identical) for (int s = 0; s < numStates; s++) { List distrs = trans.get(permut[s]); for (Distribution distr : mdp.trans.get(s)) { distrs.add(new Distribution(distr, permut)); } } if (mdp.actions != null) { actions = new ArrayList>(numStates); for (int s = 0; s < numStates; s++) actions.add(null); for (int s = 0; s < numStates; s++) { if (mdp.actions.get(s) != null) { int n = mdp.trans.get(s).size(); List list = new ArrayList(n); for (int i = 0; i < n; i++) { list.add(mdp.actions.get(s).get(i)); } actions.set(permut[s], list); } } } // Copy flags/stats too allowDupes = mdp.allowDupes; numDistrs = mdp.numDistrs; numTransitions = mdp.numTransitions; maxNumDistrs = mdp.maxNumDistrs; maxNumDistrsOk = mdp.maxNumDistrsOk; } /** * Construct an MDPSimple object from an MDPSparse one. */ public MDPSimple(MDPSparse mdp) { this(mdp.numStates); copyFrom(mdp); // Copy storage directly to avoid worrying about duplicate distributions (and for efficiency) for (int s = 0; s < numStates; s++) { for (int c = 0; c < mdp.getNumChoices(s); c++) { Distribution distr = new Distribution(); Iterator> it = mdp.getTransitionsIterator(s, c); while (it.hasNext()) { Entry entry = it.next(); distr.add(entry.getKey(), entry.getValue()); } this.addChoice(s, distr); } } if (mdp.actions != null) { actions = new ArrayList>(numStates); for (int s = 0; s < numStates; s++) actions.add(null); for (int s = 0; s < numStates; s++) { int n = mdp.getNumChoices(s); List list = new ArrayList(n); for (int i = 0; i < n; i++) { list.add(mdp.getAction(s, i)); } actions.set(s, list); } } // Copy flags/stats too allowDupes = false; // TODO check this numDistrs = mdp.numDistrs; numTransitions = mdp.numTransitions; maxNumDistrs = mdp.maxNumDistrs; maxNumDistrsOk = true; // TODO not sure } // Mutators (for ModelSimple) @Override public void initialise(int numStates) { super.initialise(numStates); numDistrs = numTransitions = maxNumDistrs = 0; maxNumDistrsOk = true; trans = new ArrayList>(numStates); for (int i = 0; i < numStates; i++) { trans.add(new ArrayList()); } actions = null; } @Override public void clearState(int s) { // Do nothing if state does not exist if (s >= numStates || s < 0) return; // Clear data structures and update stats List list = trans.get(s); numDistrs -= list.size(); for (Distribution distr : list) { numTransitions -= distr.size(); } maxNumDistrsOk = false; trans.get(s).clear(); if (actions != null && actions.get(s) != null) actions.get(s).clear(); } @Override public int addState() { addStates(1); return numStates - 1; } @Override public void addStates(int numToAdd) { for (int i = 0; i < numToAdd; i++) { trans.add(new ArrayList()); if (actions != null) actions.add(null); numStates++; } } @Override public void buildFromPrismExplicit(String filename) throws PrismException { // we want to accurately store the model as it appears // in the file, so we allow dupes allowDupes = true; int lineNum = 0; // Open file for reading, automatic close try (BufferedReader in = new BufferedReader(new FileReader(new File(filename)))) { // Parse first line to get num states String info = in.readLine(); lineNum = 1; if (info == null) { throw new PrismException("Missing first line of .tra file"); } String[] infos = info.split(" "); if (infos.length < 3) { throw new PrismException("First line of .tra file must read #states, #choices, #transitions"); } int n = Integer.parseInt(infos[0]); int expectedNumChoices = Integer.parseInt(infos[1]); int expectedNumTransitions = Integer.parseInt(infos[2]); int emptyDistributions = 0; // Initialise initialise(n); // Go though list of transitions in file String s = in.readLine(); lineNum++; while (s != null) { s = s.trim(); if (s.length() > 0) { String[] transition = s.split(" "); int source = Integer.parseInt(transition[0]); int choice = Integer.parseInt(transition[1]); int target = Integer.parseInt(transition[2]); double prob = Double.parseDouble(transition[3]); if (source < 0 || source >= numStates) { throw new PrismException("Problem in .tra file (line " + lineNum + "): illegal source state index " + source); } if (target < 0 || target >= numStates) { throw new PrismException("Problem in .tra file (line " + lineNum + "): illegal target state index " + target); } // ensure distributions for all choices up to choice (inclusive) exist // this potentially creates empty distributions that are never defined // so we keep track of the number of distributions that are still empty // and provide an error message if there are still empty distributions // after having read the full .tra file while (choice >= getNumChoices(source)) { addChoice(source, new Distribution()); emptyDistributions++; } if (trans.get(source).get(choice).isEmpty()) { // was empty distribution, becomes non-empty below emptyDistributions--; } // add transition if (! trans.get(source).get(choice).add(target, prob)) { numTransitions++; } else { throw new PrismException("Problem in .tra file (line " + lineNum + "): redefinition of probability for " + source + " " + choice + " " + target); } // add action if (transition.length > 4) { String action = transition[4]; Object oldAction = getAction(source, choice); if (oldAction != null && !action.equals(oldAction)) { throw new PrismException("Problem in .tra file (line " + lineNum + "):" + "inconsistent action label for " + source + ", " + choice + ": " + oldAction + " and " + action); } setAction(source, choice, action); } } s = in.readLine(); lineNum++; } // check integrity if (getNumChoices() != expectedNumChoices) { throw new PrismException("Problem in .tra file: unexpected number of choices: " + getNumChoices()); } if (getNumTransitions() != expectedNumTransitions) { throw new PrismException("Problem in .tra file: unexpected number of transitions: " + getNumTransitions()); } assert(emptyDistributions >= 0); if (emptyDistributions > 0) { throw new PrismException("Problem in .tra file: there are " + emptyDistributions + " empty distribution, are there gaps in the choice indices?"); } } catch (IOException e) { throw new PrismException("File I/O error reading from \"" + filename + "\": " + e.getMessage()); } catch (NumberFormatException e) { throw new PrismException("Problem in .tra file (line " + lineNum + ") for " + getModelType()); } } // Mutators (other) /** * Add a choice (distribution {@code distr}) to state {@code s} (which must exist). * Distribution is only actually added if it does not already exists for state {@code s}. * (Assuming {@code allowDupes} flag is not enabled.) * Returns the index of the (existing or newly added) distribution. * Returns -1 in case of error. */ public int addChoice(int s, Distribution distr) { List set; // Check state exists if (s >= numStates || s < 0) return -1; // Add distribution (if new) if (!allowDupes) { int i = indexOfChoice(s, distr); if (i != -1) return i; } set = trans.get(s); set.add(distr); // Add null action if necessary if (actions != null && actions.get(s) != null) actions.get(s).add(null); // Update stats numDistrs++; maxNumDistrs = Math.max(maxNumDistrs, set.size()); numTransitions += distr.size(); return set.size() - 1; } /** * Add a choice (distribution {@code distr}) labelled with {@code action} to state {@code s} (which must exist). * Action/distribution is only actually added if it does not already exists for state {@code s}. * (Assuming {@code allowDupes} flag is not enabled.) * Returns the index of the (existing or newly added) distribution. * Returns -1 in case of error. */ public int addActionLabelledChoice(int s, Distribution distr, Object action) { List set; // Check state exists if (s >= numStates || s < 0) return -1; // Add distribution/action (if new) if (!allowDupes) { int i = indexOfActionLabelledChoice(s, distr, action); if (i != -1) return i; } set = trans.get(s); set.add(distr); // Add null action if necessary if (actions != null && actions.get(s) != null) actions.get(s).add(null); // Set action setAction(s, set.size() - 1, action); // Update stats numDistrs++; maxNumDistrs = Math.max(maxNumDistrs, set.size()); numTransitions += distr.size(); return set.size() - 1; } /** * Set the action label for choice i in some state s. * This method does not know about duplicates (i.e. if setting an action causes * two choices to be identical, one will not be removed). * Use {@link #addActionLabelledChoice(int, Distribution, Object)} which is more reliable. */ public void setAction(int s, int i, Object o) { // If action to be set is null, nothing to do if (o == null) return; // If no actions array created yet, create it if (actions == null) { actions = new ArrayList>(numStates); for (int j = 0; j < numStates; j++) actions.add(null); } // If no actions for state i yet, create list if (actions.get(s) == null) { int n = trans.get(s).size(); List list = new ArrayList(n); for (int j = 0; j < n; j++) { list.add(null); } actions.set(s, list); } // Set actions actions.get(s).set(i, o); } // Accessors (for Model) @Override public int getNumTransitions() { return numTransitions; } @Override public void findDeadlocks(boolean fix) throws PrismException { for (int i = 0; i < numStates; i++) { // Note that no distributions is a deadlock, not an empty distribution if (trans.get(i).isEmpty()) { addDeadlockState(i); if (fix) { Distribution distr = new Distribution(); distr.add(i, 1.0); addChoice(i, distr); } } } } @Override public void checkForDeadlocks(BitSet except) throws PrismException { for (int i = 0; i < numStates; i++) { if (trans.get(i).isEmpty() && (except == null || !except.get(i))) throw new PrismException("MDP has a deadlock in state " + i); } } // Accessors (for NondetModel) @Override public int getNumChoices(int s) { return trans.get(s).size(); } @Override public int getMaxNumChoices() { // Recompute if necessary if (!maxNumDistrsOk) { maxNumDistrs = 0; for (int s = 0; s < numStates; s++) maxNumDistrs = Math.max(maxNumDistrs, getNumChoices(s)); } return maxNumDistrs; } @Override public int getNumChoices() { return numDistrs; } @Override public Object getAction(int s, int i) { List list; if (i < 0 || actions == null || (list = actions.get(s)) == null) return null; return list.get(i); } @Override public boolean allSuccessorsInSet(int s, int i, BitSet set) { return trans.get(s).get(i).isSubsetOf(set); } @Override public boolean someSuccessorsInSet(int s, int i, BitSet set) { return trans.get(s).get(i).containsOneOf(set); } @Override public Iterator getSuccessorsIterator(final int s, final int i) { return trans.get(s).get(i).getSupport().iterator(); } @Override public SuccessorsIterator getSuccessors(final int s, final int i) { return SuccessorsIterator.from(getSuccessorsIterator(s, i), true); } // Accessors (for MDP) @Override public int getNumTransitions(int s, int i) { return trans.get(s).get(i).size(); } @Override public Iterator> getTransitionsIterator(int s, int i) { return trans.get(s).get(i).iterator(); } // Accessors (other) /** * Get the list of choices (distributions) for state s. */ public List getChoices(int s) { return trans.get(s); } /** * Get the ith choice (distribution) for state s. */ public Distribution getChoice(int s, int i) { return trans.get(s).get(i); } /** * Returns the index of the choice {@code distr} for state {@code s}, if it exists. * If none, -1 is returned. If there are multiple (i.e. allowDupes is true), the first is returned. */ public int indexOfChoice(int s, Distribution distr) { return trans.get(s).indexOf(distr); } /** * Returns the index of the {@code action}-labelled choice {@code distr} for state {@code s}, if it exists. * If none, -1 is returned. If there are multiple (i.e. allowDupes is true), the first is returned. */ public int indexOfActionLabelledChoice(int s, Distribution distr, Object action) { List set = trans.get(s); int i, n = set.size(); if (distr == null) { for (i = 0; i < n; i++) { if (set.get(i) == null) { Object a = getAction(s, i); if (action == null) { if (a == null) return i; } else { if (action.equals(a)) return i; } } } } else { for (i = 0; i < n; i++) { if (distr.equals(set.get(i))) { Object a = getAction(s, i); if (action == null) { if (a == null) return i; } else { if (action.equals(a)) return i; } } } } return -1; } // Standard methods @Override public String toString() { int i, j, n; Object o; String s = ""; s = "[ "; for (i = 0; i < numStates; i++) { if (i > 0) s += ", "; s += i + ": "; s += "["; n = getNumChoices(i); for (j = 0; j < n; j++) { if (j > 0) s += ","; o = getAction(i, j); if (o != null) s += o + ":"; s += trans.get(i).get(j); } s += "]"; } s += " ]\n"; return s; } @Override public boolean equals(Object o) { if (o == null || !(o instanceof MDPSimple)) return false; MDPSimple mdp = (MDPSimple) o; if (numStates != mdp.numStates) return false; if (!initialStates.equals(mdp.initialStates)) return false; if (!trans.equals(mdp.trans)) return false; // TODO: compare actions (complicated: null = null,null,null,...) return true; } }