Browse Source

on my way of making (the refactored version) bisimulation work again for deterministic models

Former-commit-id: 79c089a693
main
dehnert 9 years ago
parent
commit
11c21eb338
  1. 1
      src/models/sparse/Model.h
  2. 41
      src/storage/bisimulation/BisimulationDecomposition.cpp
  3. 9
      src/storage/bisimulation/BisimulationDecomposition.h
  4. 41
      src/storage/bisimulation/Block.cpp
  5. 22
      src/storage/bisimulation/Block.h
  6. 585
      src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp
  7. 106
      src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h
  8. 139
      src/storage/bisimulation/Partition.cpp
  9. 25
      src/storage/bisimulation/Partition.h
  10. 167
      test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp

1
src/models/sparse/Model.h

@ -154,7 +154,6 @@ namespace storm {
*/
RewardModelType const& getRewardModel(std::string const& rewardModelName) const;
/*!
* Retrieves the unique reward model, if there exists exactly one. Otherwise, an exception is thrown.
*

41
src/storage/bisimulation/BisimulationDecomposition.cpp

@ -4,6 +4,7 @@
#include "src/models/sparse/Dtmc.h"
#include "src/models/sparse/Ctmc.h"
#include "src/models/sparse/StandardRewardModel.h"
#include "src/modelchecker/propositional/SparsePropositionalModelChecker.h"
#include "src/modelchecker/results/ExplicitQualitativeCheckResult.h"
@ -13,6 +14,7 @@
#include "src/utility/macros.h"
#include "src/exceptions/IllegalFunctionCallException.h"
#include "src/exceptions/InvalidOptionException.h"
namespace storm {
namespace storage {
@ -137,6 +139,17 @@ namespace storm {
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 initialPartitionStart = std::chrono::high_resolution_clock::now();
// 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();
}
std::chrono::high_resolution_clock::duration initialPartitionTime = std::chrono::high_resolution_clock::now() - initialPartitionStart;
std::chrono::high_resolution_clock::time_point refinementStart = std::chrono::high_resolution_clock::now();
this->performPartitionRefinement();
@ -155,12 +168,14 @@ namespace storm {
std::chrono::high_resolution_clock::duration totalTime = std::chrono::high_resolution_clock::now() - totalStart;
if (storm::settings::generalSettings().isShowStatisticsSet()) {
std::chrono::milliseconds initialPartitionTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(initialPartitionTime);
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 initial partition: " << initialPartitionTimeInMilliseconds.count() << "ms" << 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;
@ -174,7 +189,7 @@ namespace storm {
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); } } );
std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (std::unique_ptr<Block> const& block) { if (block->isMarkedAsSplitter()) { splitterQueue.push_back(block.get()); } } );
// Then perform the actual splitting until there are no more splitters.
while (!splitterQueue.empty()) {
@ -187,6 +202,7 @@ namespace storm {
splitter->unmarkAsSplitter();
// Now refine the partition using the current splitter.
std::cout << "refining based on splitter " << splitter->getId() << std::endl;
refinePartitionBasedOnSplitter(*splitter, splitterQueue);
}
}
@ -200,14 +216,13 @@ namespace storm {
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]; });
partition.split([&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;
@ -220,11 +235,14 @@ namespace storm {
if (options.keepRewards && model.hasRewardModel()) {
this->splitInitialPartitionBasedOnStateRewards();
}
std::cout << "successfully built (label) initial partition" << std::endl;
partition.print();
}
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());
std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = this->getStatesWithProbability01();
boost::optional<storm::storage::sparse::state_type> representativePsiState;
if (!options.psiStates.get().empty()) {
@ -238,6 +256,9 @@ namespace storm {
if (options.keepRewards && model.hasRewardModel()) {
this->splitInitialPartitionBasedOnStateRewards();
}
std::cout << "successfully built (measure-driven) initial partition" << std::endl;
partition.print();
}
template<typename ModelType>
@ -245,15 +266,19 @@ namespace storm {
// 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()) {
for (auto const& blockPtr : partition.getBlocks()) {
// We need to sort the states to allow for rapid construction of the blocks.
partition.sortBlock(block);
partition.sortBlock(*blockPtr);
// Convert the state-value-pairs to states only.
this->blocks[block.getId()] = block_type(partition.begin(block), partition.end(block), true);
this->blocks[blockPtr->getId()] = block_type(partition.begin(*blockPtr), partition.end(*blockPtr), true);
}
}
template class BisimulationDecomposition<storm::models::sparse::Dtmc<double>>;
#ifdef STORM_HAVE_CARL
template class BisimulationDecomposition<storm::models::sparse::Dtmc<storm::RationalFunction>>;
#endif
}
}

9
src/storage/bisimulation/BisimulationDecomposition.h

@ -137,13 +137,13 @@ namespace storm {
*/
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();
protected:
/*!
* 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
@ -158,7 +158,7 @@ namespace storm {
* @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);
virtual void refinePartitionBasedOnSplitter(bisimulation::Block const& splitter, std::deque<bisimulation::Block*>& splitterQueue) = 0;
/*!
* Builds the quotient model based on the previously computed equivalence classes (stored in the blocks
@ -180,12 +180,9 @@ namespace storm {
* 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;
virtual std::pair<storm::storage::BitVector, storm::storage::BitVector> getStatesWithProbability01() = 0;
/*!
* Splits the initial partition based on the (unique) state reward vector of the model.

41
src/storage/bisimulation/Block.cpp

@ -5,13 +5,14 @@
#include "src/storage/bisimulation/Partition.h"
#include "src/exceptions/InvalidOperationException.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) {
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), needsRefinementFlag(false), absorbing(false), id(id) {
if (nextBlock != nullptr) {
nextBlock->previousBlock = this;
}
@ -21,11 +22,20 @@ namespace storm {
STORM_LOG_ASSERT(this->beginIndex < this->endIndex, "Unable to create block of illegal size.");
}
bool Block::operator==(Block const& other) const {
return this == &other;
}
bool Block::operator!=(Block const& other) const {
return this != &other;
}
void Block::print(Partition const& partition) const {
std::cout << "block " << this->id << " from " << this->beginIndex << " to " << this->endIndex;
std::cout << "block [" << this << "] " << this->id << " from " << this->beginIndex << " to " << this->endIndex << std::endl;
}
void Block::setBeginIndex(storm::storage::sparse::state_type beginIndex) {
std::cout << "setting beg index to " << beginIndex << " [" << this << "]" << std::endl;
this->beginIndex = beginIndex;
STORM_LOG_ASSERT(beginIndex < endIndex, "Unable to resize block to illegal size.");
}
@ -50,6 +60,10 @@ namespace storm {
bool Block::hasNextBlock() const {
return this->nextBlock != nullptr;
}
Block* Block::getNextBlockPointer() {
return this->nextBlock;
}
Block const* Block::getNextBlockPointer() const {
return this->nextBlock;
@ -58,7 +72,11 @@ namespace storm {
Block const& Block::getPreviousBlock() const {
return *this->previousBlock;
}
Block* Block::getPreviousBlockPointer() {
return this->previousBlock;
}
Block const* Block::getPreviousBlockPointer() const {
return this->previousBlock;
}
@ -70,7 +88,7 @@ namespace storm {
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.");
STORM_LOG_ASSERT(!this->hasNextBlock() || this->getNextBlock().getPreviousBlockPointer() == this, "Illegal next block.");
return true;
}
@ -114,19 +132,16 @@ namespace storm {
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;
// Retrieves whether the block is marked as a predecessor.
bool Block::needsRefinement() const {
return needsRefinementFlag;
}
bool Block::isMarkedAsPredecessor() const {
return markedAsPredecessorBlock;
// Marks the block as needing refinement (or not).
void Block::setNeedsRefinement(bool value) {
needsRefinementFlag = value;
}
}
}
}

22
src/storage/bisimulation/Block.h

@ -25,6 +25,9 @@ namespace storm {
Block(Block&& other) = default;
Block& operator=(Block&& other) = default;
bool operator==(Block const& other) const;
bool operator!=(Block const& other) const;
// Prints the block to the standard output.
void print(Partition const& partition) const;
@ -36,6 +39,9 @@ namespace storm {
// 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();
// Gets a pointer to the next block (if there is one).
Block const* getNextBlockPointer() const;
@ -46,6 +52,9 @@ namespace storm {
// Gets the next block (if there is one).
Block const& getPreviousBlock() const;
// Gets a pointer to the previous block (if there is one).
Block* getPreviousBlockPointer();
// Gets a pointer to the previous block (if there is one).
Block const* getPreviousBlockPointer() const;
@ -71,13 +80,10 @@ namespace storm {
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();
bool needsRefinement() const;
// Removes the marking.
void unmarkAsPredecessorBlock();
// Marks the block as needing refinement (or not).
void setNeedsRefinement(bool value = true);
// Sets whether or not the block is to be interpreted as absorbing.
void setAbsorbing(bool absorbing);
@ -112,8 +118,8 @@ namespace storm {
// 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 field that can be used for marking the block as needing refinement.
bool needsRefinementFlag;
// A flag indicating whether the block is to be interpreted as absorbing or not.
bool absorbing;

585
src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp

@ -17,7 +17,6 @@
#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"
@ -29,33 +28,15 @@ namespace storm {
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) {
DeterministicModelBisimulationDecomposition<ModelType>::DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition<ModelType>::Options const& options) : BisimulationDecomposition<ModelType>(model, options), probabilitiesToCurrentSplitter(model.getNumberOfStates(), storm::utility::zero<ValueType>()), predecessorsOfCurrentSplitter(model.getNumberOfStates()) {
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();
STORM_LOG_THROW(options.type != BisimulationType::Weak || !options.bounded, storm::exceptions::IllegalFunctionCallException, "Weak bisimulation cannot preserve bounded properties.");
}
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);
std::pair<storm::storage::BitVector, storm::storage::BitVector> DeterministicModelBisimulationDecomposition<ModelType>::getStatesWithProbability01() {
return storm::utility::graph::performProb01(this->backwardTransitions, this->options.phiStates.get(), this->options.psiStates.get());
}
template<typename ModelType>
@ -64,28 +45,28 @@ namespace storm {
stateStack.reserve(this->model.getNumberOfStates());
storm::storage::BitVector nondivergentStates(this->model.getNumberOfStates());
for (auto& block : this->partition.getBlocks()) {
for (auto const& blockPtr : 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)) {
for (auto stateIt = this->partition.begin(*blockPtr), stateIte = this->partition.end(*blockPtr); stateIt != stateIte; ++stateIt) {
if (nondivergentStates.get(*stateIt)) {
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)) {
for (auto const& successor : this->model.getRows(*stateIt)) {
// 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) {
if (this->partition.getBlock(successor.getColumn()) != *blockPtr) {
isDirectlyNonDivergent = true;
break;
}
}
if (isDirectlyNonDivergent) {
stateStack.push_back(stateIt->first);
stateStack.push_back(*stateIt);
while (!stateStack.empty()) {
storm::storage::sparse::state_type currentState = stateStack.back();
@ -93,7 +74,7 @@ namespace storm {
nondivergentStates.set(currentState);
for (auto const& predecessor : this->backwardTransitions.getRow(currentState)) {
if (&this->partition.getBlock(predecessor.getColumn()) == &block && !nondivergentStates.get(predecessor.getColumn())) {
if (this->partition.getBlock(predecessor.getColumn()) == *blockPtr && !nondivergentStates.get(predecessor.getColumn())) {
stateStack.push_back(predecessor.getColumn());
}
}
@ -102,17 +83,17 @@ namespace storm {
}
if (nondivergentStates.getNumberOfSetBits() > 0 && nondivergentStates.getNumberOfSetBits() != block.getNumberOfStates()) {
if (nondivergentStates.getNumberOfSetBits() > 0 && nondivergentStates.getNumberOfSetBits() != blockPtr->getNumberOfStates()) {
// After performing the split, the current block will contain the divergent states only.
this->partition.splitStates(block, nondivergentStates);
this->partition.splitStates(*blockPtr, 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);
blockPtr->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);
blockPtr->setAbsorbing(true);
}
}
}
@ -141,8 +122,8 @@ namespace storm {
template<typename ModelType>
void DeterministicModelBisimulationDecomposition<ModelType>::initializeMeasureDrivenPartition() {
BisimulationDecomposition<ModelType>::initializeMeasureDrivenPartition();
if (this->options.type == BisimulationType::Weak && this->model.getModelType() == ModelType::Dtmc) {
if (this->options.type == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc) {
this->initializeWeakDtmcBisimulation();
}
}
@ -151,12 +132,112 @@ namespace storm {
void DeterministicModelBisimulationDecomposition<ModelType>::initializeLabelBasedPartition() {
BisimulationDecomposition<ModelType>::initializeLabelBasedPartition();
if (this->options.type == BisimulationType::Weak && this->model.getModelType() == ModelType::Dtmc) {
if (this->options.type == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc) {
this->initializeWeakDtmcBisimulation();
}
}
template<typename ModelType>
typename DeterministicModelBisimulationDecomposition<ModelType>::ValueType const& DeterministicModelBisimulationDecomposition<ModelType>::getProbabilityToSplitter(storm::storage::sparse::state_type const& state) const {
return probabilitiesToCurrentSplitter[state];
}
template<typename ModelType>
bool DeterministicModelBisimulationDecomposition<ModelType>::isSilent(storm::storage::sparse::state_type const& state) const {
return this->comparator.isOne(silentProbabilities[state]);
}
template<typename ModelType>
typename DeterministicModelBisimulationDecomposition<ModelType>::ValueType DeterministicModelBisimulationDecomposition<ModelType>::getSilentProbability(storm::storage::sparse::state_type const& state) const {
return silentProbabilities[state];
}
template<typename ModelType>
bool DeterministicModelBisimulationDecomposition<ModelType>::isPredecessorOfCurrentSplitter(storm::storage::sparse::state_type const& state) const {
return this->predecessorsOfCurrentSplitter.get(state);
}
template<typename ModelType>
void DeterministicModelBisimulationDecomposition<ModelType>::refinePredecessorBlocksOfSplitter(std::list<Block*>& predecessorBlocks, std::deque<bisimulation::Block*>& splitterQueue) {
for (auto block : predecessorBlocks) {
std::cout << "splitting predecessor block " << block->getId() << " of splitter" << std::endl;
this->partition.splitBlock(*block, [this] (storm::storage::sparse::state_type const& state1, storm::storage::sparse::state_type const& state2) {
bool firstIsPredecessor = isPredecessorOfCurrentSplitter(state1);
bool secondIsPredecessor = isPredecessorOfCurrentSplitter(state2);
if (firstIsPredecessor && !secondIsPredecessor) {
return true;
} else if (firstIsPredecessor && secondIsPredecessor) {
return getProbabilityToSplitter(state1) < getProbabilityToSplitter(state2);
} else {
return false;
}
}, [&splitterQueue] (Block& block) {
splitterQueue.emplace_back(&block);
});
// this->partition.print();
std::cout << "size: " << this->partition.size() << std::endl;
// Remember that we have refined the block.
block->setNeedsRefinement(false);
this->partition.check();
}
}
template<typename ModelType>
void DeterministicModelBisimulationDecomposition<ModelType>::refinePartitionBasedOnSplitter(bisimulation::Block const& splitter, std::deque<bisimulation::Block*>& splitterQueue) {
// The outline of the refinement is as follows.
//
// (0) we prepare the environment for the splitting process.
//
// (1) we iterate over all states of the splitter and determine for each predecessor the state the
// probability entering the splitter. These probabilities are written to a member vector. Doing so, we marl
// all predecessors of the splitter in a member bit vector.
// (0)
predecessorsOfCurrentSplitter.clear();
std::list<Block*> predecessorBlocks;
// (1)
for (auto splitterIt = this->partition.begin(splitter), splitterIte = this->partition.end(splitter); splitterIt != splitterIte; ++splitterIt) {
storm::storage::sparse::state_type currentState = *splitterIt;
for (auto const& predecessorEntry : this->backwardTransitions.getRow(currentState)) {
storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn();
Block& predecessorBlock = this->partition.getBlock(predecessor);
// 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;
}
// If we have not seen this predecessor before, we reset its value and mark it as a predecessor of
// the splitter.
if (!predecessorsOfCurrentSplitter.get(predecessor)) {
predecessorsOfCurrentSplitter.set(predecessor);
probabilitiesToCurrentSplitter[predecessor] = predecessorEntry.getValue();
} else {
// Otherwise, we increase the probability by the current transition.
probabilitiesToCurrentSplitter[predecessor] += predecessorEntry.getValue();
}
if (!predecessorBlock.needsRefinement()) {
predecessorBlocks.emplace_back(&predecessorBlock);
predecessorBlock.setNeedsRefinement();
}
}
}
std::cout << "probs of splitter predecessors: " << std::endl;
for (auto state : predecessorsOfCurrentSplitter) {
std::cout << state << " [" << this->partition.getBlock(state).getId() << "]" << " -> " << probabilitiesToCurrentSplitter[state] << std::endl;
}
if (this->options.type == BisimulationType::Strong || this->model.getType() == storm::models::ModelType::Ctmc) {
refinePredecessorBlocksOfSplitter(predecessorBlocks, splitterQueue);
} else {
assert(false);
}
}
template<typename ModelType>
void DeterministicModelBisimulationDecomposition<ModelType>::buildQuotient() {
@ -170,7 +251,7 @@ namespace storm {
// Prepare the new state labeling for (b).
storm::models::sparse::StateLabeling newLabeling(this->size());
std::set<std::string> atomicPropositionsSet = this->options.selectedAtomicPropositions.get();
std::set<std::string> atomicPropositionsSet = this->options.respectedAtomicPropositions.get();
atomicPropositionsSet.insert("init");
std::vector<std::string> atomicPropositions = std::vector<std::string>(atomicPropositionsSet.begin(), atomicPropositionsSet.end());
for (auto const& ap : atomicPropositions) {
@ -195,14 +276,14 @@ namespace storm {
// there is any such state).
if (this->options.type == BisimulationType::Weak) {
for (auto const& state : block) {
if (!partition.isSilent(state, comparator)) {
if (!isSilent(state)) {
representativeState = state;
break;
}
}
}
Block const& oldBlock = partition.getBlock(representativeState);
Block const& oldBlock = this->partition.getBlock(representativeState);
// If the block is absorbing, we simply add a self-loop.
if (oldBlock.isAbsorbing()) {
@ -212,22 +293,22 @@ namespace storm {
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)) {
if (this->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();
for (auto const& entry : this->model.getTransitionMatrix().getRow(representativeState)) {
storm::storage::sparse::state_type targetBlock = this->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) {
if ((this->options.type == BisimulationType::Weak) && targetBlock == blockIndex) {
continue;
}
@ -241,8 +322,8 @@ namespace storm {
// 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)));
if (this->options.type == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc) {
builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second / (storm::utility::one<ValueType>() - getSilentProbability(representativeState)));
} else {
builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second);
}
@ -251,7 +332,7 @@ namespace storm {
// 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)) {
if (this->model.getStateLabeling().getStateHasLabel(ap, representativeState)) {
newLabeling.addLabelToState(ap, blockIndex);
}
}
@ -259,423 +340,33 @@ namespace storm {
// 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();
if (this->options.keepRewards && this->model.hasRewardModel()) {
typename std::unordered_map<std::string, typename ModelType::RewardModelType>::const_iterator nameRewardModelPair = this->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);
for (auto initialState : this->model.getInitialStates()) {
Block const& initialBlock = this->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();
if (this->options.keepRewards && this->model.hasRewardModel()) {
typename std::unordered_map<std::string, typename ModelType::RewardModelType>::const_iterator nameRewardModelPair = this->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.");
this->quotient = std::shared_ptr<ModelType>(new ModelType(builder.build(), std::move(newLabeling), std::move(rewardModels)));
}
template class DeterministicModelBisimulationDecomposition<double>;
template class DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>;
#ifdef STORM_HAVE_CARL
template class DeterministicModelBisimulationDecomposition<storm::RationalFunction>;
template class DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<storm::RationalFunction>>;
#endif
}
}

106
src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h

@ -26,13 +26,22 @@ namespace storm {
* @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());
DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition<ModelType>::Options const& options = typename BisimulationDecomposition<ModelType>::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;
protected:
virtual std::pair<storm::storage::BitVector, storm::storage::BitVector> getStatesWithProbability01() override;
virtual void initializeMeasureDrivenPartition() override;
virtual void initializeLabelBasedPartition() override;
virtual void buildQuotient() override;
virtual void refinePartitionBasedOnSplitter(bisimulation::Block const& splitter, std::deque<bisimulation::Block*>& splitterQueue) override;
private:
virtual void refinePredecessorBlocksOfSplitter(std::list<bisimulation::Block*>& predecessorBlocks, std::deque<bisimulation::Block*>& splitterQueue);
/*!
* Performs the necessary steps to compute a weak bisimulation on a DTMC.
*/
@ -49,90 +58,25 @@ namespace storm {
*/
void initializeSilentProbabilities();
virtual void initializeMeasureDrivenPartition() override;
// Retrieves the probability of going into the splitter for the given state.
ValueType const& getProbabilityToSplitter(storm::storage::sparse::state_type const& state) const;
virtual void initializeLabelBasedPartition() override;
// Retrieves the silent probability for the given state.
ValueType getSilentProbability(storm::storage::sparse::state_type const& state) const;
virtual void buildQuotient() override;
// Retrieves whether the given state is silent.
bool isSilent(storm::storage::sparse::state_type const& state) const;
/*!
* 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);
// Retrieves whether the given state is a predecessor of the current splitter.
bool isPredecessorOfCurrentSplitter(storm::storage::sparse::state_type const& state) const;
/*!
* 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);
// A vector that holds the probabilities of states going into the splitter. This is used by the method that
// refines a block based on probabilities.
std::vector<ValueType> probabilitiesToCurrentSplitter;
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);
// A bit vector storing the predecessors of the current splitter.
storm::storage::BitVector predecessorsOfCurrentSplitter;
/*!
* 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;
};

139
src/storage/bisimulation/Partition.cpp

@ -9,13 +9,13 @@ 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());
blocks.emplace_back(new Block(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();
stateToBlockMapping[state] = blocks.back().get();
}
}
@ -25,8 +25,8 @@ namespace storm {
Block* secondBlock = nullptr;
Block* thirdBlock = nullptr;
if (!prob0States.empty()) {
blocks.emplace_back(0, prob0States.getNumberOfSetBits(), nullptr, nullptr, blocks.size());
firstBlock = &blocks[0];
blocks.emplace_back(new Block(0, prob0States.getNumberOfSetBits(), nullptr, nullptr, blocks.size()));
firstBlock = blocks.front().get();
for (auto state : prob0States) {
states[position] = state;
@ -35,11 +35,12 @@ namespace storm {
++position;
}
firstBlock->setAbsorbing(true);
firstBlock->markAsSplitter();
}
if (!prob1States.empty()) {
blocks.emplace_back(position, position + prob1States.getNumberOfSetBits(), firstBlock, nullptr, blocks.size());
secondBlock = &blocks[1];
blocks.emplace_back(new Block(position, position + prob1States.getNumberOfSetBits(), firstBlock, nullptr, blocks.size()));
secondBlock = blocks[1].get();
for (auto state : prob1States) {
states[position] = state;
@ -49,12 +50,13 @@ namespace storm {
}
secondBlock->setAbsorbing(true);
secondBlock->setRepresentativeState(representativeProb1State.get());
secondBlock->markAsSplitter();
}
storm::storage::BitVector otherStates = ~(prob0States | prob1States);
if (!otherStates.empty()) {
blocks.emplace_back(position, numberOfStates, secondBlock, nullptr, blocks.size());
thirdBlock = &blocks[2];
blocks.emplace_back(new Block(position, numberOfStates, secondBlock, nullptr, blocks.size()));
thirdBlock = blocks[2].get();
for (auto state : otherStates) {
states[position] = state;
@ -62,6 +64,7 @@ namespace storm {
stateToBlockMapping[state] = thirdBlock;
++position;
}
thirdBlock->markAsSplitter();
}
}
@ -111,94 +114,122 @@ namespace storm {
Block const& Partition::getBlock(storm::storage::sparse::state_type state) const {
return *this->stateToBlockMapping[state];
}
std::vector<storm::storage::sparse::state_type>::iterator Partition::begin(Block const& block) {
auto it = this->states.begin();
std::advance(it, block.getBeginIndex());
return it;
}
std::vector<storm::storage::sparse::state_type>::const_iterator Partition::begin(Block const& block) const {
return this->states.begin() + block.getBeginIndex();
auto it = this->states.begin();
std::advance(it, block.getBeginIndex());
return it;
}
std::vector<storm::storage::sparse::state_type>::iterator Partition::end(Block const& block) {
auto it = this->states.begin();
std::advance(it, block.getEndIndex());
return it;
}
std::vector<storm::storage::sparse::state_type>::const_iterator Partition::end(Block const& block) const {
return this->states.begin() + block.getEndIndex();
auto it = this->states.begin();
std::advance(it, block.getEndIndex());
return it;
}
Block& Partition::splitBlock(Block& block, storm::storage::sparse::state_type position) {
std::pair<std::vector<std::unique_ptr<Block>>::iterator, bool> 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.");
std::cout << "splitting " << block.getId() << " at pos " << position << " (was " << block.getBeginIndex() << " to " << block.getEndIndex() << ")" << std::endl;
// 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;
auto it = blocks.begin();
std::advance(it, block.getId());
return std::make_pair(it, false);
}
// Actually create the new block.
blocks.emplace_back(block.getBeginIndex(), position, block.getPreviousBlockPointer(), &block, blocks.size());
Block& newBlock = blocks.back();
blocks.emplace_back(new Block(block.getBeginIndex(), position, block.getPreviousBlockPointer(), &block, blocks.size()));
auto newBlockIt = std::prev(blocks.end());
// Resize the current block appropriately.
std::cout << "setting begin pos of block " << block.getId() << " to " << position << std::endl;
block.setBeginIndex(position);
// Mark both blocks as splitters.
block.markAsSplitter();
newBlock.markAsSplitter();
(*newBlockIt)->markAsSplitter();
// Update the mapping of the states in the newly created block.
this->mapStatesToBlock(newBlock, this->begin(newBlock), this->end(newBlock));
this->mapStatesToBlock(**newBlockIt, this->begin(**newBlockIt), this->end(**newBlockIt));
return newBlock;
return std::make_pair(newBlockIt, true);
}
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) {
bool Partition::splitBlock(Block& block, std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& less, std::function<void (Block&)> const& newBlockCallback) {
std::cout << "sorting the block [" << block.getId() << "]" << std::endl;
// 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);
std::sort(this->begin(block), this->end(block), less);
// std::cout << "after" << std::endl;
// for (auto it = this->begin(block), ite = this->end(block); it != ite; ++it) {
// std::cout << *it << " ";
// }
// std::cout << std::endl;
// Update the positions vector.
mapStatesToPositions(block);
// for (auto it = this->positions.begin() + block.getBeginIndex(), ite = this->positions.begin() + block.getEndIndex(); it != ite; ++it) {
// std::cout << *it << " ";
// }
// std::cout << std::endl;
// 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)) {
bool wasSplit = false;
while (less(states[begin], states[end])) {
wasSplit = true;
// 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])) {
while (begin != end && !less(states[begin], states[currentIndex])) {
++currentIndex;
}
begin = currentIndex;
this->splitBlock(block, currentIndex);
auto result = this->splitBlock(block, currentIndex);
if (result.second) {
newBlockCallback(**result.first);
}
}
return wasSplit;
}
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);
bool Partition::split(std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& less, std::function<void (Block&)> const& newBlockCallback) {
bool result = false;
// Since the underlying storage of the blocks may change during iteration, we remember the current size
// and iterate over indices. This assumes that new blocks will be added at the end of the blocks vector.
std::size_t currentSize = this->size();
for (uint_fast64_t index = 0; index < currentSize; ++index) {
result |= splitBlock(*blocks[index], less, newBlockCallback);
}
return result;
}
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(Block& block, storm::storage::BitVector const& states) {
this->splitBlock(block, [&states] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return states.get(a) && !states.get(b); });
}
void Partition::splitStates(storm::storage::BitVector const& states) {
for (auto& block : blocks) {
splitStates(block, states);
}
this->split([&states] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return states.get(a) && !states.get(b); });
}
void Partition::sortBlock(Block const& block) {
@ -211,8 +242,8 @@ namespace storm {
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();
blocks.emplace_back(new Block(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) {
@ -222,11 +253,11 @@ namespace storm {
return newBlock;
}
std::vector<Block> const& Partition::getBlocks() const {
std::vector<std::unique_ptr<Block>> const& Partition::getBlocks() const {
return this->blocks;
}
std::vector<Block>& Partition::getBlocks() {
std::vector<std::unique_ptr<Block>>& Partition::getBlocks() {
return this->blocks;
}
@ -234,10 +265,10 @@ namespace storm {
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.");
for (auto const& blockPtr : this->blocks) {
STORM_LOG_ASSERT(blockPtr->check(), "Block corrupted.");
for (auto stateIt = this->begin(*blockPtr), stateIte = this->end(*blockPtr); stateIt != stateIte; ++stateIt) {
STORM_LOG_ASSERT(this->stateToBlockMapping[*stateIt] == blockPtr.get(), "Block mapping corrupted.");
}
}
return true;
@ -245,7 +276,7 @@ namespace storm {
void Partition::print() const {
for (auto const& block : this->blocks) {
block.print(*this);
block->print(*this);
}
std::cout << "states in partition" << std::endl;
for (auto const& state : states) {

25
src/storage/bisimulation/Partition.h

@ -49,19 +49,20 @@ namespace storm {
// 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);
std::pair<std::vector<std::unique_ptr<Block>>::iterator, bool> 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);
// points. The callback function is called for every newly created block.
bool splitBlock(Block& block, std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& less, std::function<void (Block&)> const& newBlockCallback = [] (Block&) {});
// 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 all blocks by using the sorting-based splitting. The callback is called for all newly created
// blocks.
bool split(std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& less, std::function<void (Block&)> const& newBlockCallback = [] (Block&) {});
// 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);
void 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
@ -73,16 +74,22 @@ namespace storm {
void sortBlock(Block const& block);
// Retrieves the blocks of the partition.
std::vector<Block> const& getBlocks() const;
std::vector<std::unique_ptr<Block>> const& getBlocks() const;
// Retrieves the blocks of the partition.
std::vector<Block>& getBlocks();
std::vector<std::unique_ptr<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>::iterator begin(Block const& block);
// 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>::iterator end(Block const& block);
// 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;
@ -123,7 +130,7 @@ namespace storm {
void setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position);
// The of blocks in the partition.
std::vector<Block> blocks;
std::vector<std::unique_ptr<Block>> blocks;
// A mapping of states to their blocks.
std::vector<Block*> stateToBlockMapping;

167
test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp

@ -2,6 +2,7 @@
#include "storm-config.h"
#include "src/parser/AutoParser.h"
#include "src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h"
#include "src/models/sparse/Dtmc.h"
#include "src/models/sparse/StandardRewardModel.h"
TEST(DeterministicModelBisimulationDecomposition, Die) {
@ -10,7 +11,8 @@ TEST(DeterministicModelBisimulationDecomposition, Die) {
ASSERT_EQ(abstractModel->getType(), storm::models::ModelType::Dtmc);
std::shared_ptr<storm::models::sparse::Dtmc<double>> dtmc = abstractModel->as<storm::models::sparse::Dtmc<double>>();
storm::storage::DeterministicModelBisimulationDecomposition<double> bisim(*dtmc);
storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>> bisim(*dtmc);
ASSERT_NO_THROW(bisim.computeBisimulationDecomposition());
std::shared_ptr<storm::models::sparse::Model<double>> result;
ASSERT_NO_THROW(result = bisim.getQuotient());
@ -19,39 +21,42 @@ TEST(DeterministicModelBisimulationDecomposition, Die) {
EXPECT_EQ(20ul, result->getNumberOfTransitions());
#ifdef WINDOWS
storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options;
storm::storage::BisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options;
#else
typename storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options;
typename storm::storage::BisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options;
#endif
options.respectedAtomicPropositions = std::set<std::string>({"one"});
storm::storage::DeterministicModelBisimulationDecomposition<double> bisim2(*dtmc, options);
storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>> bisim2(*dtmc, options);
ASSERT_NO_THROW(bisim2.computeBisimulationDecomposition());
ASSERT_NO_THROW(result = bisim2.getQuotient());
EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
EXPECT_EQ(5ul, result->getNumberOfStates());
EXPECT_EQ(8ul, result->getNumberOfTransitions());
options.bounded = false;
options.weak = true;
storm::storage::DeterministicModelBisimulationDecomposition<double> bisim3(*dtmc, options);
ASSERT_NO_THROW(result = bisim3.getQuotient());
EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
EXPECT_EQ(5ul, result->getNumberOfStates());
EXPECT_EQ(8ul, result->getNumberOfTransitions());
// options.bounded = false;
// options.type = storm::storage::BisimulationType::Weak;
//
// storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>> bisim3(*dtmc, options);
// ASSERT_NO_THROW(bisim3.computeBisimulationDecomposition());
// ASSERT_NO_THROW(result = bisim3.getQuotient());
//
// EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
// EXPECT_EQ(5ul, result->getNumberOfStates());
// EXPECT_EQ(8ul, result->getNumberOfTransitions());
auto labelFormula = std::make_shared<storm::logic::AtomicLabelFormula>("one");
auto eventuallyFormula = std::make_shared<storm::logic::EventuallyFormula>(labelFormula);
#ifdef WINDOWS
storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options2(*dtmc, *eventuallyFormula);
storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options2(*dtmc, *eventuallyFormula);
#else
typename storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options2(*dtmc, *eventuallyFormula);
typename storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options2(*dtmc, *eventuallyFormula);
#endif
storm::storage::DeterministicModelBisimulationDecomposition<double> bisim4(*dtmc, options2);
storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>> bisim4(*dtmc, options2);
ASSERT_NO_THROW(bisim4.computeBisimulationDecomposition());
ASSERT_NO_THROW(result = bisim4.getQuotient());
EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
EXPECT_EQ(5ul, result->getNumberOfStates());
@ -64,78 +69,84 @@ TEST(DeterministicModelBisimulationDecomposition, Crowds) {
ASSERT_EQ(abstractModel->getType(), storm::models::ModelType::Dtmc);
std::shared_ptr<storm::models::sparse::Dtmc<double>> dtmc = abstractModel->as<storm::models::sparse::Dtmc<double>>();
storm::storage::DeterministicModelBisimulationDecomposition<double> bisim(*dtmc);
// storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>> bisim(*dtmc);
std::shared_ptr<storm::models::sparse::Model<double>> result;
ASSERT_NO_THROW(result = bisim.getQuotient());
EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
EXPECT_EQ(334ul, result->getNumberOfStates());
EXPECT_EQ(546ul, result->getNumberOfTransitions());
#ifdef WINDOWS
storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options;
#else
typename storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options;
#endif
options.respectedAtomicPropositions = std::set<std::string>({"observe0Greater1"});
storm::storage::DeterministicModelBisimulationDecomposition<double> bisim2(*dtmc, options);
ASSERT_NO_THROW(result = bisim2.getQuotient());
EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
EXPECT_EQ(65ul, result->getNumberOfStates());
EXPECT_EQ(105ul, result->getNumberOfTransitions());
options.bounded = false;
options.weak = true;
storm::storage::DeterministicModelBisimulationDecomposition<double> bisim3(*dtmc, options);
ASSERT_NO_THROW(result = bisim3.getQuotient());
EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
EXPECT_EQ(43ul, result->getNumberOfStates());
EXPECT_EQ(83ul, result->getNumberOfTransitions());
// ASSERT_NO_THROW(bisim.computeBisimulationDecomposition());
// ASSERT_NO_THROW(result = bisim.getQuotient());
//
// EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
// EXPECT_EQ(334ul, result->getNumberOfStates());
// EXPECT_EQ(546ul, result->getNumberOfTransitions());
//#ifdef WINDOWS
// storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options;
//#else
// typename storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options;
//#endif
// options.respectedAtomicPropositions = std::set<std::string>({"observe0Greater1"});
//
// storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>> bisim2(*dtmc, options);
// ASSERT_NO_THROW(bisim2.computeBisimulationDecomposition());
// ASSERT_NO_THROW(result = bisim2.getQuotient());
//
// EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
// EXPECT_EQ(65ul, result->getNumberOfStates());
// EXPECT_EQ(105ul, result->getNumberOfTransitions());
//
// options.bounded = false;
// options.type = storm::storage::BisimulationType::Weak;
//
// storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>> bisim3(*dtmc, options);
// ASSERT_NO_THROW(bisim3.computeBisimulationDecomposition());
// ASSERT_NO_THROW(result = bisim3.getQuotient());
//
// EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
// EXPECT_EQ(43ul, result->getNumberOfStates());
// EXPECT_EQ(83ul, result->getNumberOfTransitions());
//
auto labelFormula = std::make_shared<storm::logic::AtomicLabelFormula>("observe0Greater1");
auto eventuallyFormula = std::make_shared<storm::logic::EventuallyFormula>(labelFormula);
#ifdef WINDOWS
storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options2(*dtmc, *eventuallyFormula);
storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options2(*dtmc, *eventuallyFormula);
#else
typename storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options2(*dtmc, *eventuallyFormula);
typename storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options2(*dtmc, *eventuallyFormula);
#endif
storm::storage::DeterministicModelBisimulationDecomposition<double> bisim4(*dtmc, options2);
storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>> bisim4(*dtmc, options2);
ASSERT_NO_THROW(bisim4.computeBisimulationDecomposition());
ASSERT_NO_THROW(result = bisim4.getQuotient());
EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
EXPECT_EQ(64ul, result->getNumberOfStates());
EXPECT_EQ(104ul, result->getNumberOfTransitions());
auto probabilityOperatorFormula = std::make_shared<storm::logic::ProbabilityOperatorFormula>(eventuallyFormula);
#ifdef WINDOWS
storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options3(*dtmc, *probabilityOperatorFormula);
#else
typename storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options3(*dtmc, *probabilityOperatorFormula);
#endif
storm::storage::DeterministicModelBisimulationDecomposition<double> bisim5(*dtmc, options3);
ASSERT_NO_THROW(result = bisim5.getQuotient());
EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
EXPECT_EQ(64ul, result->getNumberOfStates());
EXPECT_EQ(104ul, result->getNumberOfTransitions());
auto boundedUntilFormula = std::make_shared<storm::logic::BoundedUntilFormula>(std::make_shared<storm::logic::BooleanLiteralFormula>(true), labelFormula, 50);
#ifdef WINDOWS
storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options4(*dtmc, *boundedUntilFormula);
#else
typename storm::storage::DeterministicModelBisimulationDecomposition<double>::Options options4(*dtmc, *boundedUntilFormula);
#endif
storm::storage::DeterministicModelBisimulationDecomposition<double> bisim6(*dtmc, options4);
ASSERT_NO_THROW(result = bisim6.getQuotient());
EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
EXPECT_EQ(65ul, result->getNumberOfStates());
EXPECT_EQ(105ul, result->getNumberOfTransitions());
//
// auto probabilityOperatorFormula = std::make_shared<storm::logic::ProbabilityOperatorFormula>(eventuallyFormula);
//
//#ifdef WINDOWS
// storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options3(*dtmc, *probabilityOperatorFormula);
//#else
// typename storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options3(*dtmc, *probabilityOperatorFormula);
//#endif
// storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>> bisim5(*dtmc, options3);
// ASSERT_NO_THROW(bisim5.computeBisimulationDecomposition());
// ASSERT_NO_THROW(result = bisim5.getQuotient());
//
// EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
// EXPECT_EQ(64ul, result->getNumberOfStates());
// EXPECT_EQ(104ul, result->getNumberOfTransitions());
//
// auto boundedUntilFormula = std::make_shared<storm::logic::BoundedUntilFormula>(std::make_shared<storm::logic::BooleanLiteralFormula>(true), labelFormula, 50);
//
//#ifdef WINDOWS
// storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options4(*dtmc, *boundedUntilFormula);
//#else
// typename storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options4(*dtmc, *boundedUntilFormula);
//#endif
// storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>> bisim6(*dtmc, options4);
// ASSERT_NO_THROW(bisim6.computeBisimulationDecomposition());
// ASSERT_NO_THROW(result = bisim6.getQuotient());
//
// EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType());
// EXPECT_EQ(65ul, result->getNumberOfStates());
// EXPECT_EQ(105ul, result->getNumberOfTransitions());
}
Loading…
Cancel
Save