Browse Source
refactoring of bisimulation class in the prospect of extending it to (CT)MDPs, not yet done
refactoring of bisimulation class in the prospect of extending it to (CT)MDPs, not yet done
Former-commit-id: 09f47ad977
main
15 changed files with 2000 additions and 2130 deletions
-
2src/CMakeLists.txt
-
1538src/storage/DeterministicModelBisimulationDecomposition.cpp
-
590src/storage/DeterministicModelBisimulationDecomposition.h
-
259src/storage/bisimulation/BisimulationDecomposition.cpp
-
222src/storage/bisimulation/BisimulationDecomposition.h
-
132src/storage/bisimulation/Block.cpp
-
132src/storage/bisimulation/Block.h
-
681src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp
-
142src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h
-
273src/storage/bisimulation/Partition.cpp
-
142src/storage/bisimulation/Partition.h
-
6src/storage/expressions/Expression.cpp
-
7src/storage/expressions/Expression.h
-
2src/utility/storm.h
-
2test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp
1538
src/storage/DeterministicModelBisimulationDecomposition.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,590 +0,0 @@ |
|||
#ifndef STORM_STORAGE_DeterministicModelBisimulationDecomposition_H_ |
|||
#define STORM_STORAGE_DeterministicModelBisimulationDecomposition_H_ |
|||
|
|||
#include <queue> |
|||
#include <deque> |
|||
|
|||
#include "src/storage/sparse/StateType.h" |
|||
#include "src/storage/Decomposition.h" |
|||
#include "src/storage/StateBlock.h" |
|||
#include "src/logic/Formulas.h" |
|||
#include "src/models/sparse/Dtmc.h" |
|||
#include "src/models/sparse/Ctmc.h" |
|||
#include "src/storage/Distribution.h" |
|||
#include "src/utility/constants.h" |
|||
#include "src/utility/OsDetection.h" |
|||
#include "src/exceptions/InvalidOperationException.h" |
|||
|
|||
namespace storm { |
|||
namespace utility { |
|||
template <typename ValueType> class ConstantsComparator; |
|||
} |
|||
|
|||
namespace storage { |
|||
|
|||
/*! |
|||
* This class represents the decomposition model into its (strong) bisimulation quotient. |
|||
*/ |
|||
template <typename ValueType> |
|||
class DeterministicModelBisimulationDecomposition : public Decomposition<StateBlock> { |
|||
public: |
|||
// A class that offers the possibility to customize the bisimulation. |
|||
struct Options { |
|||
// Creates an object representing the default values for all options. |
|||
Options(); |
|||
|
|||
/*! |
|||
* Creates an object representing the options necessary to obtain the quotient while still preserving |
|||
* the given formula. |
|||
* |
|||
* @param The model for which the quotient model shall be computed. This needs to be given in order to |
|||
* derive a suitable initial partition. |
|||
* @param formula The formula that is to be preserved. |
|||
*/ |
|||
Options(storm::models::sparse::Model<ValueType> const& model, storm::logic::Formula const& formula); |
|||
|
|||
/*! |
|||
* Creates an object representing the options necessary to obtain the smallest quotient while still |
|||
* preserving the given formula. |
|||
* |
|||
* @param The model for which the quotient model shall be computed. This needs to be given in order to |
|||
* derive a suitable initial partition. |
|||
* @param formulas The formula that is to be preserved. |
|||
*/ |
|||
Options(storm::models::sparse::Model<ValueType> const& model, std::vector<std::shared_ptr<storm::logic::Formula>> const& formulas); |
|||
|
|||
// A flag that indicates whether a measure driven initial partition is to be used. If this flag is set |
|||
// to true, the two optional pairs phiStatesAndLabel and psiStatesAndLabel must be set. Then, the |
|||
// measure driven initial partition wrt. to the states phi and psi is taken. |
|||
bool measureDrivenInitialPartition; |
|||
boost::optional<storm::storage::BitVector> phiStates; |
|||
boost::optional<storm::storage::BitVector> psiStates; |
|||
|
|||
// An optional set of strings that indicate which of the atomic propositions of the model are to be |
|||
// respected and which may be ignored. If not given, all atomic propositions of the model are respected. |
|||
boost::optional<std::set<std::string>> respectedAtomicPropositions; |
|||
|
|||
// A flag that indicates whether or not the state-rewards of the model are to be respected (and should |
|||
// be kept in the quotient model, if one is built). |
|||
bool keepRewards; |
|||
|
|||
// A flag that indicates whether a strong or a weak bisimulation is to be computed. |
|||
bool weak; |
|||
|
|||
// A flag that indicates whether step-bounded properties are to be preserved. This may only be set to tru |
|||
// when computing strong bisimulation equivalence. |
|||
bool bounded; |
|||
|
|||
// A flag that governs whether the quotient model is actually built or only the decomposition is computed. |
|||
bool buildQuotient; |
|||
|
|||
private: |
|||
/*! |
|||
* Sets the options under the assumption that the given formula is the only one that is to be checked. |
|||
* |
|||
* @param model The model for which to preserve the formula. |
|||
* @param formula The only formula to check. |
|||
*/ |
|||
void preserveSingleFormula(storm::models::sparse::Model<ValueType> const& model, storm::logic::Formula const& formula); |
|||
}; |
|||
|
|||
/*! |
|||
* Decomposes the given DTMC into equivalance classes of a bisimulation. Which kind of bisimulation is |
|||
* computed, is customizable via the given options. |
|||
* |
|||
* @param model The model to decompose. |
|||
* @param options The options that customize the computed bisimulation. |
|||
*/ |
|||
DeterministicModelBisimulationDecomposition(storm::models::sparse::Dtmc<ValueType> const& model, Options const& options = Options()); |
|||
|
|||
/*! |
|||
* Decomposes the given CTMC into equivalance classes of a bisimulation. Which kind of bisimulation is |
|||
* computed, is customizable via the given options. |
|||
* |
|||
* @param model The model to decompose. |
|||
* @param options The options that customize the computed bisimulation. |
|||
*/ |
|||
DeterministicModelBisimulationDecomposition(storm::models::sparse::Ctmc<ValueType> const& model, Options const& options = Options()); |
|||
|
|||
/*! |
|||
* Retrieves the quotient of the model under the previously computed bisimulation. |
|||
* |
|||
* @return The quotient model. |
|||
*/ |
|||
std::shared_ptr<storm::models::sparse::DeterministicModel<ValueType>> getQuotient() const; |
|||
|
|||
private: |
|||
enum class BisimulationType { Strong, WeakDtmc, WeakCtmc }; |
|||
|
|||
class Partition; |
|||
|
|||
class Block { |
|||
public: |
|||
typedef typename std::list<Block>::iterator iterator; |
|||
typedef typename std::list<Block>::const_iterator const_iterator; |
|||
|
|||
// Creates a new block with the given begin and end. |
|||
Block(storm::storage::sparse::state_type begin, storm::storage::sparse::state_type end, Block* prev, Block* next, std::size_t id); |
|||
|
|||
// Prints the block. |
|||
void print(Partition const& partition) const; |
|||
|
|||
// Sets the beginning index of the block. |
|||
void setBegin(storm::storage::sparse::state_type begin); |
|||
|
|||
// Moves the beginning index of the block one step further. |
|||
void incrementBegin(); |
|||
|
|||
// Sets the end index of the block. |
|||
void setEnd(storm::storage::sparse::state_type end); |
|||
|
|||
// Returns the beginning index of the block. |
|||
storm::storage::sparse::state_type getBegin() const; |
|||
|
|||
// Returns the beginning index of the block. |
|||
storm::storage::sparse::state_type getEnd() const; |
|||
|
|||
// Retrieves the original beginning index of the block in case the begin index has been moved. |
|||
storm::storage::sparse::state_type getOriginalBegin() const; |
|||
|
|||
// Returns the iterator the block in the list of overall blocks. |
|||
iterator getIterator() const; |
|||
|
|||
// Returns the iterator the block in the list of overall blocks. |
|||
void setIterator(iterator it); |
|||
|
|||
// Returns the iterator the next block in the list of overall blocks if it exists. |
|||
iterator getNextIterator() const; |
|||
|
|||
// Returns the iterator the next block in the list of overall blocks if it exists. |
|||
iterator getPreviousIterator() const; |
|||
|
|||
// Gets the next block (if there is one). |
|||
Block& getNextBlock(); |
|||
|
|||
// Gets the next block (if there is one). |
|||
Block const& getNextBlock() const; |
|||
|
|||
// Gets a pointer to the next block (if there is one). |
|||
Block* getNextBlockPointer(); |
|||
|
|||
// Retrieves whether the block as a successor block. |
|||
bool hasNextBlock() const; |
|||
|
|||
// Gets the previous block (if there is one). |
|||
Block& getPreviousBlock(); |
|||
|
|||
// Gets a pointer to the previous block (if there is one). |
|||
Block* getPreviousBlockPointer(); |
|||
|
|||
// Gets the next block (if there is one). |
|||
Block const& getPreviousBlock() const; |
|||
|
|||
// Retrieves whether the block as a successor block. |
|||
bool hasPreviousBlock() const; |
|||
|
|||
// Checks consistency of the information in the block. |
|||
bool check() const; |
|||
|
|||
// Retrieves the number of states in this block. |
|||
std::size_t getNumberOfStates() const; |
|||
|
|||
// Checks whether the block is marked as a splitter. |
|||
bool isMarkedAsSplitter() const; |
|||
|
|||
// Marks the block as being a splitter. |
|||
void markAsSplitter(); |
|||
|
|||
// Removes the mark. |
|||
void unmarkAsSplitter(); |
|||
|
|||
// Retrieves the ID of the block. |
|||
std::size_t getId() const; |
|||
|
|||
// Retrieves the marked position in the block. |
|||
storm::storage::sparse::state_type getMarkedPosition() const; |
|||
|
|||
// Sets the marked position to the given value.. |
|||
void setMarkedPosition(storm::storage::sparse::state_type position); |
|||
|
|||
// Increases the marked position by one. |
|||
void incrementMarkedPosition(); |
|||
|
|||
// Resets the marked position to the begin of the block. |
|||
void resetMarkedPosition(); |
|||
|
|||
// Retrieves whether the block is marked as a predecessor. |
|||
bool isMarkedAsPredecessor() const; |
|||
|
|||
// Marks the block as being a predecessor block. |
|||
void markAsPredecessorBlock(); |
|||
|
|||
// Removes the marking. |
|||
void unmarkAsPredecessorBlock(); |
|||
|
|||
// Sets whether or not the block is to be interpreted as absorbing. |
|||
void setAbsorbing(bool absorbing); |
|||
|
|||
// Retrieves whether the block is to be interpreted as absorbing. |
|||
bool isAbsorbing() const; |
|||
|
|||
// Sets the representative state of this block |
|||
void setRepresentativeState(storm::storage::sparse::state_type representativeState); |
|||
|
|||
// Retrieves the representative state for this block. |
|||
bool hasRepresentativeState() const; |
|||
|
|||
// Retrieves the representative state for this block. |
|||
storm::storage::sparse::state_type getRepresentativeState() const; |
|||
|
|||
private: |
|||
// An iterator to itself. This is needed to conveniently insert elements in the overall list of blocks |
|||
// kept by the partition. |
|||
iterator selfIt; |
|||
|
|||
// Pointers to the next and previous block. |
|||
Block* next; |
|||
Block* prev; |
|||
|
|||
// The begin and end indices of the block in terms of the state vector of the partition. |
|||
storm::storage::sparse::state_type begin; |
|||
storm::storage::sparse::state_type end; |
|||
|
|||
// A field that can be used for marking the block. |
|||
bool markedAsSplitter; |
|||
|
|||
// A field that can be used for marking the block as a predecessor block. |
|||
bool markedAsPredecessorBlock; |
|||
|
|||
// A position that can be used to store a certain position within the block. |
|||
storm::storage::sparse::state_type markedPosition; |
|||
|
|||
// A flag indicating whether the block is to be interpreted as absorbing or not. |
|||
bool absorbing; |
|||
|
|||
// The ID of the block. This is only used for debugging purposes. |
|||
std::size_t id; |
|||
|
|||
// An optional representative state for the block. If this is set, this state is used to derive the |
|||
// atomic propositions of the meta state in the quotient model. |
|||
boost::optional<storm::storage::sparse::state_type> representativeState; |
|||
}; |
|||
|
|||
class Partition { |
|||
public: |
|||
friend class Block; |
|||
|
|||
/*! |
|||
* Creates a partition with one block consisting of all the states. |
|||
* |
|||
* @param numberOfStates The number of states the partition holds. |
|||
* @param keepSilentProbabilities A flag indicating whether or not silent probabilities are to be tracked. |
|||
*/ |
|||
Partition(std::size_t numberOfStates, bool keepSilentProbabilities = false); |
|||
|
|||
/*! |
|||
* Creates a partition with three blocks: one with all phi states, one with all psi states and one with |
|||
* all other states. The former two blocks are marked as being absorbing, because their outgoing |
|||
* transitions shall not be taken into account for future refinement. |
|||
* |
|||
* @param numberOfStates The number of states the partition holds. |
|||
* @param prob0States The states which have probability 0 of satisfying phi until psi. |
|||
* @param prob1States The states which have probability 1 of satisfying phi until psi. |
|||
* @param representativeProb1State If the set of prob1States is non-empty, this needs to be a state |
|||
* that is representative for this block in the sense that the state representing this block in the quotient |
|||
* model will receive exactly the atomic propositions of the representative state. |
|||
* @param keepSilentProbabilities A flag indicating whether or not silent probabilities are to be tracked. |
|||
*/ |
|||
Partition(std::size_t numberOfStates, storm::storage::BitVector const& prob0States, storm::storage::BitVector const& prob1States, boost::optional<storm::storage::sparse::state_type> representativeProb1State, bool keepSilentProbabilities = false); |
|||
|
|||
Partition() = default; |
|||
Partition(Partition const& other) = default; |
|||
Partition& operator=(Partition const& other) = default; |
|||
|
|||
// Define move-construct and move-assignment explicitly to make sure they exist (as they are vital to |
|||
// keep validity of pointers. |
|||
Partition(Partition&& other) : blocks(std::move(other.blocks)), numberOfBlocks(other.numberOfBlocks), stateToBlockMapping(std::move(other.stateToBlockMapping)), statesAndValues(std::move(other.statesAndValues)), positions(std::move(other.positions)), keepSilentProbabilities(other.keepSilentProbabilities), silentProbabilities(std::move(other.silentProbabilities)) { |
|||
// Intentionally left empty. |
|||
} |
|||
|
|||
Partition& operator=(Partition&& other) { |
|||
if (this != &other) { |
|||
blocks = std::move(other.blocks); |
|||
numberOfBlocks = other.numberOfBlocks; |
|||
stateToBlockMapping = std::move(other.stateToBlockMapping); |
|||
statesAndValues = std::move(other.statesAndValues); |
|||
positions = std::move(other.positions); |
|||
keepSilentProbabilities = other.keepSilentProbabilities; |
|||
silentProbabilities = std::move(other.silentProbabilities); |
|||
} |
|||
|
|||
return *this; |
|||
} |
|||
|
|||
/*! |
|||
* Splits all blocks of the partition such that afterwards all blocks contain only states with the label |
|||
* or no labeled state at all. |
|||
*/ |
|||
void splitLabel(storm::storage::BitVector const& statesWithLabel); |
|||
|
|||
// Retrieves the size of the partition, i.e. the number of blocks. |
|||
std::size_t size() const; |
|||
|
|||
// Prints the partition to the standard output. |
|||
void print() const; |
|||
|
|||
// Splits the block at the given position and inserts a new block after the current one holding the rest |
|||
// of the states. |
|||
Block& splitBlock(Block& block, storm::storage::sparse::state_type position); |
|||
|
|||
// Inserts a block before the given block. The new block will cover all states between the beginning |
|||
// of the given block and the end of the previous block. |
|||
Block& insertBlock(Block& block); |
|||
|
|||
// Retrieves the blocks of the partition. |
|||
std::list<Block> const& getBlocks() const; |
|||
|
|||
// Retrieves the blocks of the partition. |
|||
std::list<Block>& getBlocks(); |
|||
|
|||
// Checks the partition for internal consistency. |
|||
bool check() const; |
|||
|
|||
// Returns an iterator to the beginning of the states of the given block. |
|||
typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::iterator getBegin(Block const& block); |
|||
|
|||
// Returns an iterator to the beginning of the states of the given block. |
|||
typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::iterator getEnd(Block const& block); |
|||
|
|||
// Returns an iterator to the beginning of the states of the given block. |
|||
typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::const_iterator getBegin(Block const& block) const; |
|||
|
|||
// Returns an iterator to the beginning of the states of the given block. |
|||
typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::const_iterator getEnd(Block const& block) const; |
|||
|
|||
// Swaps the positions of the two given states. |
|||
void swapStates(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2); |
|||
|
|||
// Swaps the positions of the two states given by their positions. |
|||
void swapStatesAtPositions(storm::storage::sparse::state_type position1, storm::storage::sparse::state_type position2); |
|||
|
|||
// Retrieves the block of the given state. |
|||
Block& getBlock(storm::storage::sparse::state_type state); |
|||
|
|||
// Retrieves the block of the given state. |
|||
Block const& getBlock(storm::storage::sparse::state_type state) const; |
|||
|
|||
// Retrieves the position of the given state. |
|||
storm::storage::sparse::state_type const& getPosition(storm::storage::sparse::state_type state) const; |
|||
|
|||
// Retrieves the position of the given state. |
|||
void setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position); |
|||
|
|||
// Sets the position of the state to the given position. |
|||
storm::storage::sparse::state_type const& getState(storm::storage::sparse::state_type position) const; |
|||
|
|||
// Retrieves the value for the given state. |
|||
ValueType const& getValue(storm::storage::sparse::state_type state) const; |
|||
|
|||
// Retrieves the value at the given position. |
|||
ValueType const& getValueAtPosition(storm::storage::sparse::state_type position) const; |
|||
|
|||
// Sets the given value for the given state. |
|||
void setValue(storm::storage::sparse::state_type state, ValueType value); |
|||
|
|||
// Retrieves the vector with the probabilities going into the current splitter. |
|||
std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>& getStatesAndValues(); |
|||
|
|||
// Increases the value for the given state by the specified amount. |
|||
void increaseValue(storm::storage::sparse::state_type state, ValueType value); |
|||
|
|||
// Updates the block mapping for the given range of states to the specified block. |
|||
void updateBlockMapping(Block& block, typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::iterator first, typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::iterator last); |
|||
|
|||
// Retrieves the first block of the partition. |
|||
Block& getFirstBlock(); |
|||
|
|||
// Retrieves whether the given state is fully silent (only in case the silent probabilities are tracked). |
|||
bool isSilent(storm::storage::sparse::state_type state, storm::utility::ConstantsComparator<ValueType> const& comparator) const; |
|||
|
|||
// Retrieves whether the given state has a non-zero silent probability. |
|||
bool hasSilentProbability(storm::storage::sparse::state_type state, storm::utility::ConstantsComparator<ValueType> const& comparator) const; |
|||
|
|||
// Retrieves the silent probability (i.e. the probability to stay within the own equivalence class). |
|||
ValueType const& getSilentProbability(storm::storage::sparse::state_type state) const; |
|||
|
|||
// Sets the silent probabilities for all the states in the range to their values in the range. |
|||
void setSilentProbabilities(typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::iterator first, typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::iterator last); |
|||
|
|||
// Sets the silent probabilities for all states in the range to zero. |
|||
void setSilentProbabilitiesToZero(typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::iterator first, typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::iterator last); |
|||
|
|||
// Sets the silent probability for the given state to the given value. |
|||
void setSilentProbability(storm::storage::sparse::state_type state, ValueType const& value); |
|||
|
|||
private: |
|||
// The list of blocks in the partition. |
|||
std::list<Block> blocks; |
|||
|
|||
// The number of blocks of the partition. Although this could be retrieved via block.size(), we want to |
|||
// guarantee constant access time (which is currently not guaranteed by gcc'c standard libary). |
|||
uint_fast64_t numberOfBlocks; |
|||
|
|||
// A mapping of states to their blocks. |
|||
std::vector<Block*> stateToBlockMapping; |
|||
|
|||
// A vector containing all the states and their values. It is ordered in a special way such that the |
|||
// blocks only need to define their start/end indices. |
|||
std::vector<std::pair<storm::storage::sparse::state_type, ValueType>> statesAndValues; |
|||
|
|||
// This vector keeps track of the position of each state in the state vector. |
|||
std::vector<storm::storage::sparse::state_type> positions; |
|||
|
|||
// A flag that indicates whether or not the vector with silent probabilities exists. |
|||
bool keepSilentProbabilities; |
|||
|
|||
// This vector keeps track of the silent probabilities (i.e. the probabilities going into the very own |
|||
// equivalence class) of each state. This means that a state is silent iff its entry is non-zero. |
|||
std::vector<ValueType> silentProbabilities; |
|||
}; |
|||
|
|||
/*! |
|||
* Performs the partition refinement on the model and thereby computes the equivalence classes under strong |
|||
* bisimulation equivalence. If required, the quotient model is built and may be retrieved using |
|||
* getQuotient(). |
|||
* |
|||
* @param model The model on whose state space to compute the coarses strong bisimulation relation. |
|||
* @param atomicPropositions The set of atomic propositions that the bisimulation considers. |
|||
* @param backwardTransitions The backward transitions of the model. |
|||
* @param The initial partition. |
|||
* @param bisimulationType The kind of bisimulation that is to be computed. |
|||
* @param buildQuotient If set, the quotient model is built and may be retrieved using the getQuotient() |
|||
* method. |
|||
* @param comparator A comparator used for comparing constants. |
|||
*/ |
|||
template<typename ModelType> |
|||
void partitionRefinement(ModelType const& model, std::set<std::string> const& atomicPropositions, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, Partition& partition, BisimulationType bisimulationType, bool keepRewards, bool buildQuotient, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
/*! |
|||
* Refines the partition based on the provided splitter. After calling this method all blocks are stable |
|||
* with respect to the splitter. |
|||
* |
|||
* @param forwardTransitions The forward transitions of the model. |
|||
* @param backwardTransitions A matrix that can be used to retrieve the predecessors (and their |
|||
* probabilities). |
|||
* @param splitter The splitter to use. |
|||
* @param partition The partition to split. |
|||
* @param bisimulationType The kind of bisimulation that is to be computed. |
|||
* @param splitterQueue A queue into which all blocks that were split are inserted so they can be treated |
|||
* as splitters in the future. |
|||
* @param comparator A comparator used for comparing constants. |
|||
*/ |
|||
void refinePartition(storm::storage::SparseMatrix<ValueType> const& forwardTransitions, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, Block& splitter, Partition& partition, BisimulationType bisimulationType, std::deque<Block*>& splitterQueue, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
/*! |
|||
* Refines the block based on their probability values (leading into the splitter). |
|||
* |
|||
* @param block The block to refine. |
|||
* @param partition The partition that contains the block. |
|||
* @param bisimulationType The kind of bisimulation that is to be computed. |
|||
* @param splitterQueue A queue into which all blocks that were split are inserted so they can be treated |
|||
* as splitters in the future. |
|||
* @param comparator A comparator used for comparing constants. |
|||
*/ |
|||
void refineBlockProbabilities(Block& block, Partition& partition, BisimulationType bisimulationType, std::deque<Block*>& splitterQueue, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
void refineBlockWeak(Block& block, Partition& partition, storm::storage::SparseMatrix<ValueType> const& forwardTransitions, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, std::deque<Block*>& splitterQueue, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
/*! |
|||
* Determines the split offsets in the given block. |
|||
* |
|||
* @param block The block that is to be analyzed for splits. |
|||
* @param partition The partition that contains the block. |
|||
* @param comparator A comparator used for comparing constants. |
|||
*/ |
|||
std::vector<uint_fast64_t> getSplitPointsWeak(Block& block, Partition& partition, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
/*! |
|||
* Builds the quotient model based on the previously computed equivalence classes (stored in the blocks |
|||
* of the decomposition. |
|||
* |
|||
* @param model The model whose state space was used for computing the equivalence classes. This is used for |
|||
* determining the transitions of each equivalence class. |
|||
* @param selectedAtomicPropositions The set of atomic propositions that was considered by the bisimulation. |
|||
* The quotient will only have these atomic propositions. |
|||
* @param partition The previously computed partition. This is used for quickly retrieving the block of a |
|||
* state. |
|||
* @param bisimulationType The kind of bisimulation that is to be computed. |
|||
* @param comparator A comparator used for comparing constants. |
|||
*/ |
|||
template<typename ModelType> |
|||
void buildQuotient(ModelType const& model, std::set<std::string> const& selectedAtomicPropositions, Partition const& partition, BisimulationType bisimulationType, bool keepRewards, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
/*! |
|||
* Creates the measure-driven initial partition for reaching psi states from phi states. |
|||
* |
|||
* @param model The model whose state space is partitioned based on reachability of psi states from phi |
|||
* states. |
|||
* @param backwardTransitions The backward transitions of the model. |
|||
* @param phiStates The phi states in the model. |
|||
* @param psiStates The psi states in the model. |
|||
* @param bisimulationType The kind of bisimulation that is to be computed. |
|||
* @param bounded If set to true, the initial partition will be chosen in such a way that preserves bounded |
|||
* reachability queries. |
|||
* @param comparator A comparator used for comparing constants. |
|||
* @return The resulting partition. |
|||
*/ |
|||
template<typename ModelType> |
|||
Partition getMeasureDrivenInitialPartition(ModelType const& model, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, BisimulationType bisimulationType, bool keepRewards = true, bool bounded = false, storm::utility::ConstantsComparator<ValueType> const& comparator = storm::utility::ConstantsComparator<ValueType>()); |
|||
|
|||
/*! |
|||
* Creates the initial partition based on all the labels in the given model. |
|||
* |
|||
* @param model The model whose state space is partitioned based on its labels. |
|||
* @param backwardTransitions The backward transitions of the model. |
|||
* @param bisimulationType The kind of bisimulation that is to be computed. |
|||
* @param atomicPropositions The set of atomic propositions to respect. If not given, then all atomic |
|||
* propositions of the model are respected. |
|||
* @param comparator A comparator used for comparing constants. |
|||
* @return The resulting partition. |
|||
*/ |
|||
template<typename ModelType> |
|||
Partition getLabelBasedInitialPartition(ModelType const& model, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, BisimulationType bisimulationType, boost::optional<std::set<std::string>> const& atomicPropositions = boost::optional<std::set<std::string>>(), bool keepRewards = true, storm::utility::ConstantsComparator<ValueType> const& comparator = storm::utility::ConstantsComparator<ValueType>()); |
|||
|
|||
/*! |
|||
* Splits all blocks of the given partition into a block that contains all divergent states and another block |
|||
* containing the non-divergent states. |
|||
* |
|||
* @param model The model from which to look-up the probabilities. |
|||
* @param backwardTransitions The backward transitions of the model. |
|||
* @param partition The partition that holds the silent probabilities. |
|||
*/ |
|||
template<typename ModelType> |
|||
void splitOffDivergentStates(ModelType const& model, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, Partition& partition); |
|||
|
|||
/*! |
|||
* Initializes the silent probabilities by traversing all blocks and adding the probability of going to |
|||
* the very own equivalence class for each state. |
|||
* |
|||
* @param model The model from which to look-up the probabilities. |
|||
* @param partition The partition that holds the silent probabilities. |
|||
*/ |
|||
template<typename ModelType> |
|||
void initializeSilentProbabilities(ModelType const& model, Partition& partition); |
|||
|
|||
/*! |
|||
* Splits all blocks of the partition in a way such that the states of each block agree on the rewards. |
|||
* |
|||
* @param stateRewardVector The state reward vector of the model. |
|||
* @param partition The partition that is to be split. |
|||
* @param comparator A comparator used for comparing constants. |
|||
*/ |
|||
void splitRewards(std::vector<ValueType> const& stateRewardVector, Partition& partition, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
// If required, a quotient model is built and stored in this member. |
|||
std::shared_ptr<storm::models::sparse::DeterministicModel<ValueType>> quotient; |
|||
}; |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_STORAGE_DeterministicModelBisimulationDecomposition_H_ */ |
@ -0,0 +1,259 @@ |
|||
#include "src/storage/bisimulation/BisimulationDecomposition.h"
|
|||
|
|||
#include <chrono>
|
|||
|
|||
#include "src/models/sparse/Dtmc.h"
|
|||
#include "src/models/sparse/Ctmc.h"
|
|||
|
|||
#include "src/modelchecker/propositional/SparsePropositionalModelChecker.h"
|
|||
#include "src/modelchecker/results/ExplicitQualitativeCheckResult.h"
|
|||
|
|||
#include "src/settings/SettingsManager.h"
|
|||
#include "src/settings/modules/GeneralSettings.h"
|
|||
|
|||
#include "src/utility/macros.h"
|
|||
#include "src/exceptions/IllegalFunctionCallException.h"
|
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
|
|||
using namespace bisimulation; |
|||
|
|||
template<typename ModelType> |
|||
BisimulationDecomposition<ModelType>::Options::Options(ModelType const& model, storm::logic::Formula const& formula) : Options() { |
|||
this->preserveSingleFormula(model, formula); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
BisimulationDecomposition<ModelType>::Options::Options(ModelType const& model, std::vector<std::shared_ptr<storm::logic::Formula>> const& formulas) : Options() { |
|||
if (formulas.size() == 1) { |
|||
this->preserveSingleFormula(model, *formulas.front()); |
|||
} else { |
|||
for (auto const& formula : formulas) { |
|||
preserveFormula(model, *formula); |
|||
} |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
BisimulationDecomposition<ModelType>::Options::Options() : measureDrivenInitialPartition(false), phiStates(), psiStates(), respectedAtomicPropositions(), keepRewards(false), type(BisimulationType::Strong), bounded(false), buildQuotient(true) { |
|||
// Intentionally left empty.
|
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void BisimulationDecomposition<ModelType>::Options::preserveFormula(ModelType const& model, storm::logic::Formula const& formula) { |
|||
// Disable the measure driven initial partition.
|
|||
measureDrivenInitialPartition = false; |
|||
phiStates = boost::none; |
|||
psiStates = boost::none; |
|||
|
|||
// Preserve rewards if necessary.
|
|||
keepRewards = keepRewards || formula.containsRewardOperator(); |
|||
|
|||
// Preserve bounded properties if necessary.
|
|||
bounded = bounded || (formula.containsBoundedUntilFormula() || formula.containsNextFormula()); |
|||
|
|||
// Compute the relevant labels and expressions.
|
|||
this->addToRespectedAtomicPropositions(formula.getAtomicExpressionFormulas(), formula.getAtomicLabelFormulas()); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void BisimulationDecomposition<ModelType>::Options::preserveSingleFormula(ModelType const& model, storm::logic::Formula const& formula) { |
|||
keepRewards = formula.containsRewardOperator(); |
|||
|
|||
// We need to preserve bounded properties iff the formula contains a bounded until or a next subformula.
|
|||
bounded = formula.containsBoundedUntilFormula() || formula.containsNextFormula(); |
|||
|
|||
// Compute the relevant labels and expressions.
|
|||
this->addToRespectedAtomicPropositions(formula.getAtomicExpressionFormulas(), formula.getAtomicLabelFormulas()); |
|||
|
|||
// Check whether measure driven initial partition is possible and, if so, set it.
|
|||
this->checkAndSetMeasureDrivenInitialPartition(model, formula); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void BisimulationDecomposition<ModelType>::Options::checkAndSetMeasureDrivenInitialPartition(ModelType const& model, storm::logic::Formula const& formula) { |
|||
std::shared_ptr<storm::logic::Formula const> newFormula = formula.asSharedPointer(); |
|||
|
|||
if (formula.isProbabilityOperatorFormula()) { |
|||
newFormula = formula.asProbabilityOperatorFormula().getSubformula().asSharedPointer(); |
|||
} else if (formula.isRewardOperatorFormula()) { |
|||
newFormula = formula.asRewardOperatorFormula().getSubformula().asSharedPointer(); |
|||
} |
|||
|
|||
std::shared_ptr<storm::logic::Formula const> leftSubformula = std::make_shared<storm::logic::BooleanLiteralFormula>(true); |
|||
std::shared_ptr<storm::logic::Formula const> rightSubformula; |
|||
if (newFormula->isUntilFormula()) { |
|||
leftSubformula = newFormula->asUntilFormula().getLeftSubformula().asSharedPointer(); |
|||
rightSubformula = newFormula->asUntilFormula().getRightSubformula().asSharedPointer(); |
|||
if (leftSubformula->isPropositionalFormula() && rightSubformula->isPropositionalFormula()) { |
|||
measureDrivenInitialPartition = true; |
|||
} |
|||
} else if (newFormula->isEventuallyFormula()) { |
|||
rightSubformula = newFormula->asEventuallyFormula().getSubformula().asSharedPointer(); |
|||
if (rightSubformula->isPropositionalFormula()) { |
|||
measureDrivenInitialPartition = true; |
|||
} |
|||
} else if (newFormula->isReachabilityRewardFormula()) { |
|||
rightSubformula = newFormula->asReachabilityRewardFormula().getSubformula().asSharedPointer(); |
|||
if (rightSubformula->isPropositionalFormula()) { |
|||
measureDrivenInitialPartition = true; |
|||
} |
|||
} |
|||
|
|||
if (measureDrivenInitialPartition) { |
|||
storm::modelchecker::SparsePropositionalModelChecker<ModelType> checker(model); |
|||
std::unique_ptr<storm::modelchecker::CheckResult> phiStatesCheckResult = checker.check(*leftSubformula); |
|||
std::unique_ptr<storm::modelchecker::CheckResult> psiStatesCheckResult = checker.check(*rightSubformula); |
|||
phiStates = phiStatesCheckResult->asExplicitQualitativeCheckResult().getTruthValuesVector(); |
|||
psiStates = psiStatesCheckResult->asExplicitQualitativeCheckResult().getTruthValuesVector(); |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void BisimulationDecomposition<ModelType>::Options::addToRespectedAtomicPropositions(std::vector<std::shared_ptr<storm::logic::AtomicExpressionFormula const>> const& expressions, std::vector<std::shared_ptr<storm::logic::AtomicLabelFormula const>> const& labels) { |
|||
std::set<std::string> labelsToRespect; |
|||
for (auto const& labelFormula : labels) { |
|||
labelsToRespect.insert(labelFormula->getLabel()); |
|||
} |
|||
for (auto const& expressionFormula : expressions) { |
|||
labelsToRespect.insert(expressionFormula->toString()); |
|||
} |
|||
if (!respectedAtomicPropositions) { |
|||
respectedAtomicPropositions = labelsToRespect; |
|||
} else { |
|||
respectedAtomicPropositions.get().insert(labelsToRespect.begin(), labelsToRespect.end()); |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
BisimulationDecomposition<ModelType>::BisimulationDecomposition(ModelType const& model, Options const& options) : model(model), backwardTransitions(model.getBackwardTransitions()), options(options), partition(), comparator(), quotient(nullptr) { |
|||
// Fix the respected atomic propositions if they were not explicitly given.
|
|||
if (!this->options.respectedAtomicPropositions) { |
|||
this->options.respectedAtomicPropositions = model.getStateLabeling().getLabels(); |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void BisimulationDecomposition<ModelType>::computeBisimulationDecomposition() { |
|||
std::chrono::high_resolution_clock::time_point totalStart = std::chrono::high_resolution_clock::now(); |
|||
|
|||
std::chrono::high_resolution_clock::time_point refinementStart = std::chrono::high_resolution_clock::now(); |
|||
this->performPartitionRefinement(); |
|||
std::chrono::high_resolution_clock::duration refinementTime = std::chrono::high_resolution_clock::now() - refinementStart; |
|||
|
|||
std::chrono::high_resolution_clock::time_point extractionStart = std::chrono::high_resolution_clock::now(); |
|||
this->extractDecompositionBlocks(); |
|||
std::chrono::high_resolution_clock::duration extractionTime = std::chrono::high_resolution_clock::now() - extractionStart; |
|||
|
|||
std::chrono::high_resolution_clock::time_point quotientBuildStart = std::chrono::high_resolution_clock::now(); |
|||
if (options.buildQuotient) { |
|||
this->buildQuotient(); |
|||
} |
|||
std::chrono::high_resolution_clock::duration quotientBuildTime = std::chrono::high_resolution_clock::now() - quotientBuildStart; |
|||
|
|||
std::chrono::high_resolution_clock::duration totalTime = std::chrono::high_resolution_clock::now() - totalStart; |
|||
|
|||
if (storm::settings::generalSettings().isShowStatisticsSet()) { |
|||
std::chrono::milliseconds refinementTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(refinementTime); |
|||
std::chrono::milliseconds extractionTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(extractionTime); |
|||
std::chrono::milliseconds quotientBuildTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(quotientBuildTime); |
|||
std::chrono::milliseconds totalTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(totalTime); |
|||
std::cout << std::endl; |
|||
std::cout << "Time breakdown:" << std::endl; |
|||
std::cout << " * time for partitioning: " << refinementTimeInMilliseconds.count() << "ms" << std::endl; |
|||
std::cout << " * time for extraction: " << extractionTimeInMilliseconds.count() << "ms" << std::endl; |
|||
std::cout << " * time for building quotient: " << quotientBuildTimeInMilliseconds.count() << "ms" << std::endl; |
|||
std::cout << "------------------------------------------" << std::endl; |
|||
std::cout << " * total time: " << totalTimeInMilliseconds.count() << "ms" << std::endl; |
|||
std::cout << std::endl; |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void BisimulationDecomposition<ModelType>::performPartitionRefinement() { |
|||
// Insert all blocks into the splitter queue that are initially marked as being a (potential) splitter.
|
|||
std::deque<Block*> splitterQueue; |
|||
std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (Block& a) { if (a.isMarkedAsSplitter()) { splitterQueue.push_back(&a); } } ); |
|||
|
|||
// Then perform the actual splitting until there are no more splitters.
|
|||
while (!splitterQueue.empty()) { |
|||
// Optionally: sort the splitter queue according to some criterion (here: prefer small splitters).
|
|||
std::sort(splitterQueue.begin(), splitterQueue.end(), [] (Block const* b1, Block const* b2) { return b1->getNumberOfStates() < b2->getNumberOfStates(); } ); |
|||
|
|||
// Get and prepare the next splitter.
|
|||
Block* splitter = splitterQueue.front(); |
|||
splitterQueue.pop_front(); |
|||
splitter->unmarkAsSplitter(); |
|||
|
|||
// Now refine the partition using the current splitter.
|
|||
refinePartitionBasedOnSplitter(*splitter, splitterQueue); |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
std::shared_ptr<ModelType> BisimulationDecomposition<ModelType>::getQuotient() const { |
|||
STORM_LOG_THROW(this->quotient != nullptr, storm::exceptions::IllegalFunctionCallException, "Unable to retrieve quotient model from bisimulation decomposition, because it was not built."); |
|||
return this->quotient; |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void BisimulationDecomposition<ModelType>::splitInitialPartitionBasedOnStateRewards() { |
|||
std::vector<ValueType> const& stateRewardVector = model.getUniqueRewardModel()->second.getStateRewardVector(); |
|||
partition.split([&stateRewardVector] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return stateRewardVector[a] < stateRewardVector[b]; }, |
|||
[&stateRewardVector] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return stateRewardVector[a] != stateRewardVector[b]; }); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void BisimulationDecomposition<ModelType>::initializeLabelBasedPartition() { |
|||
|
|||
partition = storm::storage::bisimulation::Partition(model.getNumberOfStates()); |
|||
for (auto const& label : options.respectedAtomicPropositions.get()) { |
|||
if (label == "init") { |
|||
continue; |
|||
} |
|||
partition.splitStates(model.getStates(label)); |
|||
} |
|||
|
|||
// If the model has state rewards, we need to consider them, because otherwise reward properties are not
|
|||
// preserved.
|
|||
if (options.keepRewards && model.hasRewardModel()) { |
|||
this->splitInitialPartitionBasedOnStateRewards(); |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void BisimulationDecomposition<ModelType>::initializeMeasureDrivenPartition() { |
|||
std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = this->getStatesWithProbability01(backwardTransitions, options.phiStates.get(), options.psiStates.get()); |
|||
|
|||
boost::optional<storm::storage::sparse::state_type> representativePsiState; |
|||
if (!options.psiStates.get().empty()) { |
|||
representativePsiState = *options.psiStates.get().begin(); |
|||
} |
|||
|
|||
partition = storm::storage::bisimulation::Partition(model.getNumberOfStates(), statesWithProbability01.first, options.bounded || options.keepRewards ? options.psiStates.get() : statesWithProbability01.second, representativePsiState); |
|||
|
|||
// If the model has state rewards, we need to consider them, because otherwise reward properties are not
|
|||
// preserved.
|
|||
if (options.keepRewards && model.hasRewardModel()) { |
|||
this->splitInitialPartitionBasedOnStateRewards(); |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void BisimulationDecomposition<ModelType>::extractDecompositionBlocks() { |
|||
// Now move the states from the internal partition into their final place in the decomposition. We do so in
|
|||
// a way that maintains the block IDs as indices.
|
|||
this->blocks.resize(partition.size()); |
|||
for (auto const& block : partition.getBlocks()) { |
|||
// We need to sort the states to allow for rapid construction of the blocks.
|
|||
partition.sortBlock(block); |
|||
|
|||
// Convert the state-value-pairs to states only.
|
|||
this->blocks[block.getId()] = block_type(partition.begin(block), partition.end(block), true); |
|||
} |
|||
} |
|||
|
|||
template class BisimulationDecomposition<storm::models::sparse::Dtmc<double>>; |
|||
} |
|||
} |
@ -0,0 +1,222 @@ |
|||
#ifndef STORM_STORAGE_BISIMULATIONDECOMPOSITION_H_ |
|||
#define STORM_STORAGE_BISIMULATIONDECOMPOSITION_H_ |
|||
|
|||
#include <deque> |
|||
|
|||
#include "src/storage/sparse/StateType.h" |
|||
#include "src/storage/Decomposition.h" |
|||
#include "src/storage/StateBlock.h" |
|||
#include "src/storage/bisimulation/Partition.h" |
|||
|
|||
#include "src/logic/Formulas.h" |
|||
|
|||
#include "src/utility/constants.h" |
|||
#include "src/utility/ConstantsComparator.h" |
|||
|
|||
namespace storm { |
|||
namespace utility { |
|||
template <typename ValueType> class ConstantsComparator; |
|||
} |
|||
|
|||
namespace logic { |
|||
class Formula; |
|||
} |
|||
|
|||
namespace storage { |
|||
|
|||
enum class BisimulationType { Strong, Weak }; |
|||
|
|||
/*! |
|||
* This class is the superclass of all decompositions of a sparse model into its bisimulation quotient. |
|||
*/ |
|||
template <typename ModelType> |
|||
class BisimulationDecomposition : public Decomposition<StateBlock> { |
|||
public: |
|||
typedef typename ModelType::ValueType ValueType; |
|||
typedef typename ModelType::RewardModelType RewardModelType; |
|||
|
|||
// A class that offers the possibility to customize the bisimulation. |
|||
struct Options { |
|||
// Creates an object representing the default values for all options. |
|||
Options(); |
|||
|
|||
/*! |
|||
* Creates an object representing the options necessary to obtain the quotient while still preserving |
|||
* the given formula. |
|||
* |
|||
* @param The model for which the quotient model shall be computed. This needs to be given in order to |
|||
* derive a suitable initial partition. |
|||
* @param formula The formula that is to be preserved. |
|||
*/ |
|||
Options(ModelType const& model, storm::logic::Formula const& formula); |
|||
|
|||
/*! |
|||
* Creates an object representing the options necessary to obtain the smallest quotient while still |
|||
* preserving the given formulas. |
|||
* |
|||
* @param The model for which the quotient model shall be computed. This needs to be given in order to |
|||
* derive a suitable initial partition. |
|||
* @param formulas The formulas that need to be preserved. |
|||
*/ |
|||
Options(ModelType const& model, std::vector<std::shared_ptr<storm::logic::Formula>> const& formulas); |
|||
|
|||
/*! |
|||
* Changes the options in a way that the given formula is preserved. |
|||
* |
|||
* @param model The model for which to preserve the formula. |
|||
* @param formula The only formula to check. |
|||
*/ |
|||
void preserveFormula(ModelType const& model, storm::logic::Formula const& formula); |
|||
|
|||
// A flag that indicates whether a measure driven initial partition is to be used. If this flag is set |
|||
// to true, the two optional pairs phiStatesAndLabel and psiStatesAndLabel must be set. Then, the |
|||
// measure driven initial partition wrt. to the states phi and psi is taken. |
|||
bool measureDrivenInitialPartition; |
|||
boost::optional<storm::storage::BitVector> phiStates; |
|||
boost::optional<storm::storage::BitVector> psiStates; |
|||
|
|||
// An optional set of strings that indicate which of the atomic propositions of the model are to be |
|||
// respected and which may be ignored. If not given, all atomic propositions of the model are respected. |
|||
boost::optional<std::set<std::string>> respectedAtomicPropositions; |
|||
|
|||
// A flag that indicates whether or not the state-rewards of the model are to be respected (and should |
|||
// be kept in the quotient model, if one is built). |
|||
bool keepRewards; |
|||
|
|||
// A flag that indicates whether a strong or a weak bisimulation is to be computed. |
|||
BisimulationType type; |
|||
|
|||
// A flag that indicates whether step-bounded properties are to be preserved. This may only be set to tru |
|||
// when computing strong bisimulation equivalence. |
|||
bool bounded; |
|||
|
|||
// A flag that governs whether the quotient model is actually built or only the decomposition is computed. |
|||
bool buildQuotient; |
|||
|
|||
private: |
|||
/*! |
|||
* Sets the options under the assumption that the given formula is the only one that is to be checked. |
|||
* |
|||
* @param model The model for which to preserve the formula. |
|||
* @param formula The only formula to check. |
|||
*/ |
|||
void preserveSingleFormula(ModelType const& model, storm::logic::Formula const& formula); |
|||
|
|||
/*! |
|||
* Adds the given expressions and labels to the set of respected atomic propositions. |
|||
* |
|||
* @param expressions The expressions to respect. |
|||
* @param labels The labels to respect. |
|||
*/ |
|||
void addToRespectedAtomicPropositions(std::vector<std::shared_ptr<storm::logic::AtomicExpressionFormula const>> const& expressions, std::vector<std::shared_ptr<storm::logic::AtomicLabelFormula const>> const& labels); |
|||
|
|||
/* |
|||
* Checks whether a measure driven partition is possible with the given formula and sets the necessary |
|||
* data if this is the case. |
|||
* |
|||
* @param model The model for which to derive the data. |
|||
* @param formula The formula for which to derive the data for the measure driven initial partition (if |
|||
* applicable). |
|||
*/ |
|||
void checkAndSetMeasureDrivenInitialPartition(ModelType const& model, storm::logic::Formula const& formula); |
|||
}; |
|||
|
|||
/*! |
|||
* Decomposes the given model into equivalance classes of a bisimulation. |
|||
* |
|||
* @param model The model to decompose. |
|||
* @param type The type of the bisimulation to compute. |
|||
* @param buildQuotient A flag specifying whether the quotient is to be build. |
|||
*/ |
|||
BisimulationDecomposition(ModelType const& model, Options const& options); |
|||
|
|||
/*! |
|||
* Retrieves the quotient of the model under the computed bisimulation. |
|||
* |
|||
* @return The quotient model. |
|||
*/ |
|||
std::shared_ptr<ModelType> getQuotient() const; |
|||
|
|||
protected: |
|||
/*! |
|||
* Computes the decomposition of the model into bisimulation equivalence classes. If requested, a quotient |
|||
* model is built. |
|||
*/ |
|||
void computeBisimulationDecomposition(); |
|||
|
|||
/*! |
|||
* Performs the partition refinement on the model and thereby computes the equivalence classes under strong |
|||
* bisimulation equivalence. If required, the quotient model is built and may be retrieved using |
|||
* getQuotient(). |
|||
*/ |
|||
void performPartitionRefinement(); |
|||
|
|||
/*! |
|||
* Refines the partition by considering the given splitter. All blocks that become potential splitters |
|||
* because of this refinement, are marked as splitters and inserted into the splitter queue. |
|||
* |
|||
* @param splitter The splitter to use. |
|||
* @param splitterQueue The queue into which to insert the newly discovered potential splitters. |
|||
*/ |
|||
virtual void refinePartitionBasedOnSplitter(bisimulation::Block const& splitter, std::deque<bisimulation::Block*>& splitterQueue); |
|||
|
|||
/*! |
|||
* Builds the quotient model based on the previously computed equivalence classes (stored in the blocks |
|||
* of the decomposition. |
|||
*/ |
|||
virtual void buildQuotient() = 0; |
|||
|
|||
/*! |
|||
* Initializes the initial partition based on all respected labels. |
|||
*/ |
|||
virtual void initializeLabelBasedPartition(); |
|||
|
|||
/*! |
|||
* Creates the measure-driven initial partition for reaching psi states from phi states. |
|||
*/ |
|||
virtual void initializeMeasureDrivenPartition(); |
|||
|
|||
/*! |
|||
* Computes the set of states with probability 0/1 for satisfying phi until psi. This is used for the measure |
|||
* driven initial partition. |
|||
* |
|||
* @param backwardTransitions The backward transitions of the model. |
|||
* @param phiStates The phi states. |
|||
* @param psiStates The psi states. |
|||
* @return The states with probability 0 and 1. |
|||
*/ |
|||
virtual std::pair<storm::storage::BitVector, storm::storage::BitVector> getStatesWithProbability01(storm::storage::SparseMatrix<ValueType> const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) = 0; |
|||
|
|||
/*! |
|||
* Splits the initial partition based on the (unique) state reward vector of the model. |
|||
*/ |
|||
virtual void splitInitialPartitionBasedOnStateRewards(); |
|||
|
|||
/*! |
|||
* Constructs the blocks of the decomposition object based on the current partition. |
|||
*/ |
|||
void extractDecompositionBlocks(); |
|||
|
|||
// The model to decompose. |
|||
ModelType const& model; |
|||
|
|||
// The backward transitions of the model. |
|||
storm::storage::SparseMatrix<ValueType> backwardTransitions; |
|||
|
|||
// The options used during construction. |
|||
Options options; |
|||
|
|||
// The current partition (used by partition refinement). |
|||
storm::storage::bisimulation::Partition partition; |
|||
|
|||
// A comparator used for comparing the distances of constants. |
|||
storm::utility::ConstantsComparator<ValueType> comparator; |
|||
|
|||
// The quotient, if it was build. Otherwhise a null pointer. |
|||
std::shared_ptr<ModelType> quotient; |
|||
}; |
|||
} |
|||
} |
|||
|
|||
|
|||
#endif /* STORM_STORAGE_BISIMULATIONDECOMPOSITION_H_ */ |
@ -0,0 +1,132 @@ |
|||
#include "src/storage/bisimulation/Block.h"
|
|||
|
|||
#include <iostream>
|
|||
#include <iomanip>
|
|||
|
|||
#include "src/storage/bisimulation/Partition.h"
|
|||
|
|||
#include "src/utility/macros.h"
|
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
namespace bisimulation { |
|||
|
|||
Block::Block(storm::storage::sparse::state_type beginIndex, storm::storage::sparse::state_type endIndex, Block* previousBlock, Block* nextBlock, std::size_t id) : nextBlock(nextBlock), previousBlock(previousBlock), beginIndex(beginIndex), endIndex(endIndex), markedAsSplitter(false), markedAsPredecessorBlock(false), absorbing(false), id(id) { |
|||
if (nextBlock != nullptr) { |
|||
nextBlock->previousBlock = this; |
|||
} |
|||
if (previousBlock != nullptr) { |
|||
previousBlock->nextBlock = this; |
|||
} |
|||
STORM_LOG_ASSERT(this->beginIndex < this->endIndex, "Unable to create block of illegal size."); |
|||
} |
|||
|
|||
void Block::print(Partition const& partition) const { |
|||
std::cout << "block " << this->id << " from " << this->beginIndex << " to " << this->endIndex; |
|||
} |
|||
|
|||
void Block::setBeginIndex(storm::storage::sparse::state_type beginIndex) { |
|||
this->beginIndex = beginIndex; |
|||
STORM_LOG_ASSERT(beginIndex < endIndex, "Unable to resize block to illegal size."); |
|||
} |
|||
|
|||
void Block::setEndIndex(storm::storage::sparse::state_type endIndex) { |
|||
this->endIndex = endIndex; |
|||
STORM_LOG_ASSERT(beginIndex < endIndex, "Unable to resize block to illegal size."); |
|||
} |
|||
|
|||
storm::storage::sparse::state_type Block::getBeginIndex() const { |
|||
return this->beginIndex; |
|||
} |
|||
|
|||
storm::storage::sparse::state_type Block::getEndIndex() const { |
|||
return this->endIndex; |
|||
} |
|||
|
|||
Block const& Block::getNextBlock() const { |
|||
return *this->nextBlock; |
|||
} |
|||
|
|||
bool Block::hasNextBlock() const { |
|||
return this->nextBlock != nullptr; |
|||
} |
|||
|
|||
Block const* Block::getNextBlockPointer() const { |
|||
return this->nextBlock; |
|||
} |
|||
|
|||
Block const& Block::getPreviousBlock() const { |
|||
return *this->previousBlock; |
|||
} |
|||
|
|||
Block const* Block::getPreviousBlockPointer() const { |
|||
return this->previousBlock; |
|||
} |
|||
|
|||
bool Block::hasPreviousBlock() const { |
|||
return this->previousBlock != nullptr; |
|||
} |
|||
|
|||
bool Block::check() const { |
|||
STORM_LOG_ASSERT(this->beginIndex < this->endIndex, "Block has negative size."); |
|||
STORM_LOG_ASSERT(!this->hasPreviousBlock() || this->getPreviousBlock().getNextBlockPointer() == this, "Illegal previous block."); |
|||
STORM_LOG_ASSERT(!this->hasNextBlock() || this->getNextBlock()().getPreviousBlockPointer() == this, "Illegal next block."); |
|||
return true; |
|||
} |
|||
|
|||
std::size_t Block::getNumberOfStates() const { |
|||
return (this->endIndex - this->beginIndex); |
|||
} |
|||
|
|||
bool Block::isMarkedAsSplitter() const { |
|||
return this->markedAsSplitter; |
|||
} |
|||
|
|||
void Block::markAsSplitter() { |
|||
this->markedAsSplitter = true; |
|||
} |
|||
|
|||
void Block::unmarkAsSplitter() { |
|||
this->markedAsSplitter = false; |
|||
} |
|||
|
|||
std::size_t Block::getId() const { |
|||
return this->id; |
|||
} |
|||
|
|||
void Block::setAbsorbing(bool absorbing) { |
|||
this->absorbing = absorbing; |
|||
} |
|||
|
|||
bool Block::isAbsorbing() const { |
|||
return this->absorbing; |
|||
} |
|||
|
|||
void Block::setRepresentativeState(storm::storage::sparse::state_type representativeState) { |
|||
this->representativeState = representativeState; |
|||
} |
|||
|
|||
bool Block::hasRepresentativeState() const { |
|||
return static_cast<bool>(representativeState); |
|||
} |
|||
|
|||
storm::storage::sparse::state_type Block::getRepresentativeState() const { |
|||
STORM_LOG_THROW(representativeState, storm::exceptions::InvalidOperationException, "Unable to retrieve representative state for block."); |
|||
return representativeState.get(); |
|||
} |
|||
|
|||
void Block::markAsPredecessorBlock() { |
|||
this->markedAsPredecessorBlock = true; |
|||
} |
|||
|
|||
void Block::unmarkAsPredecessorBlock() { |
|||
this->markedAsPredecessorBlock = false; |
|||
} |
|||
|
|||
bool Block::isMarkedAsPredecessor() const { |
|||
return markedAsPredecessorBlock; |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,132 @@ |
|||
#ifndef STORM_STORAGE_BISIMULATION_BLOCK_H_ |
|||
#define STORM_STORAGE_BISIMULATION_BLOCK_H_ |
|||
|
|||
#include <list> |
|||
#include <boost/optional.hpp> |
|||
|
|||
#include "src/storage/sparse/StateType.h" |
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
namespace bisimulation { |
|||
// Forward-declare partition class. |
|||
class Partition; |
|||
|
|||
class Block { |
|||
public: |
|||
friend class Partition; |
|||
|
|||
// Creates a new block with the given begin and end. |
|||
Block(storm::storage::sparse::state_type beginIndex, storm::storage::sparse::state_type endIndex, Block* previous, Block* next, std::size_t id); |
|||
|
|||
Block() = default; |
|||
Block(Block const& other) = default; |
|||
Block& operator=(Block const& other) = default; |
|||
Block(Block&& other) = default; |
|||
Block& operator=(Block&& other) = default; |
|||
|
|||
// Prints the block to the standard output. |
|||
void print(Partition const& partition) const; |
|||
|
|||
// Returns the beginning index of the block. |
|||
storm::storage::sparse::state_type getBeginIndex() const; |
|||
|
|||
// Returns the beginning index of the block. |
|||
storm::storage::sparse::state_type getEndIndex() const; |
|||
|
|||
// Gets the next block (if there is one). |
|||
Block const& getNextBlock() const; |
|||
|
|||
// Gets a pointer to the next block (if there is one). |
|||
Block const* getNextBlockPointer() const; |
|||
|
|||
// Retrieves whether the block as a successor block. |
|||
bool hasNextBlock() const; |
|||
|
|||
// Gets the next block (if there is one). |
|||
Block const& getPreviousBlock() const; |
|||
|
|||
// Gets a pointer to the previous block (if there is one). |
|||
Block const* getPreviousBlockPointer() const; |
|||
|
|||
// Retrieves whether the block as a successor block. |
|||
bool hasPreviousBlock() const; |
|||
|
|||
// Checks consistency of the information in the block. |
|||
bool check() const; |
|||
|
|||
// Retrieves the number of states in this block. |
|||
std::size_t getNumberOfStates() const; |
|||
|
|||
// Checks whether the block is marked as a splitter. |
|||
bool isMarkedAsSplitter() const; |
|||
|
|||
// Marks the block as being a splitter. |
|||
void markAsSplitter(); |
|||
|
|||
// Removes the mark. |
|||
void unmarkAsSplitter(); |
|||
|
|||
// Retrieves the ID of the block. |
|||
std::size_t getId() const; |
|||
|
|||
// Retrieves whether the block is marked as a predecessor. |
|||
bool isMarkedAsPredecessor() const; |
|||
|
|||
// Marks the block as being a predecessor block. |
|||
void markAsPredecessorBlock(); |
|||
|
|||
// Removes the marking. |
|||
void unmarkAsPredecessorBlock(); |
|||
|
|||
// Sets whether or not the block is to be interpreted as absorbing. |
|||
void setAbsorbing(bool absorbing); |
|||
|
|||
// Retrieves whether the block is to be interpreted as absorbing. |
|||
bool isAbsorbing() const; |
|||
|
|||
// Sets the representative state of this block |
|||
void setRepresentativeState(storm::storage::sparse::state_type representativeState); |
|||
|
|||
// Retrieves whether this block has a representative state. |
|||
bool hasRepresentativeState() const; |
|||
|
|||
// Retrieves the representative state for this block. |
|||
storm::storage::sparse::state_type getRepresentativeState() const; |
|||
|
|||
private: |
|||
// Sets the beginning index of the block. |
|||
void setBeginIndex(storm::storage::sparse::state_type beginIndex); |
|||
|
|||
// Sets the end index of the block. |
|||
void setEndIndex(storm::storage::sparse::state_type endIndex); |
|||
|
|||
// Pointers to the next and previous block. |
|||
Block* nextBlock; |
|||
Block* previousBlock; |
|||
|
|||
// The begin and end indices of the block in terms of the state vector of the partition. |
|||
storm::storage::sparse::state_type beginIndex; |
|||
storm::storage::sparse::state_type endIndex; |
|||
|
|||
// A field that can be used for marking the block. |
|||
bool markedAsSplitter; |
|||
|
|||
// A field that can be used for marking the block as a predecessor block. |
|||
bool markedAsPredecessorBlock; |
|||
|
|||
// A flag indicating whether the block is to be interpreted as absorbing or not. |
|||
bool absorbing; |
|||
|
|||
// The ID of the block. This is only used for debugging purposes. |
|||
std::size_t id; |
|||
|
|||
// An optional representative state for the block. If this is set, this state is used to derive the |
|||
// atomic propositions of the meta state in the quotient model. |
|||
boost::optional<storm::storage::sparse::state_type> representativeState; |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_STORAGE_BISIMULATION_BLOCK_H_ */ |
@ -0,0 +1,681 @@ |
|||
#include "src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h"
|
|||
|
|||
#include <algorithm>
|
|||
#include <unordered_map>
|
|||
#include <chrono>
|
|||
#include <iomanip>
|
|||
#include <boost/iterator/transform_iterator.hpp>
|
|||
|
|||
#include "src/adapters/CarlAdapter.h"
|
|||
#include "src/modelchecker/results/ExplicitQualitativeCheckResult.h"
|
|||
|
|||
#include "src/models/sparse/Dtmc.h"
|
|||
#include "src/models/sparse/Ctmc.h"
|
|||
#include "src/models/sparse/StandardRewardModel.h"
|
|||
|
|||
#include "src/utility/graph.h"
|
|||
#include "src/utility/constants.h"
|
|||
#include "src/utility/ConstantsComparator.h"
|
|||
#include "src/exceptions/IllegalFunctionCallException.h"
|
|||
#include "src/exceptions/InvalidOptionException.h"
|
|||
#include "src/exceptions/InvalidArgumentException.h"
|
|||
|
|||
#include "src/settings/SettingsManager.h"
|
|||
#include "src/settings/modules/GeneralSettings.h"
|
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
|
|||
using namespace bisimulation; |
|||
|
|||
template<typename ModelType> |
|||
DeterministicModelBisimulationDecomposition<ModelType>::DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition<ModelType>::Options const& options) : BisimulationDecomposition<ModelType>(model, options.weak ? BisimulationType::Weak : BisimulationType::Strong) { |
|||
STORM_LOG_THROW(!model.hasRewardModel() || model.hasUniqueRewardModel(), storm::exceptions::IllegalFunctionCallException, "Bisimulation currently only supports models with at most one reward model."); |
|||
STORM_LOG_THROW(!model.hasRewardModel() || model.getUniqueRewardModel()->second.hasOnlyStateRewards(), storm::exceptions::IllegalFunctionCallException, "Bisimulation is currently supported for models with state rewards only. Consider converting the transition rewards to state rewards (via suitable function calls)."); |
|||
STORM_LOG_THROW(!options.weak || !options.bounded, storm::exceptions::IllegalFunctionCallException, "Weak bisimulation cannot preserve bounded properties."); |
|||
|
|||
// Extract the set of respected atomic propositions from the options.
|
|||
if (options.respectedAtomicPropositions) { |
|||
this->selectedAtomicPropositions = options.respectedAtomicPropositions.get(); |
|||
} else { |
|||
this->selectedAtomicPropositions = model.getStateLabeling().getLabels(); |
|||
} |
|||
|
|||
// initialize the initial partition.
|
|||
if (options.measureDrivenInitialPartition) { |
|||
STORM_LOG_THROW(options.phiStates, storm::exceptions::InvalidOptionException, "Unable to compute measure-driven initial partition without phi states."); |
|||
STORM_LOG_THROW(options.psiStates, storm::exceptions::InvalidOptionException, "Unable to compute measure-driven initial partition without psi states."); |
|||
this->initializeMeasureDrivenPartition(); |
|||
} else { |
|||
this->initializeLabelBasedPartition(); |
|||
} |
|||
|
|||
this->computeBisimulationDecomposition(); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
std::pair<storm::storage::BitVector, storm::storage::BitVector> DeterministicModelBisimulationDecomposition<ModelType>::getStatesWithProbability01(storm::storage::SparseMatrix<ValueType> const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) { |
|||
return storm::utility::graph::performProb01(backwardTransitions, phiStates, psiStates); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void DeterministicModelBisimulationDecomposition<ModelType>::splitOffDivergentStates() { |
|||
std::vector<storm::storage::sparse::state_type> stateStack; |
|||
stateStack.reserve(this->model.getNumberOfStates()); |
|||
storm::storage::BitVector nondivergentStates(this->model.getNumberOfStates()); |
|||
|
|||
for (auto& block : this->partition.getBlocks()) { |
|||
nondivergentStates.clear(); |
|||
|
|||
for (auto stateIt = this->partition.begin(block), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) { |
|||
if (nondivergentStates.get(stateIt->first)) { |
|||
continue; |
|||
} |
|||
|
|||
// Now traverse the forward transitions of the current state and check whether there is a
|
|||
// transition to some other block.
|
|||
bool isDirectlyNonDivergent = false; |
|||
for (auto const& successor : this->model.getRows(stateIt->first)) { |
|||
// If there is such a transition, then we can mark all states in the current block that can
|
|||
// reach the state as non-divergent.
|
|||
if (&this->partition.getBlock(successor.getColumn()) != &block) { |
|||
isDirectlyNonDivergent = true; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (isDirectlyNonDivergent) { |
|||
stateStack.push_back(stateIt->first); |
|||
|
|||
while (!stateStack.empty()) { |
|||
storm::storage::sparse::state_type currentState = stateStack.back(); |
|||
stateStack.pop_back(); |
|||
nondivergentStates.set(currentState); |
|||
|
|||
for (auto const& predecessor : this->backwardTransitions.getRow(currentState)) { |
|||
if (&this->partition.getBlock(predecessor.getColumn()) == &block && !nondivergentStates.get(predecessor.getColumn())) { |
|||
stateStack.push_back(predecessor.getColumn()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
if (nondivergentStates.getNumberOfSetBits() > 0 && nondivergentStates.getNumberOfSetBits() != block.getNumberOfStates()) { |
|||
// After performing the split, the current block will contain the divergent states only.
|
|||
this->partition.splitStates(block, nondivergentStates); |
|||
|
|||
// Since the remaining states in the block are divergent, we can mark the block as absorbing.
|
|||
// This also guarantees that the self-loop will be added to the state of the quotient
|
|||
// representing this block of states.
|
|||
block.setAbsorbing(true); |
|||
} else if (nondivergentStates.getNumberOfSetBits() == 0) { |
|||
// If there are only diverging states in the block, we need to make it absorbing.
|
|||
block.setAbsorbing(true); |
|||
} |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void DeterministicModelBisimulationDecomposition<ModelType>::initializeSilentProbabilities() { |
|||
silentProbabilities.resize(this->model.getNumberOfStates(), storm::utility::zero<ValueType>()); |
|||
for (storm::storage::sparse::state_type state = 0; state < this->model.getNumberOfStates(); ++state) { |
|||
Block const* currentBlockPtr = &this->partition.getBlock(state); |
|||
for (auto const& successorEntry : this->model.getRows(state)) { |
|||
if (&this->partition.getBlock(successorEntry.getColumn()) == currentBlockPtr) { |
|||
silentProbabilities[state] += successorEntry.getValue(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void DeterministicModelBisimulationDecomposition<ModelType>::initializeWeakDtmcBisimulation() { |
|||
// If we are creating the initial partition for weak bisimulation on DTMCs, we need to (a) split off all
|
|||
// divergent states of each initial block and (b) initialize the vector of silent probabilities.
|
|||
this->splitOffDivergentStates(); |
|||
this->initializeSilentProbabilities(); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void DeterministicModelBisimulationDecomposition<ModelType>::initializeMeasureDrivenPartition() { |
|||
BisimulationDecomposition<ModelType>::initializeMeasureDrivenPartition(); |
|||
|
|||
if (this->options.type == BisimulationType::Weak && this->model.getModelType() == ModelType::Dtmc) { |
|||
this->initializeWeakDtmcBisimulation(); |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
void DeterministicModelBisimulationDecomposition<ModelType>::initializeLabelBasedPartition() { |
|||
BisimulationDecomposition<ModelType>::initializeLabelBasedPartition(); |
|||
|
|||
if (this->options.type == BisimulationType::Weak && this->model.getModelType() == ModelType::Dtmc) { |
|||
this->initializeWeakDtmcBisimulation(); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
template<typename ModelType> |
|||
void DeterministicModelBisimulationDecomposition<ModelType>::buildQuotient() { |
|||
// In order to create the quotient model, we need to construct
|
|||
// (a) the new transition matrix,
|
|||
// (b) the new labeling,
|
|||
// (c) the new reward structures.
|
|||
|
|||
// Prepare a matrix builder for (a).
|
|||
storm::storage::SparseMatrixBuilder<ValueType> builder(this->size(), this->size()); |
|||
|
|||
// Prepare the new state labeling for (b).
|
|||
storm::models::sparse::StateLabeling newLabeling(this->size()); |
|||
std::set<std::string> atomicPropositionsSet = this->options.selectedAtomicPropositions.get(); |
|||
atomicPropositionsSet.insert("init"); |
|||
std::vector<std::string> atomicPropositions = std::vector<std::string>(atomicPropositionsSet.begin(), atomicPropositionsSet.end()); |
|||
for (auto const& ap : atomicPropositions) { |
|||
newLabeling.addLabel(ap); |
|||
} |
|||
|
|||
// If the model had state rewards, we need to build the state rewards for the quotient as well.
|
|||
boost::optional<std::vector<ValueType>> stateRewards; |
|||
if (this->options.keepRewards && this->model.hasRewardModel()) { |
|||
stateRewards = std::vector<ValueType>(this->blocks.size()); |
|||
} |
|||
|
|||
// Now build (a) and (b) by traversing all blocks.
|
|||
for (uint_fast64_t blockIndex = 0; blockIndex < this->blocks.size(); ++blockIndex) { |
|||
auto const& block = this->blocks[blockIndex]; |
|||
|
|||
// Pick one representative state. For strong bisimulation it doesn't matter which state it is, because
|
|||
// they all behave equally.
|
|||
storm::storage::sparse::state_type representativeState = *block.begin(); |
|||
|
|||
// However, for weak bisimulation, we need to make sure the representative state is a non-silent one (if
|
|||
// there is any such state).
|
|||
if (this->options.type == BisimulationType::Weak) { |
|||
for (auto const& state : block) { |
|||
if (!partition.isSilent(state, comparator)) { |
|||
representativeState = state; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
Block const& oldBlock = partition.getBlock(representativeState); |
|||
|
|||
// If the block is absorbing, we simply add a self-loop.
|
|||
if (oldBlock.isAbsorbing()) { |
|||
builder.addNextValue(blockIndex, blockIndex, storm::utility::one<ValueType>()); |
|||
|
|||
// If the block has a special representative state, we retrieve it now.
|
|||
if (oldBlock.hasRepresentativeState()) { |
|||
representativeState = oldBlock.getRepresentativeState(); |
|||
} |
|||
|
|||
// Add all of the selected atomic propositions that hold in the representative state to the state
|
|||
// representing the block.
|
|||
for (auto const& ap : atomicPropositions) { |
|||
if (model.getStateLabeling().getStateHasLabel(ap, representativeState)) { |
|||
newLabeling.addLabelToState(ap, blockIndex); |
|||
} |
|||
} |
|||
} else { |
|||
// Compute the outgoing transitions of the block.
|
|||
std::map<storm::storage::sparse::state_type, ValueType> blockProbability; |
|||
for (auto const& entry : model.getTransitionMatrix().getRow(representativeState)) { |
|||
storm::storage::sparse::state_type targetBlock = partition.getBlock(entry.getColumn()).getId(); |
|||
|
|||
// If we are computing a weak bisimulation quotient, there is no need to add self-loops.
|
|||
if ((bisimulationType == BisimulationType::WeakDtmc || bisimulationType == BisimulationType::WeakCtmc) && targetBlock == blockIndex) { |
|||
continue; |
|||
} |
|||
|
|||
auto probIterator = blockProbability.find(targetBlock); |
|||
if (probIterator != blockProbability.end()) { |
|||
probIterator->second += entry.getValue(); |
|||
} else { |
|||
blockProbability[targetBlock] = entry.getValue(); |
|||
} |
|||
} |
|||
|
|||
// Now add them to the actual matrix.
|
|||
for (auto const& probabilityEntry : blockProbability) { |
|||
if (bisimulationType == BisimulationType::WeakDtmc) { |
|||
builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second / (storm::utility::one<ValueType>() - partition.getSilentProbability(representativeState))); |
|||
} else { |
|||
builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second); |
|||
} |
|||
} |
|||
|
|||
// Otherwise add all atomic propositions to the equivalence class that the representative state
|
|||
// satisfies.
|
|||
for (auto const& ap : atomicPropositions) { |
|||
if (model.getStateLabeling().getStateHasLabel(ap, representativeState)) { |
|||
newLabeling.addLabelToState(ap, blockIndex); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// If the model has state rewards, we simply copy the state reward of the representative state, because
|
|||
// all states in a block are guaranteed to have the same state reward.
|
|||
if (keepRewards && model.hasRewardModel()) { |
|||
typename std::unordered_map<std::string, typename ModelType::RewardModelType>::const_iterator nameRewardModelPair = model.getUniqueRewardModel(); |
|||
stateRewards.get()[blockIndex] = nameRewardModelPair->second.getStateRewardVector()[representativeState]; |
|||
} |
|||
} |
|||
|
|||
// Now check which of the blocks of the partition contain at least one initial state.
|
|||
for (auto initialState : model.getInitialStates()) { |
|||
Block const& initialBlock = partition.getBlock(initialState); |
|||
newLabeling.addLabelToState("init", initialBlock.getId()); |
|||
} |
|||
|
|||
// Construct the reward model mapping.
|
|||
std::unordered_map<std::string, typename ModelType::RewardModelType> rewardModels; |
|||
if (keepRewards && model.hasRewardModel()) { |
|||
typename std::unordered_map<std::string, typename ModelType::RewardModelType>::const_iterator nameRewardModelPair = model.getUniqueRewardModel(); |
|||
rewardModels.insert(std::make_pair(nameRewardModelPair->first, typename ModelType::RewardModelType(stateRewards))); |
|||
} |
|||
|
|||
// Finally construct the quotient model.
|
|||
this->quotient = std::shared_ptr<storm::models::sparse::DeterministicModel<ValueType, typename ModelType::RewardModelType>>(new ModelType(builder.build(), std::move(newLabeling), std::move(rewardModels))); |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
void DeterministicModelBisimulationDecomposition<ValueType>::refineBlockProbabilities(Block& block, Partition& partition, BisimulationType bisimulationType, std::deque<Block*>& splitterQueue, storm::utility::ConstantsComparator<ValueType> const& comparator) { |
|||
// Sort the states in the block based on their probabilities.
|
|||
std::sort(partition.getBegin(block), partition.getEnd(block), [&partition] (std::pair<storm::storage::sparse::state_type, ValueType> const& a, std::pair<storm::storage::sparse::state_type, ValueType> const& b) { return a.second < b.second; } ); |
|||
|
|||
// Update the positions vector.
|
|||
storm::storage::sparse::state_type position = block.getBegin(); |
|||
for (auto stateIt = partition.getBegin(block), stateIte = partition.getEnd(block); stateIt != stateIte; ++stateIt, ++position) { |
|||
partition.setPosition(stateIt->first, position); |
|||
} |
|||
|
|||
// Finally, we need to scan the ranges of states that agree on the probability.
|
|||
typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::const_iterator begin = partition.getBegin(block); |
|||
typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::const_iterator end = partition.getEnd(block) - 1; |
|||
storm::storage::sparse::state_type currentIndex = block.getBegin(); |
|||
|
|||
// Now we can check whether the block needs to be split, which is the case iff the probabilities for the
|
|||
// first and the last state are different.
|
|||
bool blockSplit = !comparator.isEqual(begin->second, end->second); |
|||
while (!comparator.isEqual(begin->second, end->second)) { |
|||
// Now we scan for the first state in the block that disagrees on the probability value.
|
|||
// Note that we do not have to check currentIndex for staying within bounds, because we know the matching
|
|||
// state is within bounds.
|
|||
ValueType const& currentValue = begin->second; |
|||
|
|||
++begin; |
|||
++currentIndex; |
|||
while (begin != end && comparator.isEqual(begin->second, currentValue)) { |
|||
++begin; |
|||
++currentIndex; |
|||
} |
|||
|
|||
// Now we split the block and mark it as a potential splitter.
|
|||
Block& newBlock = partition.splitBlock(block, currentIndex); |
|||
if (!newBlock.isMarkedAsSplitter()) { |
|||
splitterQueue.push_back(&newBlock); |
|||
newBlock.markAsSplitter(); |
|||
} |
|||
} |
|||
|
|||
// If the block was split, we also need to insert itself into the splitter queue.
|
|||
if (blockSplit) { |
|||
if (!block.isMarkedAsSplitter()) { |
|||
splitterQueue.push_back(&block); |
|||
block.markAsSplitter(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
void DeterministicModelBisimulationDecomposition<ValueType>::refineBlockWeak(Block& block, Partition& partition, storm::storage::SparseMatrix<ValueType> const& forwardTransitions, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, std::deque<Block*>& splitterQueue, storm::utility::ConstantsComparator<ValueType> const& comparator) { |
|||
std::vector<uint_fast64_t> splitPoints = getSplitPointsWeak(block, partition, comparator); |
|||
|
|||
// Restore the original begin of the block.
|
|||
block.setBegin(block.getOriginalBegin()); |
|||
|
|||
// Now that we have the split points of the non-silent states, we perform a backward search from
|
|||
// each non-silent state and label the predecessors with the class of the non-silent state.
|
|||
std::vector<storm::storage::BitVector> stateLabels(block.getEnd() - block.getBegin(), storm::storage::BitVector(splitPoints.size() - 1)); |
|||
|
|||
std::vector<storm::storage::sparse::state_type> stateStack; |
|||
stateStack.reserve(block.getEnd() - block.getBegin()); |
|||
for (uint_fast64_t stateClassIndex = 0; stateClassIndex < splitPoints.size() - 1; ++stateClassIndex) { |
|||
for (auto stateIt = partition.getStatesAndValues().begin() + splitPoints[stateClassIndex], stateIte = partition.getStatesAndValues().begin() + splitPoints[stateClassIndex + 1]; stateIt != stateIte; ++stateIt) { |
|||
|
|||
stateStack.push_back(stateIt->first); |
|||
stateLabels[partition.getPosition(stateIt->first) - block.getBegin()].set(stateClassIndex); |
|||
while (!stateStack.empty()) { |
|||
storm::storage::sparse::state_type currentState = stateStack.back(); |
|||
stateStack.pop_back(); |
|||
|
|||
for (auto const& predecessorEntry : backwardTransitions.getRow(currentState)) { |
|||
if (comparator.isZero(predecessorEntry.getValue())) { |
|||
continue; |
|||
} |
|||
|
|||
storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn(); |
|||
|
|||
// Only if the state is in the same block, is a silent state and it has not yet been
|
|||
// labeled with the current label.
|
|||
if (&partition.getBlock(predecessor) == &block && partition.isSilent(predecessor, comparator) && !stateLabels[partition.getPosition(predecessor) - block.getBegin()].get(stateClassIndex)) { |
|||
stateStack.push_back(predecessor); |
|||
stateLabels[partition.getPosition(predecessor) - block.getBegin()].set(stateClassIndex); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Now that all states were appropriately labeled, we can sort the states according to their labels and then
|
|||
// scan for ranges that agree on the label.
|
|||
std::sort(partition.getBegin(block), partition.getEnd(block), [&] (std::pair<storm::storage::sparse::state_type, ValueType> const& a, std::pair<storm::storage::sparse::state_type, ValueType> const& b) { return stateLabels[partition.getPosition(a.first) - block.getBegin()] < stateLabels[partition.getPosition(b.first) - block.getBegin()]; }); |
|||
|
|||
// Note that we do not yet repair the positions vector, but for the sake of efficiency temporariliy keep the
|
|||
// data structure in an inconsistent state.
|
|||
|
|||
// Now we have everything in place to actually split the block by just scanning for ranges of equal label.
|
|||
typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::const_iterator begin = partition.getBegin(block); |
|||
typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::const_iterator end = partition.getEnd(block) - 1; |
|||
storm::storage::sparse::state_type currentIndex = block.getBegin(); |
|||
|
|||
// Now we can check whether the block needs to be split, which is the case iff the labels for the first and
|
|||
// the last state are different. Store the offset of the block seperately, because it will potentially
|
|||
// modified by splits.
|
|||
storm::storage::sparse::state_type blockOffset = block.getBegin(); |
|||
bool blockSplit = stateLabels[partition.getPosition(begin->first) - blockOffset] != stateLabels[partition.getPosition(end->first) - blockOffset]; |
|||
while (stateLabels[partition.getPosition(begin->first) - blockOffset] != stateLabels[partition.getPosition(end->first) - blockOffset]) { |
|||
// Now we scan for the first state in the block that disagrees on the labeling value.
|
|||
// Note that we do not have to check currentIndex for staying within bounds, because we know the matching
|
|||
// state is within bounds.
|
|||
storm::storage::BitVector const& currentValue = stateLabels[partition.getPosition(begin->first) - blockOffset]; |
|||
|
|||
++begin; |
|||
++currentIndex; |
|||
while (begin != end && stateLabels[partition.getPosition(begin->first) - blockOffset] == currentValue) { |
|||
++begin; |
|||
++currentIndex; |
|||
} |
|||
|
|||
// Now we split the block and mark it as a potential splitter.
|
|||
Block& newBlock = partition.splitBlock(block, currentIndex); |
|||
|
|||
// Update the silent probabilities for all the states in the new block.
|
|||
for (auto stateIt = partition.getBegin(newBlock), stateIte = partition.getEnd(newBlock); stateIt != stateIte; ++stateIt) { |
|||
if (partition.hasSilentProbability(stateIt->first, comparator)) { |
|||
ValueType newSilentProbability = storm::utility::zero<ValueType>(); |
|||
for (auto const& successorEntry : forwardTransitions.getRow(stateIt->first)) { |
|||
if (&partition.getBlock(successorEntry.getColumn()) == &newBlock) { |
|||
newSilentProbability += successorEntry.getValue(); |
|||
} |
|||
} |
|||
partition.setSilentProbability(stateIt->first, newSilentProbability); |
|||
} |
|||
} |
|||
|
|||
if (!newBlock.isMarkedAsSplitter()) { |
|||
splitterQueue.push_back(&newBlock); |
|||
newBlock.markAsSplitter(); |
|||
} |
|||
} |
|||
|
|||
// If the block was split, we also need to insert itself into the splitter queue.
|
|||
if (blockSplit) { |
|||
if (!block.isMarkedAsSplitter()) { |
|||
splitterQueue.push_back(&block); |
|||
block.markAsSplitter(); |
|||
} |
|||
|
|||
// Update the silent probabilities for all the states in the old block.
|
|||
for (auto stateIt = partition.getBegin(block), stateIte = partition.getEnd(block); stateIt != stateIte; ++stateIt) { |
|||
if (partition.hasSilentProbability(stateIt->first, comparator)) { |
|||
ValueType newSilentProbability = storm::utility::zero<ValueType>(); |
|||
for (auto const& successorEntry : forwardTransitions.getRow(stateIt->first)) { |
|||
if (&partition.getBlock(successorEntry.getColumn()) == &block) { |
|||
newSilentProbability += successorEntry.getValue(); |
|||
} |
|||
} |
|||
partition.setSilentProbability(stateIt->first, newSilentProbability); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Finally update the positions vector.
|
|||
storm::storage::sparse::state_type position = blockOffset; |
|||
for (auto stateIt = partition.getStatesAndValues().begin() + blockOffset, stateIte = partition.getEnd(block); stateIt != stateIte; ++stateIt, ++position) { |
|||
partition.setPosition(stateIt->first, position); |
|||
} |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
std::vector<uint_fast64_t> DeterministicModelBisimulationDecomposition<ValueType>::getSplitPointsWeak(Block& block, Partition& partition, storm::utility::ConstantsComparator<ValueType> const& comparator) { |
|||
std::vector<uint_fast64_t> result; |
|||
// We first scale all probabilities with (1-p[s]) where p[s] is the silent probability of state s.
|
|||
std::for_each(partition.getStatesAndValues().begin() + block.getOriginalBegin(), partition.getStatesAndValues().begin() + block.getBegin(), [&] (std::pair<storm::storage::sparse::state_type, ValueType>& stateValuePair) { |
|||
ValueType const& silentProbability = partition.getSilentProbability(stateValuePair.first); |
|||
if (!comparator.isOne(silentProbability) && !comparator.isZero(silentProbability)) { |
|||
stateValuePair.second /= storm::utility::one<ValueType>() - silentProbability; |
|||
} |
|||
}); |
|||
|
|||
// Now sort the states based on their probabilities.
|
|||
std::sort(partition.getStatesAndValues().begin() + block.getOriginalBegin(), partition.getStatesAndValues().begin() + block.getBegin(), [&partition] (std::pair<storm::storage::sparse::state_type, ValueType> const& a, std::pair<storm::storage::sparse::state_type, ValueType> const& b) { return a.second < b.second; } ); |
|||
|
|||
// Update the positions vector.
|
|||
storm::storage::sparse::state_type position = block.getOriginalBegin(); |
|||
for (auto stateIt = partition.getStatesAndValues().begin() + block.getOriginalBegin(), stateIte = partition.getStatesAndValues().begin() + block.getBegin(); stateIt != stateIte; ++stateIt, ++position) { |
|||
partition.setPosition(stateIt->first, position); |
|||
} |
|||
|
|||
// Then, we scan for the ranges of states that agree on the probability.
|
|||
typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::const_iterator begin = partition.getStatesAndValues().begin() + block.getOriginalBegin(); |
|||
typename std::vector<std::pair<storm::storage::sparse::state_type, ValueType>>::const_iterator end = partition.getStatesAndValues().begin() + block.getBegin() - 1; |
|||
storm::storage::sparse::state_type currentIndex = block.getOriginalBegin(); |
|||
result.push_back(currentIndex); |
|||
|
|||
// Now we can check whether the block needs to be split, which is the case iff the probabilities for the
|
|||
// first and the last state are different.
|
|||
while (!comparator.isEqual(begin->second, end->second)) { |
|||
// Now we scan for the first state in the block that disagrees on the probability value.
|
|||
// Note that we do not have to check currentIndex for staying within bounds, because we know the matching
|
|||
// state is within bounds.
|
|||
ValueType const& currentValue = begin->second; |
|||
|
|||
++begin; |
|||
++currentIndex; |
|||
while (begin != end && comparator.isEqual(begin->second, currentValue)) { |
|||
++begin; |
|||
++currentIndex; |
|||
} |
|||
|
|||
// Remember the index at which the probabilities were different.
|
|||
result.push_back(currentIndex); |
|||
} |
|||
|
|||
// Push a sentinel element and return result.
|
|||
result.push_back(block.getBegin()); |
|||
return result; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
void DeterministicModelBisimulationDecomposition<ValueType>::refinePartition(storm::storage::SparseMatrix<ValueType> const& forwardTransitions, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, Block& splitter, Partition& partition, BisimulationType bisimulationType, std::deque<Block*>& splitterQueue, storm::utility::ConstantsComparator<ValueType> const& comparator) { |
|||
std::list<Block*> predecessorBlocks; |
|||
|
|||
// Iterate over all states of the splitter and check its predecessors.
|
|||
bool splitterIsPredecessor = false; |
|||
storm::storage::sparse::state_type currentPosition = splitter.getBegin(); |
|||
for (auto stateIterator = partition.getBegin(splitter), stateIte = partition.getEnd(splitter); stateIterator != stateIte; ++stateIterator, ++currentPosition) { |
|||
storm::storage::sparse::state_type currentState = stateIterator->first; |
|||
|
|||
uint_fast64_t elementsToSkip = 0; |
|||
for (auto const& predecessorEntry : backwardTransitions.getRow(currentState)) { |
|||
storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn(); |
|||
|
|||
// Get predecessor block and remember if the splitter was a predecessor of itself.
|
|||
Block& predecessorBlock = partition.getBlock(predecessor); |
|||
if (&predecessorBlock == &splitter) { |
|||
splitterIsPredecessor = true; |
|||
} |
|||
|
|||
// If the predecessor block has just one state or is marked as being absorbing, we must not split it.
|
|||
if (predecessorBlock.getNumberOfStates() <= 1 || predecessorBlock.isAbsorbing()) { |
|||
continue; |
|||
} |
|||
|
|||
storm::storage::sparse::state_type predecessorPosition = partition.getPosition(predecessor); |
|||
|
|||
// If we have not seen this predecessor before, we move it to a part near the beginning of the block.
|
|||
if (predecessorPosition >= predecessorBlock.getBegin()) { |
|||
if (&predecessorBlock == &splitter) { |
|||
// If the predecessor we just found was already processed (in terms of visiting its predecessors),
|
|||
// we swap it with the state that is currently at the beginning of the block and move the start
|
|||
// of the block one step further.
|
|||
if (predecessorPosition <= currentPosition + elementsToSkip) { |
|||
partition.swapStates(predecessor, partition.getState(predecessorBlock.getBegin())); |
|||
predecessorBlock.incrementBegin(); |
|||
} else { |
|||
// Otherwise, we need to move the predecessor, but we need to make sure that we explore its
|
|||
// predecessors later.
|
|||
if (predecessorBlock.getMarkedPosition() == predecessorBlock.getBegin()) { |
|||
partition.swapStatesAtPositions(predecessorBlock.getMarkedPosition(), predecessorPosition); |
|||
partition.swapStatesAtPositions(predecessorPosition, currentPosition + elementsToSkip + 1); |
|||
} else { |
|||
partition.swapStatesAtPositions(predecessorBlock.getMarkedPosition(), predecessorPosition); |
|||
partition.swapStatesAtPositions(predecessorPosition, predecessorBlock.getBegin()); |
|||
partition.swapStatesAtPositions(predecessorPosition, currentPosition + elementsToSkip + 1); |
|||
} |
|||
|
|||
++elementsToSkip; |
|||
predecessorBlock.incrementMarkedPosition(); |
|||
predecessorBlock.incrementBegin(); |
|||
} |
|||
} else { |
|||
partition.swapStates(predecessor, partition.getState(predecessorBlock.getBegin())); |
|||
predecessorBlock.incrementBegin(); |
|||
} |
|||
partition.setValue(predecessor, predecessorEntry.getValue()); |
|||
} else { |
|||
// Otherwise, we just need to update the probability for this predecessor.
|
|||
partition.increaseValue(predecessor, predecessorEntry.getValue()); |
|||
} |
|||
|
|||
if (!predecessorBlock.isMarkedAsPredecessor()) { |
|||
predecessorBlocks.emplace_back(&predecessorBlock); |
|||
predecessorBlock.markAsPredecessorBlock(); |
|||
} |
|||
} |
|||
|
|||
// If we had to move some elements beyond the current element, we may have to skip them.
|
|||
if (elementsToSkip > 0) { |
|||
stateIterator += elementsToSkip; |
|||
currentPosition += elementsToSkip; |
|||
} |
|||
} |
|||
|
|||
// Now we can traverse the list of states of the splitter whose predecessors we have not yet explored.
|
|||
for (auto stateIterator = partition.getStatesAndValues().begin() + splitter.getOriginalBegin(), stateIte = partition.getStatesAndValues().begin() + splitter.getMarkedPosition(); stateIterator != stateIte; ++stateIterator) { |
|||
storm::storage::sparse::state_type currentState = stateIterator->first; |
|||
|
|||
for (auto const& predecessorEntry : backwardTransitions.getRow(currentState)) { |
|||
storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn(); |
|||
Block& predecessorBlock = partition.getBlock(predecessor); |
|||
storm::storage::sparse::state_type predecessorPosition = partition.getPosition(predecessor); |
|||
|
|||
if (predecessorPosition >= predecessorBlock.getBegin()) { |
|||
partition.swapStatesAtPositions(predecessorPosition, predecessorBlock.getBegin()); |
|||
predecessorBlock.incrementBegin(); |
|||
partition.setValue(predecessor, predecessorEntry.getValue()); |
|||
} else { |
|||
partition.increaseValue(predecessor, predecessorEntry.getValue()); |
|||
} |
|||
|
|||
if (!predecessorBlock.isMarkedAsPredecessor()) { |
|||
predecessorBlocks.emplace_back(&predecessorBlock); |
|||
predecessorBlock.markAsPredecessorBlock(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (bisimulationType == BisimulationType::Strong || bisimulationType == BisimulationType::WeakCtmc) { |
|||
std::vector<Block*> blocksToSplit; |
|||
|
|||
// Now, we can iterate over the predecessor blocks and see whether we have to create a new block for
|
|||
// predecessors of the splitter.
|
|||
for (auto blockPtr : predecessorBlocks) { |
|||
Block& block = *blockPtr; |
|||
|
|||
block.unmarkAsPredecessorBlock(); |
|||
block.resetMarkedPosition(); |
|||
|
|||
// If we have moved the begin of the block to somewhere in the middle of the block, we need to split it.
|
|||
if (block.getBegin() != block.getEnd()) { |
|||
Block& newBlock = partition.insertBlock(block); |
|||
if (!newBlock.isMarkedAsSplitter()) { |
|||
splitterQueue.push_back(&newBlock); |
|||
newBlock.markAsSplitter(); |
|||
} |
|||
|
|||
// Schedule the block of predecessors for refinement based on probabilities.
|
|||
blocksToSplit.emplace_back(&newBlock); |
|||
} else { |
|||
// In this case, we can keep the block by setting its begin to the old value.
|
|||
block.setBegin(block.getOriginalBegin()); |
|||
blocksToSplit.emplace_back(&block); |
|||
} |
|||
} |
|||
|
|||
// Finally, we walk through the blocks that have a transition to the splitter and split them using
|
|||
// probabilistic information.
|
|||
for (auto blockPtr : blocksToSplit) { |
|||
if (blockPtr->getNumberOfStates() <= 1) { |
|||
continue; |
|||
} |
|||
|
|||
// In the case of weak bisimulation for CTMCs, we don't need to make sure the rate of staying inside
|
|||
// the own block is the same.
|
|||
if (bisimulationType == BisimulationType::WeakCtmc && blockPtr == &splitter) { |
|||
continue; |
|||
} |
|||
|
|||
refineBlockProbabilities(*blockPtr, partition, bisimulationType, splitterQueue, comparator); |
|||
} |
|||
} else { // In this case, we are computing a weak bisimulation on a DTMC.
|
|||
// If the splitter was a predecessor of itself and we are computing a weak bisimulation, we need to update
|
|||
// the silent probabilities.
|
|||
if (splitterIsPredecessor) { |
|||
partition.setSilentProbabilities(partition.getStatesAndValues().begin() + splitter.getOriginalBegin(), partition.getStatesAndValues().begin() + splitter.getBegin()); |
|||
partition.setSilentProbabilitiesToZero(partition.getStatesAndValues().begin() + splitter.getBegin(), partition.getStatesAndValues().begin() + splitter.getEnd()); |
|||
} |
|||
|
|||
// Now refine all predecessor blocks in a weak manner. That is, we split according to the criterion of
|
|||
// weak bisimulation.
|
|||
for (auto blockPtr : predecessorBlocks) { |
|||
Block& block = *blockPtr; |
|||
|
|||
// If the splitter is also the predecessor block, we must not refine it at this point.
|
|||
if (&block != &splitter) { |
|||
refineBlockWeak(block, partition, forwardTransitions, backwardTransitions, splitterQueue, comparator); |
|||
} else { |
|||
// Restore the begin of the block.
|
|||
block.setBegin(block.getOriginalBegin()); |
|||
} |
|||
|
|||
block.unmarkAsPredecessorBlock(); |
|||
block.resetMarkedPosition(); |
|||
} |
|||
} |
|||
|
|||
STORM_LOG_ASSERT(partition.check(), "Partition became inconsistent."); |
|||
} |
|||
|
|||
template class DeterministicModelBisimulationDecomposition<double>; |
|||
|
|||
#ifdef STORM_HAVE_CARL
|
|||
template class DeterministicModelBisimulationDecomposition<storm::RationalFunction>; |
|||
#endif
|
|||
} |
|||
} |
@ -0,0 +1,142 @@ |
|||
#ifndef STORM_STORAGE_BISIMULATION_DETERMINISTICMODELBISIMULATIONDECOMPOSITION_H_ |
|||
#define STORM_STORAGE_BISIMULATION_DETERMINISTICMODELBISIMULATIONDECOMPOSITION_H_ |
|||
|
|||
#include "src/storage/bisimulation/BisimulationDecomposition.h" |
|||
|
|||
namespace storm { |
|||
namespace utility { |
|||
template <typename ValueType> class ConstantsComparator; |
|||
} |
|||
|
|||
namespace storage { |
|||
|
|||
/*! |
|||
* This class represents the decomposition of a deterministic model into its bisimulation quotient. |
|||
*/ |
|||
template<typename ModelType> |
|||
class DeterministicModelBisimulationDecomposition : public BisimulationDecomposition<ModelType> { |
|||
public: |
|||
typedef typename ModelType::ValueType ValueType; |
|||
typedef typename ModelType::RewardModelType RewardModelType; |
|||
|
|||
/*! |
|||
* Computes the bisimulation relation for the given model. Which kind of bisimulation is computed, is |
|||
* customizable via the given options. |
|||
* |
|||
* @param model The model to decompose. |
|||
* @param options The options that customize the computed bisimulation. |
|||
*/ |
|||
DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition<ModelType>::Options const& options = Options()); |
|||
|
|||
private: |
|||
virtual std::pair<storm::storage::BitVector, storm::storage::BitVector> getStatesWithProbability01(storm::storage::SparseMatrix<ValueType> const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) override; |
|||
|
|||
virtual void initializeLabelBasedPartition() override; |
|||
|
|||
/*! |
|||
* Performs the necessary steps to compute a weak bisimulation on a DTMC. |
|||
*/ |
|||
void initializeWeakDtmcBisimulation(); |
|||
|
|||
/*! |
|||
* Splits all blocks of the current partition into a block that contains all divergent states and another |
|||
* block containing the non-divergent states. |
|||
*/ |
|||
void splitOffDivergentStates(); |
|||
|
|||
/*! |
|||
* Initializes the vector of silent probabilities. |
|||
*/ |
|||
void initializeSilentProbabilities(); |
|||
|
|||
virtual void initializeMeasureDrivenPartition() override; |
|||
|
|||
virtual void initializeLabelBasedPartition() override; |
|||
|
|||
virtual void buildQuotient() override; |
|||
|
|||
/*! |
|||
* Refines the partition based on the provided splitter. After calling this method all blocks are stable |
|||
* with respect to the splitter. |
|||
* |
|||
* @param forwardTransitions The forward transitions of the model. |
|||
* @param backwardTransitions A matrix that can be used to retrieve the predecessors (and their |
|||
* probabilities). |
|||
* @param splitter The splitter to use. |
|||
* @param partition The partition to split. |
|||
* @param bisimulationType The kind of bisimulation that is to be computed. |
|||
* @param splitterQueue A queue into which all blocks that were split are inserted so they can be treated |
|||
* as splitters in the future. |
|||
* @param comparator A comparator used for comparing constants. |
|||
*/ |
|||
void refinePartition(storm::storage::SparseMatrix<ValueType> const& forwardTransitions, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, Block& splitter, Partition& partition, BisimulationType bisimulationType, std::deque<Block*>& splitterQueue, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
/*! |
|||
* Refines the block based on their probability values (leading into the splitter). |
|||
* |
|||
* @param block The block to refine. |
|||
* @param partition The partition that contains the block. |
|||
* @param bisimulationType The kind of bisimulation that is to be computed. |
|||
* @param splitterQueue A queue into which all blocks that were split are inserted so they can be treated |
|||
* as splitters in the future. |
|||
* @param comparator A comparator used for comparing constants. |
|||
*/ |
|||
void refineBlockProbabilities(Block& block, Partition& partition, BisimulationType bisimulationType, std::deque<Block*>& splitterQueue, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
void refineBlockWeak(Block& block, Partition& partition, storm::storage::SparseMatrix<ValueType> const& forwardTransitions, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, std::deque<Block*>& splitterQueue, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
/*! |
|||
* Determines the split offsets in the given block. |
|||
* |
|||
* @param block The block that is to be analyzed for splits. |
|||
* @param partition The partition that contains the block. |
|||
* @param comparator A comparator used for comparing constants. |
|||
*/ |
|||
std::vector<uint_fast64_t> getSplitPointsWeak(Block& block, Partition& partition, storm::utility::ConstantsComparator<ValueType> const& comparator); |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
/*! |
|||
* Creates the measure-driven initial partition for reaching psi states from phi states. |
|||
* |
|||
* @param model The model whose state space is partitioned based on reachability of psi states from phi |
|||
* states. |
|||
* @param backwardTransitions The backward transitions of the model. |
|||
* @param phiStates The phi states in the model. |
|||
* @param psiStates The psi states in the model. |
|||
* @param bisimulationType The kind of bisimulation that is to be computed. |
|||
* @param bounded If set to true, the initial partition will be chosen in such a way that preserves bounded |
|||
* reachability queries. |
|||
* @param comparator A comparator used for comparing constants. |
|||
* @return The resulting partition. |
|||
*/ |
|||
template<typename ModelType> |
|||
Partition getMeasureDrivenInitialPartition(ModelType const& model, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, BisimulationType bisimulationType, bool keepRewards = true, bool bounded = false, storm::utility::ConstantsComparator<ValueType> const& comparator = storm::utility::ConstantsComparator<ValueType>()); |
|||
|
|||
/*! |
|||
* Creates the initial partition based on all the labels in the given model. |
|||
* |
|||
* @param model The model whose state space is partitioned based on its labels. |
|||
* @param backwardTransitions The backward transitions of the model. |
|||
* @param bisimulationType The kind of bisimulation that is to be computed. |
|||
* @param atomicPropositions The set of atomic propositions to respect. If not given, then all atomic |
|||
* propositions of the model are respected. |
|||
* @param comparator A comparator used for comparing constants. |
|||
* @return The resulting partition. |
|||
*/ |
|||
template<typename ModelType> |
|||
Partition getLabelBasedInitialPartition(ModelType const& model, storm::storage::SparseMatrix<ValueType> const& backwardTransitions, BisimulationType bisimulationType, std::set<std::string> const& atomicPropositions, bool keepRewards = true, storm::utility::ConstantsComparator<ValueType> const& comparator = storm::utility::ConstantsComparator<ValueType>()); |
|||
|
|||
// A vector mapping each state to its silent probability. |
|||
std::vector<ValueType> silentProbabilities; |
|||
}; |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_STORAGE_BISIMULATION_DETERMINISTICMODELBISIMULATIONDECOMPOSITION_H_ */ |
@ -0,0 +1,273 @@ |
|||
#include "src/storage/bisimulation/Partition.h"
|
|||
|
|||
#include <iostream>
|
|||
|
|||
#include "src/utility/macros.h"
|
|||
#include "src/exceptions/InvalidArgumentException.h"
|
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
namespace bisimulation { |
|||
Partition::Partition(std::size_t numberOfStates) : stateToBlockMapping(numberOfStates), states(numberOfStates), positions(numberOfStates) { |
|||
blocks.emplace_back(0, numberOfStates, nullptr, nullptr, blocks.size()); |
|||
|
|||
// Set up the different parts of the internal structure.
|
|||
for (storm::storage::sparse::state_type state = 0; state < numberOfStates; ++state) { |
|||
states[state] = state; |
|||
positions[state] = state; |
|||
stateToBlockMapping[state] = &blocks.back(); |
|||
} |
|||
} |
|||
|
|||
Partition::Partition(std::size_t numberOfStates, storm::storage::BitVector const& prob0States, storm::storage::BitVector const& prob1States, boost::optional<storm::storage::sparse::state_type> representativeProb1State) : stateToBlockMapping(numberOfStates), states(numberOfStates), positions(numberOfStates) { |
|||
storm::storage::sparse::state_type position = 0; |
|||
Block* firstBlock = nullptr; |
|||
Block* secondBlock = nullptr; |
|||
Block* thirdBlock = nullptr; |
|||
if (!prob0States.empty()) { |
|||
blocks.emplace_back(0, prob0States.getNumberOfSetBits(), nullptr, nullptr, blocks.size()); |
|||
firstBlock = &blocks[0]; |
|||
|
|||
for (auto state : prob0States) { |
|||
states[position] = state; |
|||
positions[state] = position; |
|||
stateToBlockMapping[state] = firstBlock; |
|||
++position; |
|||
} |
|||
firstBlock->setAbsorbing(true); |
|||
} |
|||
|
|||
if (!prob1States.empty()) { |
|||
blocks.emplace_back(position, position + prob1States.getNumberOfSetBits(), firstBlock, nullptr, blocks.size()); |
|||
secondBlock = &blocks[1]; |
|||
|
|||
for (auto state : prob1States) { |
|||
states[position] = state; |
|||
positions[state] = position; |
|||
stateToBlockMapping[state] = secondBlock; |
|||
++position; |
|||
} |
|||
secondBlock->setAbsorbing(true); |
|||
secondBlock->setRepresentativeState(representativeProb1State.get()); |
|||
} |
|||
|
|||
storm::storage::BitVector otherStates = ~(prob0States | prob1States); |
|||
if (!otherStates.empty()) { |
|||
blocks.emplace_back(position, numberOfStates, secondBlock, nullptr, blocks.size()); |
|||
thirdBlock = &blocks[2]; |
|||
|
|||
for (auto state : otherStates) { |
|||
states[position] = state; |
|||
positions[state] = position; |
|||
stateToBlockMapping[state] = thirdBlock; |
|||
++position; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void Partition::swapStates(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { |
|||
std::swap(this->states[this->positions[state1]], this->states[this->positions[state2]]); |
|||
std::swap(this->positions[state1], this->positions[state2]); |
|||
} |
|||
|
|||
void Partition::swapStatesAtPositions(storm::storage::sparse::state_type position1, storm::storage::sparse::state_type position2) { |
|||
storm::storage::sparse::state_type state1 = this->states[position1]; |
|||
storm::storage::sparse::state_type state2 = this->states[position2]; |
|||
|
|||
std::swap(this->states[position1], this->states[position2]); |
|||
this->positions[state1] = position2; |
|||
this->positions[state2] = position1; |
|||
} |
|||
|
|||
storm::storage::sparse::state_type const& Partition::getPosition(storm::storage::sparse::state_type state) const { |
|||
return this->positions[state]; |
|||
} |
|||
|
|||
void Partition::setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position) { |
|||
this->positions[state] = position; |
|||
} |
|||
|
|||
storm::storage::sparse::state_type const& Partition::getState(storm::storage::sparse::state_type position) const { |
|||
return this->states[position]; |
|||
} |
|||
|
|||
void Partition::mapStatesToBlock(Block& block, std::vector<storm::storage::sparse::state_type>::iterator first, std::vector<storm::storage::sparse::state_type>::iterator last) { |
|||
for (; first != last; ++first) { |
|||
this->stateToBlockMapping[*first] = █ |
|||
} |
|||
} |
|||
|
|||
void Partition::mapStatesToPositions(Block const& block) { |
|||
storm::storage::sparse::state_type position = block.getBeginIndex(); |
|||
for (auto stateIt = this->begin(block), stateIte = this->end(block); stateIt != stateIte; ++stateIt, ++position) { |
|||
this->positions[*stateIt] = position; |
|||
} |
|||
} |
|||
|
|||
Block& Partition::getBlock(storm::storage::sparse::state_type state) { |
|||
return *this->stateToBlockMapping[state]; |
|||
} |
|||
|
|||
Block const& Partition::getBlock(storm::storage::sparse::state_type state) const { |
|||
return *this->stateToBlockMapping[state]; |
|||
} |
|||
|
|||
std::vector<storm::storage::sparse::state_type>::const_iterator Partition::begin(Block const& block) const { |
|||
return this->states.begin() + block.getBeginIndex(); |
|||
} |
|||
|
|||
std::vector<storm::storage::sparse::state_type>::const_iterator Partition::end(Block const& block) const { |
|||
return this->states.begin() + block.getEndIndex(); |
|||
} |
|||
|
|||
Block& Partition::splitBlock(Block& block, storm::storage::sparse::state_type position) { |
|||
STORM_LOG_THROW(position >= block.getBeginIndex() && position <= block.getEndIndex(), storm::exceptions::InvalidArgumentException, "Cannot split block at illegal position."); |
|||
|
|||
// In case one of the resulting blocks would be empty, we simply return the current block and do not create
|
|||
// a new one.
|
|||
if (position == block.getBeginIndex() || position == block.getEndIndex()) { |
|||
return block; |
|||
} |
|||
|
|||
// Actually create the new block.
|
|||
blocks.emplace_back(block.getBeginIndex(), position, block.getPreviousBlockPointer(), &block, blocks.size()); |
|||
Block& newBlock = blocks.back(); |
|||
|
|||
// Resize the current block appropriately.
|
|||
block.setBeginIndex(position); |
|||
|
|||
// Mark both blocks as splitters.
|
|||
block.markAsSplitter(); |
|||
newBlock.markAsSplitter(); |
|||
|
|||
// Update the mapping of the states in the newly created block.
|
|||
this->mapStatesToBlock(newBlock, this->begin(newBlock), this->end(newBlock)); |
|||
|
|||
return newBlock; |
|||
} |
|||
|
|||
void Partition::splitBlock(Block& block, std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& lessFunction, std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& changedFunction) { |
|||
// Sort the range of the block such that all states that have the label are moved to the front.
|
|||
std::sort(this->begin(block), this->end(block), lessFunction); |
|||
|
|||
// Update the positions vector.
|
|||
mapStatesToPositions(block); |
|||
|
|||
// Now we can check whether the block needs to be split, which is the case iff the changed function returns
|
|||
// true for the first and last element of the remaining state range.
|
|||
storm::storage::sparse::state_type begin = block.getBeginIndex(); |
|||
storm::storage::sparse::state_type end = block.getEndIndex() - 1; |
|||
while (changedFunction(begin, end)) { |
|||
// Now we scan for the first state in the block for which the changed function returns true.
|
|||
// Note that we do not have to check currentIndex for staying within bounds, because we know the matching
|
|||
// state is within bounds.
|
|||
storm::storage::sparse::state_type currentIndex = begin + 1; |
|||
while (begin != end && !changedFunction(states[begin], states[currentIndex])) { |
|||
++currentIndex; |
|||
} |
|||
begin = currentIndex; |
|||
|
|||
this->splitBlock(block, currentIndex); |
|||
} |
|||
} |
|||
|
|||
void Partition::split(std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& lessFunction, std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& changedFunction) { |
|||
for (auto& block : blocks) { |
|||
splitBlock(block, lessFunction, changedFunction); |
|||
} |
|||
} |
|||
|
|||
Block& Partition::splitStates(Block& block, storm::storage::BitVector const& states) { |
|||
// Sort the range of the block such that all states that have the label are moved to the front.
|
|||
std::sort(this->begin(block), this->end(block), [&states] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return states.get(a) && !states.get(b); } ); |
|||
|
|||
// Update the positions vector.
|
|||
mapStatesToPositions(block); |
|||
|
|||
// Now we can find the first position in the block that does not have the label and create new blocks.
|
|||
std::vector<storm::storage::sparse::state_type>::const_iterator it = std::find_if(this->begin(block), this->end(block), [&states] (storm::storage::sparse::state_type const& a) { return !states.get(a); }); |
|||
|
|||
if (it != this->begin(block) && it != this->end(block)) { |
|||
auto cutPoint = std::distance(this->states.cbegin(), it); |
|||
return this->splitBlock(block, cutPoint); |
|||
} else { |
|||
return block; |
|||
} |
|||
} |
|||
|
|||
void Partition::splitStates(storm::storage::BitVector const& states) { |
|||
for (auto& block : blocks) { |
|||
splitStates(block, states); |
|||
} |
|||
} |
|||
|
|||
void Partition::sortBlock(Block const& block) { |
|||
std::sort(this->begin(block), this->end(block), [] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return a < b; }); |
|||
mapStatesToPositions(block); |
|||
} |
|||
|
|||
Block& Partition::insertBlock(Block& block) { |
|||
// Find the beginning of the new block.
|
|||
storm::storage::sparse::state_type begin = block.hasPreviousBlock() ? block.getPreviousBlock().getEndIndex() : 0; |
|||
|
|||
// Actually insert the new block.
|
|||
blocks.emplace_back(begin, block.getBeginIndex(), block.getPreviousBlockPointer(), &block, blocks.size()); |
|||
Block& newBlock = blocks.back(); |
|||
|
|||
// Update the mapping of the states in the newly created block.
|
|||
for (auto it = this->begin(newBlock), ite = this->end(newBlock); it != ite; ++it) { |
|||
stateToBlockMapping[*it] = &newBlock; |
|||
} |
|||
|
|||
return newBlock; |
|||
} |
|||
|
|||
std::vector<Block> const& Partition::getBlocks() const { |
|||
return this->blocks; |
|||
} |
|||
|
|||
std::vector<Block>& Partition::getBlocks() { |
|||
return this->blocks; |
|||
} |
|||
|
|||
bool Partition::check() const { |
|||
for (uint_fast64_t state = 0; state < this->positions.size(); ++state) { |
|||
STORM_LOG_ASSERT(this->states[this->positions[state]] == state, "Position mapping corrupted."); |
|||
} |
|||
for (auto const& block : this->blocks) { |
|||
STORM_LOG_ASSERT(block.check(), "Block corrupted."); |
|||
for (auto stateIt = this->begin(block), stateIte = this->end(block); stateIt != stateIte; ++stateIt) { |
|||
STORM_LOG_ASSERT(this->stateToBlockMapping[*stateIt] == &block, "Block mapping corrupted."); |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
void Partition::print() const { |
|||
for (auto const& block : this->blocks) { |
|||
block.print(*this); |
|||
} |
|||
std::cout << "states in partition" << std::endl; |
|||
for (auto const& state : states) { |
|||
std::cout << state << " "; |
|||
} |
|||
std::cout << std::endl << "positions: " << std::endl; |
|||
for (auto const& index : positions) { |
|||
std::cout << index << " "; |
|||
} |
|||
std::cout << std::endl << "state to block mapping: " << std::endl; |
|||
for (auto const& block : stateToBlockMapping) { |
|||
std::cout << block << "[" << block->getId() <<"] "; |
|||
} |
|||
std::cout << std::endl; |
|||
std::cout << "size: " << blocks.size() << std::endl; |
|||
STORM_LOG_ASSERT(this->check(), "Partition corrupted."); |
|||
} |
|||
|
|||
std::size_t Partition::size() const { |
|||
return blocks.size(); |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,142 @@ |
|||
#ifndef STORM_STORAGE_BISIMULATION_PARTITION_H_ |
|||
#define STORM_STORAGE_BISIMULATION_PARTITION_H_ |
|||
|
|||
#include <cstddef> |
|||
#include <list> |
|||
|
|||
#include "src/storage/bisimulation/Block.h" |
|||
|
|||
#include "src/storage/BitVector.h" |
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
namespace bisimulation { |
|||
|
|||
class Partition { |
|||
public: |
|||
/*! |
|||
* Creates a partition with one block consisting of all the states. |
|||
* |
|||
* @param numberOfStates The number of states the partition holds. |
|||
*/ |
|||
Partition(std::size_t numberOfStates); |
|||
|
|||
/*! |
|||
* Creates a partition with three blocks: one with all phi states, one with all psi states and one with |
|||
* all other states. The former two blocks are marked as being absorbing, because their outgoing |
|||
* transitions shall not be taken into account for future refinement. |
|||
* |
|||
* @param numberOfStates The number of states the partition holds. |
|||
* @param prob0States The states which have probability 0 of satisfying phi until psi. |
|||
* @param prob1States The states which have probability 1 of satisfying phi until psi. |
|||
* @param representativeProb1State If the set of prob1States is non-empty, this needs to be a state |
|||
* that is representative for this block in the sense that the state representing this block in the quotient |
|||
* model will receive exactly the atomic propositions of the representative state. |
|||
*/ |
|||
Partition(std::size_t numberOfStates, storm::storage::BitVector const& prob0States, storm::storage::BitVector const& prob1States, boost::optional<storm::storage::sparse::state_type> representativeProb1State); |
|||
|
|||
Partition() = default; |
|||
Partition(Partition const& other) = default; |
|||
Partition& operator=(Partition const& other) = default; |
|||
Partition(Partition&& other) = default; |
|||
Partition& operator=(Partition&& other) = default; |
|||
|
|||
// Retrieves the size of the partition, i.e. the number of blocks. |
|||
std::size_t size() const; |
|||
|
|||
// Prints the partition to the standard output. |
|||
void print() const; |
|||
|
|||
// Splits the block at the given position and inserts a new block after the current one holding the rest |
|||
// of the states. |
|||
Block& splitBlock(Block& block, storm::storage::sparse::state_type position); |
|||
|
|||
// Splits the block by sorting the states according to the given function and then identifying the split |
|||
// points via the changed-function. |
|||
void splitBlock(Block& block, std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& lessFunction, std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& changedFunction); |
|||
|
|||
// Splits all blocks by using the sorting-based splitting. |
|||
void split(std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& lessFunction, std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& changedFunction); |
|||
|
|||
// Splits the block such that the resulting blocks contain only states in the given set or none at all. |
|||
// If the block is split, the given block will contain the states *not* in the given set and the newly |
|||
// created block will contain the states *in* the given set. |
|||
Block& splitStates(Block& block, storm::storage::BitVector const& states); |
|||
|
|||
/*! |
|||
* Splits all blocks of the partition such that afterwards all blocks contain only states within the given |
|||
* set of states or no such state at all. |
|||
*/ |
|||
void splitStates(storm::storage::BitVector const& states); |
|||
|
|||
// Sorts the block based on the state indices. |
|||
void sortBlock(Block const& block); |
|||
|
|||
// Retrieves the blocks of the partition. |
|||
std::vector<Block> const& getBlocks() const; |
|||
|
|||
// Retrieves the blocks of the partition. |
|||
std::vector<Block>& getBlocks(); |
|||
|
|||
// Checks the partition for internal consistency. |
|||
bool check() const; |
|||
|
|||
// Returns an iterator to the beginning of the states of the given block. |
|||
std::vector<storm::storage::sparse::state_type>::const_iterator begin(Block const& block) const; |
|||
|
|||
// Returns an iterator to the beginning of the states of the given block. |
|||
std::vector<storm::storage::sparse::state_type>::const_iterator end(Block const& block) const; |
|||
|
|||
// Swaps the positions of the two given states. |
|||
void swapStates(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2); |
|||
|
|||
// Retrieves the block of the given state. |
|||
Block& getBlock(storm::storage::sparse::state_type state); |
|||
|
|||
// Retrieves the block of the given state. |
|||
Block const& getBlock(storm::storage::sparse::state_type state) const; |
|||
|
|||
// Retrieves the position of the given state. |
|||
storm::storage::sparse::state_type const& getPosition(storm::storage::sparse::state_type state) const; |
|||
|
|||
// Sets the position of the state to the given position. |
|||
storm::storage::sparse::state_type const& getState(storm::storage::sparse::state_type position) const; |
|||
|
|||
// Updates the block mapping for the given range of states to the specified block. |
|||
void mapStatesToBlock(Block& block, std::vector<storm::storage::sparse::state_type>::iterator first, std::vector<storm::storage::sparse::state_type>::iterator last); |
|||
|
|||
// Update the state to position for the states in the given block. |
|||
void mapStatesToPositions(Block const& block); |
|||
|
|||
private: |
|||
// FIXME: necessary? |
|||
// Swaps the positions of the two states given by their positions. |
|||
void swapStatesAtPositions(storm::storage::sparse::state_type position1, storm::storage::sparse::state_type position2); |
|||
|
|||
// FIXME: necessary? |
|||
// Inserts a block before the given block. The new block will cover all states between the beginning |
|||
// of the given block and the end of the previous block. |
|||
Block& insertBlock(Block& block); |
|||
|
|||
// FIXME: necessary? |
|||
// Sets the position of the given state. |
|||
void setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position); |
|||
|
|||
// The of blocks in the partition. |
|||
std::vector<Block> blocks; |
|||
|
|||
// A mapping of states to their blocks. |
|||
std::vector<Block*> stateToBlockMapping; |
|||
|
|||
// A vector containing all the states. It is ordered in a way such that the blocks only need to define |
|||
// their start/end indices. |
|||
std::vector<storm::storage::sparse::state_type> states; |
|||
|
|||
// This vector keeps track of the position of each state in the state vector. |
|||
std::vector<storm::storage::sparse::state_type> positions; |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_STORAGE_BISIMULATION_PARTITION_H_ */ |
Write
Preview
Loading…
Cancel
Save
Reference in new issue