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.
288 lines
11 KiB
288 lines
11 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.ArrayDeque;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.ListIterator;
|
|
import java.util.Map.Entry;
|
|
|
|
/**
|
|
* Weak bisimulation lumper.
|
|
* Notice that weak bisimulation is only valid for unbounded reachability,
|
|
* but must not be used for expected accumulated rewards or long-run
|
|
* average rewards.
|
|
*
|
|
* @author Ernst Moritz Hahn <emhahn@cs.ox.ac.uk> (University of Oxford)
|
|
*/
|
|
final class WeakLumper extends Lumper {
|
|
|
|
/**
|
|
* Construct a new weak bisimulation lumper.
|
|
*
|
|
* @param origPmc Markov chain to construct lumper for
|
|
*/
|
|
WeakLumper(MutablePMC origPmc) {
|
|
super(origPmc);
|
|
}
|
|
|
|
/**
|
|
* Construct the weak bisimulation signature of given block.
|
|
* The signature is a mapping of blocks to the probability to move
|
|
* from the given state to any state of the block under the condition
|
|
* that the block is left. Thus, it is only defined for states which have
|
|
* a non-zero probability of leaving the block in one step. The function
|
|
* returns {@code null} for states ("silent" states) for which this is
|
|
* not the case.
|
|
*
|
|
* @param state state to compute signature of
|
|
* @return signature of this state
|
|
*/
|
|
private HashMap<HashSet<Integer>, Function> stateSignature(int state, HashSet<Integer> ownClass)
|
|
{
|
|
HashMap<HashSet<Integer>, Function> signature = new HashMap<HashSet<Integer>, Function>();
|
|
ListIterator<Integer> toStateIter = origPmc.transitionTargets.get(state).listIterator();
|
|
ListIterator<Function> toProbIter = origPmc.transitionProbs.get(state).listIterator();
|
|
|
|
/* compute probability to remain in block in one step */
|
|
Function slProb = origPmc.getFunctionFactory().getZero();
|
|
while (toStateIter.hasNext()) {
|
|
int toState = toStateIter.next();
|
|
Function toStateProb = toProbIter.next();
|
|
if (ownClass.contains(toState)) {
|
|
slProb = slProb.add(toStateProb);
|
|
}
|
|
}
|
|
/* for states which cannot leave their block directly, return {@code null} */
|
|
if (slProb.equals(origPmc.getFunctionFactory().getOne())) {
|
|
return null;
|
|
}
|
|
/* 1 / (1 - slProb) */
|
|
Function star = slProb.star();
|
|
|
|
toStateIter = origPmc.transitionTargets.get(state).listIterator();
|
|
toProbIter = origPmc.transitionProbs.get(state).listIterator();
|
|
while (toStateIter.hasNext()) {
|
|
int toState = toStateIter.next();
|
|
Function toStateProb = toProbIter.next();
|
|
HashSet<Integer> toBlock = partition.getStateBlock(toState);
|
|
if (ownClass != toBlock) {
|
|
toStateProb = star.multiply(toStateProb);
|
|
Function toBlockProb = signature.get(toBlock);
|
|
if (toBlockProb == null) {
|
|
toBlockProb = origPmc.getFunctionFactory().getZero();
|
|
}
|
|
toBlockProb = toBlockProb.add(toStateProb);
|
|
signature.put(toBlock, toBlockProb);
|
|
}
|
|
}
|
|
return signature;
|
|
}
|
|
|
|
/**
|
|
* Refines a given block to a list of new blocks for weak bisimulation.
|
|
* New blocks are as follows: some of the new blocks consist of the
|
|
* states which can leave their block ("non-silent" states) and which
|
|
* have the same signature. In addition, such a block contains the states
|
|
* which cannot leave their block in one step ("silent" states) and which
|
|
* can only reach states of this particular new block. Other blocks
|
|
* consist of silent states which can reach more than one particular
|
|
* signature block. For these kind of blocks, we have a new block for
|
|
* each combination of new blocks they might reach. For instance, if
|
|
* there are new blocks (based on a signature) A,B,C, we add blocks
|
|
* {A,B},{B,C} and {A,B,C}, containing silent states which can reach
|
|
* A and B / B and C / A, B and C.
|
|
*
|
|
* @param oldBlock block to refine
|
|
* @param newBlocks list of new blocks generated
|
|
*/
|
|
@Override
|
|
protected void refineBlock(HashSet<Integer> oldBlock,
|
|
ArrayList<HashSet<Integer>> newBlocks) {
|
|
ArrayList<Integer> nonSilent = new ArrayList<Integer>(oldBlock.size());
|
|
HashSet<Integer> silent = new HashSet<Integer>();
|
|
HashMap<HashMap<HashSet<Integer>, Function>, HashSet<Integer>> signatures = new HashMap<HashMap<HashSet<Integer>, Function>, HashSet<Integer>>();
|
|
HashMap<Integer, HashSet<Integer>> stateToBlock = new HashMap<Integer, HashSet<Integer>>();
|
|
/* compute signatures of states of old block and divide into silent/
|
|
* nonsilent states. Silent states are states which cannot leave the
|
|
* block in one step. */
|
|
for (int state : oldBlock) {
|
|
HashMap<HashSet<Integer>, Function> signature = stateSignature(state, oldBlock);
|
|
if (signature != null) {
|
|
nonSilent.add(state);
|
|
HashSet<Integer> newBlock = signatures.get(signature);
|
|
if (newBlock == null) {
|
|
newBlock = new HashSet<Integer>();
|
|
signatures.put(signature, newBlock);
|
|
}
|
|
newBlock.add(state);
|
|
stateToBlock.put(state, newBlock);
|
|
} else {
|
|
silent.add(state);
|
|
}
|
|
}
|
|
|
|
/* non-silent states reach only the new block they are contained in */
|
|
HashMap<Integer, HashSet<HashSet<Integer>>> reachWhichBlocks = new HashMap<Integer, HashSet<HashSet<Integer>>>();
|
|
for (int state : oldBlock) {
|
|
HashSet<HashSet<Integer>> predReachBlocks = new HashSet<HashSet<Integer>>();
|
|
if (!silent.contains(state)) {
|
|
predReachBlocks.add(stateToBlock.get(state));
|
|
}
|
|
reachWhichBlocks.put(state, predReachBlocks);
|
|
}
|
|
|
|
/* collect all silent states which can reach a particular
|
|
* non-silent state by performing a backwards depth-first search.
|
|
* Mark silent states one comes across with the block of the
|
|
* non-silent state. We can already stop the search if we know
|
|
* that the state has previously been visited from another
|
|
* state from the same block. */
|
|
for (int state : nonSilent) {
|
|
HashSet<Integer> block = stateToBlock.get(state);
|
|
ArrayDeque<Integer> stack = new ArrayDeque<Integer>();
|
|
stack.push(state);
|
|
while (!stack.isEmpty()) {
|
|
int stackState = stack.pop();
|
|
for (int predState : origPmc.incoming.get(stackState)) {
|
|
HashSet<HashSet<Integer>> predReachBlocks = reachWhichBlocks.get(predState);
|
|
if (oldBlock.contains(predState) && silent.contains(predState) && !predReachBlocks.contains(block)) {
|
|
predReachBlocks.add(block);
|
|
stack.push(predState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* compute new blocks, add the nonempty ones to list of new blocks */
|
|
HashMap<HashSet<HashSet<Integer>>, HashSet<Integer>> remap = new HashMap<HashSet<HashSet<Integer>>, HashSet<Integer>>();
|
|
for (Entry<Integer, HashSet<HashSet<Integer>>> entry : reachWhichBlocks.entrySet()) {
|
|
HashSet<Integer> sigStates = remap.get(entry.getValue());
|
|
if (sigStates == null) {
|
|
sigStates = new HashSet<Integer>();
|
|
remap.put(entry.getValue(), sigStates);
|
|
}
|
|
sigStates.add(entry.getKey());
|
|
}
|
|
for (HashSet<Integer> block : remap.values()) {
|
|
if (!block.isEmpty()) {
|
|
newBlocks.add(block);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build the weak bisimulation quotient from the blocks computed.
|
|
* Transition probabilities are basically based on the weak bisimulation
|
|
* signature. However, we must take care that we use a non-silent state
|
|
* to compute transition probabilties from. Also, states which can never
|
|
* leave their block after an arbitrary number of steps ("divergent"
|
|
* states) must lead to adding a self loop in their containing block.
|
|
*/
|
|
@Override
|
|
protected void buildQuotient() {
|
|
optPmc = new MutablePMC(origPmc.getFunctionFactory(), blocks.size(), origPmc.isUseRewards(), false);
|
|
for (int newState = 0; newState < blocks.size(); newState++) {
|
|
HashMap<HashSet<Integer>, Function> signature = null;
|
|
int oldState = -1;
|
|
HashSet<Integer> fromBlock = blocks.get(newState);
|
|
Iterator<Integer> iter = fromBlock.iterator();
|
|
while (iter.hasNext()) {
|
|
oldState = iter.next();
|
|
signature = stateSignature(oldState, fromBlock);
|
|
if (signature != null) {
|
|
break;
|
|
}
|
|
}
|
|
if (signature == null) {
|
|
optPmc.addTransition(newState, newState, origPmc.getFunctionFactory().getOne());
|
|
} else {
|
|
for (Entry<HashSet<Integer>, Function> entry : signature.entrySet()) {
|
|
optPmc.addTransition(newState, blockToNumber.get(entry.getKey()), entry.getValue());
|
|
}
|
|
}
|
|
if (origPmc.isUseRewards()) {
|
|
optPmc.setReward(newState, origPmc.getReward(oldState));
|
|
} else {
|
|
optPmc.setTargetState(newState, origPmc.isTargetState(oldState));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates an initial partitioning.
|
|
* This function is based on the of the {@code Lumper} class. However,
|
|
* for the weak bisimulation lumping to work correctly, for each block
|
|
* of the initial partitioning, we have to split off "divergent" states.
|
|
* Divergent states are states which can never leave their block, after
|
|
* any number of steps.
|
|
*/
|
|
@Override
|
|
protected void createInitialPartition() {
|
|
super.createInitialPartition();
|
|
ArrayList<HashSet<Integer>> newBlocks = new ArrayList<HashSet<Integer>>();
|
|
while (partition.mayChange()) {
|
|
HashSet<Integer> oldBlock = partition.nextChangeableBlock();
|
|
HashSet<Integer> leaveSet = new HashSet<Integer>();
|
|
ArrayList<Integer> directLeaving = new ArrayList<Integer>();
|
|
for (int state : oldBlock) {
|
|
for (int toState : origPmc.transitionTargets.get(state)) {
|
|
if (!oldBlock.contains(toState)) {
|
|
leaveSet.add(state);
|
|
directLeaving.add(state);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (int state : directLeaving) {
|
|
ArrayDeque<Integer> stack = new ArrayDeque<Integer>();
|
|
stack.push(state);
|
|
while (!stack.isEmpty()) {
|
|
int leaving = stack.pop();
|
|
for (int inState : origPmc.incoming.get(leaving)) {
|
|
if (oldBlock.contains(inState) && !leaveSet.contains(inState)) {
|
|
leaveSet.add(inState);
|
|
stack.push(inState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!leaveSet.isEmpty()) {
|
|
newBlocks.add(leaveSet);
|
|
}
|
|
HashSet<Integer> staying = new HashSet<Integer>(oldBlock);
|
|
staying.removeAll(leaveSet);
|
|
if (!staying.isEmpty()) {
|
|
newBlocks.add(staying);
|
|
}
|
|
}
|
|
partition.addBlocks(newBlocks);
|
|
partition.markAllBlocksAsNew();
|
|
}
|
|
}
|