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.
 
 
 
 
 
 

548 lines
18 KiB

//==============================================================================
//
// Copyright (c) 2002-
// Authors:
// * Dave Parker <david.parker@comlab.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 pta;
import java.util.*;
import prism.ModelType;
import prism.PrismComponent;
import prism.PrismException;
import explicit.*;
/**
* Probabilistic reachability for PTAs, using abstraction/refinement of stochastic games.
* See: "Stochastic Games for Verification of Probabilistic Timed Automata" (FORMATS'09).
*/
public class PTAAbstractRefine extends QuantAbstractRefine
{
// PTA, target info
protected PTA pta = null;
protected BitSet targetLocs;
protected Constraint targetConstraint;
// Forwards reachability graph
protected ReachabilityGraph graph;
// Flags + options
// Do we cache zones representing validity of transition subsets
// for reuse when splitting that zone later on? (definitely saves a little
// bit of time, but at the expense of some space).
boolean storeValidZones = true;
/**
* Default constructor.
*/
public PTAAbstractRefine(PrismComponent parent) throws PrismException
{
super(parent);
// Just do basic config for QuantAbstractRefine
setModelType(ModelType.MDP);
setPropertyType(QuantAbstractRefine.PropertyType.PROB_REACH);
}
/**
* Compute min/max PTA reachability probabilities using STPG abstraction refinement.
*/
public double forwardsReachAbstractRefine(PTA pta, BitSet targetLocs, Constraint targetConstraint, boolean min)
throws PrismException
{
// Store PTA/target info
this.pta = pta;
this.targetLocs = targetLocs;
this.targetConstraint = targetConstraint;
// Execute abstraction-refinement loop
return abstractRefine(min);
}
// Implementation of initialise() for abstraction-refinement loop; see superclass for details
@Override
protected void initialise() throws PrismException
{
ForwardsReach forwardsReach;
List<Integer> initialStates;
int src, numStates;
// Build forwards reachability graph
forwardsReach = new ForwardsReach(mainLog);
graph = forwardsReach.buildForwardsGraph(pta, targetLocs, targetConstraint);
// Store the set of target/initial states from forwards reachability
target = forwardsReach.getTarget();
initialStates = forwardsReach.getInitialStates();
// Compute validities for all symbolic transitions in the graph
graph.computeAllValidities();
// Display states, graph, etc.
if (verbosity >= 5) {
mainLog.println("\nStates: ");
graph.printStates(mainLog);
mainLog.println("\nGraph: " + graph);
mainLog.println("Target states: " + target);
}
// Build empty game
abstraction = new STPGAbstrSimple();
// Add all states
numStates = graph.states.size();
abstraction.addStates(numStates);
// Set initial states
for (int i : initialStates) {
abstraction.addInitialState(i);
}
// Build each state individually
for (src = 0; src < numStates; src++) {
buildSTPGState(src);
}
}
// Implementation of rebuildAbstraction(...) for abstraction-refinement loop; see superclass for details
@Override
protected void rebuildAbstraction(Set<Integer> rebuildStates) throws PrismException
{
for (int src : rebuildStates) {
abstraction.clearState(src);
buildSTPGState(src);
}
}
/**
* Build a single state of the STPG.
*/
protected void buildSTPGState(int src) throws PrismException
{
LocZone lz;
NCZone z;
ArrayList<NCZone> valids;
SymbolicTransition st;
int i, numTransitions, numValids, map[];
// Get state and num transitions
lz = graph.states.get(src);
numTransitions = graph.trans.get(src).size();
// Skip build of state if no transitions (should just be target states)
if (numTransitions == 0) {
if (!target.get(src))
mainLog.printWarning("Building STPG state (" + src + ") with no transitions");
return;
}
// Compute validity of each outgoing transition from this state.
// In fact, since guards for multiple transitions are often identical,
// we only do this for distinct ones, and build a 'map' between them.
valids = new ArrayList<NCZone>(numTransitions);
map = new int[numTransitions];
for (i = 0; i < numTransitions; i++) {
st = graph.trans.get(src).get(i);
z = (NCZone) st.valid;
// Check that validity constraint is non-empty
if (z.isEmpty())
throw new PrismException("Found invalid symbolic transition");
// See if we already have a copy of this zone z
map[i] = valids.indexOf(z);
// If not, add to list and store index in map
if (map[i] == -1) {
map[i] = valids.size();
valids.add(z);
}
}
// Recursive construction of this state
numValids = valids.size();
buildSTPGStateRec(src, new DBMList(lz.zone), new BitSet(numValids), valids, map, 0, numValids);
// Check for deadlocks in the constructed STPG
// (should never occur because of the restrictions we impose on PTAs)
if (abstraction.getNumChoices(src) == 0) {
throw new PrismException("STPG has deadlock in state #" + src + ":" + graph.states.get(src));
}
}
/**
* Recursive construction of STPG state 'src'.
* Consider each combination of outgoing transitions (combination stored in 'bitSet'),
* and build validity constraint ('valid') for each combination.
* (Note: actually only consider transitions with distinct validity - see above.)
*/
protected void buildSTPGStateRec(int src, NCZone valid, BitSet bitSet, ArrayList<NCZone> valids, int[] map,
int level, int numValids) throws PrismException
{
STPGAbstrSimple stpg;
ArrayList<SymbolicTransition> sts;
DistributionSet distrSet;
Distribution distr;
BitSet actionBitSet = null;
int count, i, dest;
// Cast abstraction to STPG since we know the type
stpg = (STPGAbstrSimple) abstraction;
// Bottom of recursion: check if valid and, if so, add appropriate distribution set to game
if (level == numValids) {
// Check this combination of transitions is non-empty
if (!valid.isEmpty()) {
// Ignore the case where no transitions are enabled
// (has been dealt with earlier by adding explicit "diverge" transition)
if (bitSet.cardinality() == 0)
return;
// Create distribution set for this combination of transitions
distrSet = stpg.newDistributionSet(null);
// If using BitSets for action labels (as opposed to storing the zones directly)
// create a bit set that will represent the actual combination of transitions
// (the bit set passed into this function is indexed over distinct validity transitions only)
sts = graph.trans.get(src);
if (!storeValidZones)
actionBitSet = new BitSet(sts.size());
i = 0;
for (SymbolicTransition st : sts) {
// Add distribution corresponding to ith transition if
// it is included in the set we are considering in this iteration
if (bitSet.get(map[i])) {
distr = new Distribution();
count = 0;
for (Edge edge : st.tr.getEdges()) {
dest = st.dests[count];
if (dest != -1)
distr.add(dest, edge.getProbability());
count++;
}
if (!distr.isEmpty())
distrSet.add(distr);
// If using BitSets for action labels, modify the bit set
// (setting kth bit to denote that kth transition is included)
if (!storeValidZones)
actionBitSet.set(i);
}
i++;
}
// Set the (BitSet or Zone) action label for the distribution set and add it to the game
if (storeValidZones)
distrSet.setAction(valid);
else
distrSet.setAction(actionBitSet);
stpg.addDistributionSet(src, distrSet);
}
} else {
// Recursive step
// Note that the construction of the validity constraint for
// transition combinations is done recursively - this gives big gains in efficiency.
// Note also that, the first thing added to the conjunction (i.e. the validity constraint)
// before recursion starts is the symbolic state zone. We can only do this because at
// least one transition is enabled in this combination, so its validity will need to include
// the symbolic state zone. Generally, it is better to
// add non-complemented zones, like this, early to avoid blowups
// (in terms of conjunctions on complements, which are typically large DBM lists).
// Finally, note that use of combined intersectComplement operation is, like
// for the refinement state-split set, crucial for efficiency.
// bitSet[level] = 0
NCZone validNew;
validNew = valid.deepCopy();
validNew.intersectComplement(valids.get(level));
bitSet.set(level, false);
if (!validNew.isEmpty())
buildSTPGStateRec(src, validNew, bitSet, valids, map, level + 1, numValids);
// bitSet[level] = 1
validNew = valid.deepCopy();
validNew.intersect(valids.get(level));
bitSet.set(level, true);
if (!validNew.isEmpty())
buildSTPGStateRec(src, validNew, bitSet, valids, map, level + 1, numValids);
}
}
// Implementation of splitState(...) for abstraction-refinement loop; see superclass for details
// Needs to update:
// - local storage about abstraction (i.e. states and symbolic transitions in reachability graph)
// - abstraction (new states, initial states and transitions)
// - target set
@Override
protected int splitState(int splitState, List<List<Integer>> choiceLists, Set<Integer> rebuiltStates,
Set<Integer> rebuildStates) throws PrismException
{
LocZone lzSplit;
DBMList z, valid1;
int[] newStateMap;
int i, n, numStates, numNewStates, choiceCount;
Object action;
BitSet actionBitSet;
ArrayList<DBMList> partition;
boolean rebuild;
// Get abstract state to split
lzSplit = graph.states.get(splitState);
if (verbosity >= 1)
mainLog.println("Splitting: #" + splitState + "=" + lzSplit);
// Compute zones that this state will be split into; store in 'partition'
partition = new ArrayList<DBMList>();
choiceCount = 0;
for (List<Integer> choiceList : choiceLists) {
choiceCount += choiceList.size();
z = new DBMList(pta);
for (int in : choiceList) {
// If using BitSets for action labels (i.e. have not cached zones for reuse)
// need to build the corresponding zone
action = ((STPGAbstrSimple) abstraction).getChoice(splitState, in).action;
if (!storeValidZones) {
actionBitSet = (BitSet) action;
List<SymbolicTransition> sts = graph.trans.get(splitState);
n = sts.size();
Set<NCZone> valids = new LinkedHashSet<NCZone>();
Set<NCZone> validCmpls = new LinkedHashSet<NCZone>();
for (i = 0; i < n; i++) {
SymbolicTransition st = sts.get(i);
// Store in appropriate set
(actionBitSet.get(i) ? valids : validCmpls).add((DBMList) st.valid);
}
//log.println("valids: " + valids);
//log.println("validCmpls: " + validCmpls);
// Build zone
valid1 = new DBMList(DBM.createTrue(pta));
for (Zone z2 : valids) {
valid1.intersect(z2);
}
for (NCZone z2 : validCmpls) {
valid1.intersectComplement(z2);
}
valid1.intersect(lzSplit.zone);
}
// If using zones, then we just use the stored zone
else {
valid1 = (DBMList) action;
}
z.addDBMs(valid1);
}
partition.add(z);
}
// Compute zone for remainder of the partition
// (NB: another way to do this is to build a union of remaining player 1 choices)
// (but, empirically, this seems to be bad in terms of DBM list efficiency)
// (Note, though, we can check whether we need to do this at all, which is good)
if (choiceCount < abstraction.getNumChoices(splitState)) {
// Compute union of all partition elements from above
DBMList all = new DBMList(pta);
for (DBMList dbml : partition) {
all.addDBMs(dbml.deepCopy());
}
// Subtract union from symbolic state zone
DBMList rest = new DBMList(lzSplit.zone);
rest.intersectComplement(all);
if (!rest.isEmpty())
partition.add(rest);
}
// Check we actually got a strict split of the partition
if (partition.size() <= 1) {
mainLog.printWarning("failed to split state #" + splitState + "=" + lzSplit);
return 1;
}
// Update symbolic state set info (graph.states) and store info about indices of new states
numNewStates = partition.size();
newStateMap = new int[numNewStates];
for (i = 0; i < numNewStates; i++) {
// First new state overwrites one that is being split
if (i == 0) {
graph.states.set(splitState, new LocZone(lzSplit.loc, partition.get(i)));
newStateMap[i] = splitState;
}
// Other new states are appended to end of list
else {
graph.states.add(new LocZone(lzSplit.loc, partition.get(i)));
newStateMap[i] = graph.states.size() - 1;
graph.copyState(splitState);
}
}
// Display info
if (verbosity >= 1) {
mainLog.println("Splitting: #" + splitState + "=" + lzSplit);
mainLog.println("into " + numNewStates + ":");
for (i = 0; i < numNewStates; i++)
mainLog.println("#" + newStateMap[i] + "=" + partition.get(i));
}
if (verbosity >= 5)
mainLog.println("New states: " + graph.states);
// Add new states to the abstraction
abstraction.addStates(numNewStates - 1);
// Add new states to initial state set if needed
// Note: we assume any abstract state contains either all/no initial states
if (abstraction.isInitialState(splitState)) {
for (i = 1; i < numNewStates; i++) {
abstraction.addInitialState(newStateMap[i]);
}
}
// Rebuild target states
for (i = 0; i < numNewStates; i++) {
LocZone lz = graph.states.get(newStateMap[i]);
target.set(newStateMap[i], isTarget(lz));
}
// Update symbolic transitions and abstraction
Set<SymbolicTransition> oldSTs = new LinkedHashSet<SymbolicTransition>();
Set<SymbolicTransition> newSTs = new LinkedHashSet<SymbolicTransition>();
// Go through all abstract states
numStates = graph.states.size();
for (i = 0; i < numStates; i++) {
oldSTs.clear();
newSTs.clear();
// Do we need to rebuild this state?
// (i.e. is it a new state or a successor of the split state?)
rebuild = false;
// For a new state...
if (i == splitState || i > numStates - numNewStates) {
// Split all symbolic transitions from this state
for (SymbolicTransition st : graph.trans.get(i)) {
oldSTs.add(st);
splitSymbolicTransition(i, st, splitState, newStateMap, newSTs);
}
rebuild = true;
}
// For a successor state
else {
// Split symbolic transitions that go to the split state
for (SymbolicTransition st : graph.trans.get(i)) {
if (st.hasSuccessor(splitState)) {
oldSTs.add(st);
splitSymbolicTransition(i, st, splitState, newStateMap, newSTs);
rebuild = true;
}
}
}
// Bail out, if we didn't need to rebuild for this state
if (!rebuild)
continue;
// Now, actually modify the graph
// (didn't do this on the fly because don't went to change
// the list that we are iterating over)
for (SymbolicTransition st : oldSTs)
graph.trans.get(i).remove(st);
for (SymbolicTransition st : newSTs)
graph.trans.get(i).add(st);
if ((verbosity >= 1) && !oldSTs.isEmpty()) {
mainLog.print("Replacing symbolic transitions: " + i + ":" + oldSTs);
mainLog.println(" with: " + i + ":" + newSTs);
}
// Rebuild this state of the abstraction
abstraction.clearState(i);
buildSTPGState(i);
rebuiltStates.add(i);
}
if (verbosity >= 5) {
mainLog.println("New graph: " + graph);
}
return numNewStates;
}
/**
* Split a symbolic transition, based on the division of a symbolic state into several parts.
* @param src: The source of the symbolic transition to be split
* @param st: The symbolic transition to be split
* @param splitState: The index of the symbolic state that is being split
* @param newStateMap: The indices of the new states
* @param newSTs: Where to put the new symbolic transitions
*/
private void splitSymbolicTransition(int src, SymbolicTransition st, int splitState, int newStateMap[],
Set<SymbolicTransition> newSTs)
{
// Take a copy of the transition, because we will modify it when analysing it
SymbolicTransition stNew = new SymbolicTransition(st);
// Recursively...
splitSymbolicTransition(src, stNew, splitState, newStateMap, newSTs, 0, st.dests.length);
}
private void splitSymbolicTransition(int src, SymbolicTransition st, int splitState, int newStateMap[],
Set<SymbolicTransition> newSTs, int level, int n)
{
if (level == n) {
Zone valid = graph.computeValidity(src, st.tr, st.dests);
if (!valid.isEmpty()) {
SymbolicTransition stNew = new SymbolicTransition(st);
stNew.valid = valid;
newSTs.add(stNew);
}
} else {
if (st.dests[level] == splitState) {
int m = newStateMap.length;
for (int i = 0; i < m; i++) {
st.dests[level] = newStateMap[i];
splitSymbolicTransition(src, st, splitState, newStateMap, newSTs, level + 1, n);
}
st.dests[level] = splitState;
} else {
splitSymbolicTransition(src, st, splitState, newStateMap, newSTs, level + 1, n);
}
}
}
// TODO: why does this not work?
private Zone valid2new(LocZone lz, Transition tr, int[] dests)
{
int count;
Zone z, z2;
z = new DBMList(DBM.createTrue(pta));
// Conjunction over edges in transition
count = 0;
for (Edge edge : tr.getEdges()) {
// Get (copy of) edge target
z2 = graph.states.get(dests[count]).zone.deepCopy();
// Backwards reset of clocks for edge
for (Map.Entry<Integer, Integer> e : edge.getResets()) {
z2.backReset(e.getKey(), e.getValue());
}
// Intersect
z.intersect(z2);
count++;
}
// Intersect with transition guard
for (Constraint c : tr.getGuardConstraints()) {
z.addConstraint(c);
}
// Time pre
z.down();
// Intersect with symbolic state zone
z.intersect(lz.zone);
return z;
}
private boolean isTarget(LocZone lz)
{
return targetLocs.get(lz.loc) && (targetConstraint == null || lz.zone.isSatisfied(targetConstraint));
}
}