Browse Source

made strong bisim for DTMCs work again

Former-commit-id: e42bafef4d
tempestpy_adaptions
dehnert 9 years ago
parent
commit
46fee522ff
  1. 14
      src/storage/bisimulation/BisimulationDecomposition.cpp
  2. 81
      src/storage/bisimulation/Block.cpp
  3. 54
      src/storage/bisimulation/Block.h
  4. 93
      src/storage/bisimulation/DeterministicBlockData.cpp
  5. 92
      src/storage/bisimulation/DeterministicBlockData.h
  6. 203
      src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp
  7. 17
      src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h
  8. 33
      src/storage/bisimulation/Partition.cpp
  9. 3
      src/storage/bisimulation/Partition.h
  10. 4
      test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp

14
src/storage/bisimulation/BisimulationDecomposition.cpp

@ -192,9 +192,9 @@ namespace storm {
template<typename ModelType, typename BlockDataType>
void BisimulationDecomposition<ModelType, BlockDataType>::performPartitionRefinement() {
// Insert all blocks into the splitter queue that are initially marked as being a (potential) splitter.
// Insert all blocks into the splitter queue as a (potential) splitter.
std::deque<Block<BlockDataType>*> splitterQueue;
std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (std::unique_ptr<Block<BlockDataType>> const& block) { if (block->isMarkedAsSplitter()) { splitterQueue.push_back(block.get()); } } );
std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (std::unique_ptr<Block<BlockDataType>> const& block) { splitterQueue.push_back(block.get()); } );
// Then perform the actual splitting until there are no more splitters.
uint_fast64_t iterations = 0;
@ -206,13 +206,11 @@ namespace storm {
// Get and prepare the next splitter.
Block<BlockDataType>* splitter = splitterQueue.front();
splitterQueue.pop_front();
splitter->unmarkAsSplitter();
splitter->data().setSplitter(false);
// Now refine the partition using the current splitter.
// std::cout << "refining based on splitter " << splitter->getId() << std::endl;
refinePartitionBasedOnSplitter(*splitter, splitterQueue);
}
std::cout << "done within " << iterations << " iterations." << std::endl;
}
template<typename ModelType, typename BlockDataType>
@ -243,9 +241,6 @@ namespace storm {
if (options.keepRewards && model.hasRewardModel()) {
this->splitInitialPartitionBasedOnStateRewards();
}
// std::cout << "successfully built (label) initial partition" << std::endl;
// partition.print();
}
template<typename ModelType, typename BlockDataType>
@ -264,9 +259,6 @@ namespace storm {
if (options.keepRewards && model.hasRewardModel()) {
this->splitInitialPartitionBasedOnStateRewards();
}
// std::cout << "successfully built (measure-driven) initial partition" << std::endl;
// partition.print();
}
template<typename ModelType, typename BlockDataType>

81
src/storage/bisimulation/Block.cpp

@ -6,7 +6,6 @@
#include "src/storage/bisimulation/Partition.h"
#include "src/storage/bisimulation/DeterministicBlockData.h"
#include "src/exceptions/InvalidOperationException.h"
#include "src/utility/macros.h"
namespace storm {
@ -14,14 +13,14 @@ namespace storm {
namespace bisimulation {
template<typename DataType>
Block<DataType>::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), mData() {
Block<DataType>::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), id(id), mData() {
if (nextBlock != nullptr) {
nextBlock->previousBlock = this;
}
if (previousBlock != nullptr) {
previousBlock->nextBlock = this;
}
data().notify(*this);
data().resetMarkers(*this);
STORM_LOG_ASSERT(this->beginIndex < this->endIndex, "Unable to create block of illegal size.");
}
@ -37,23 +36,28 @@ namespace storm {
template<typename DataType>
void Block<DataType>::print(Partition<DataType> const& partition) const {
std::cout << "block [" << this << "] " << this->id << " from " << this->beginIndex << " to " << this->endIndex << std::endl;
std::cout << "block [" << this << "] " << this->id << " from " << this->beginIndex << " to " << this->endIndex << " with data [" << this->data() << "]" << std::endl;
}
template<typename DataType>
void Block<DataType>::setBeginIndex(storm::storage::sparse::state_type beginIndex) {
this->beginIndex = beginIndex;
data().notify(*this);
data().resetMarkers(*this);
STORM_LOG_ASSERT(beginIndex < endIndex, "Unable to resize block to illegal size.");
}
template<typename DataType>
void Block<DataType>::setEndIndex(storm::storage::sparse::state_type endIndex) {
this->endIndex = endIndex;
data().notify(*this);
data().resetMarkers(*this);
STORM_LOG_ASSERT(beginIndex < endIndex, "Unable to resize block to illegal size.");
}
template<typename DataType>
std::size_t Block<DataType>::getId() const {
return this->id;
}
template<typename DataType>
storm::storage::sparse::state_type Block<DataType>::getBeginIndex() const {
return this->beginIndex;
@ -116,65 +120,7 @@ namespace storm {
std::size_t Block<DataType>::getNumberOfStates() const {
return (this->endIndex - this->beginIndex);
}
template<typename DataType>
bool Block<DataType>::isMarkedAsSplitter() const {
return this->markedAsSplitter;
}
template<typename DataType>
void Block<DataType>::markAsSplitter() {
this->markedAsSplitter = true;
}
template<typename DataType>
void Block<DataType>::unmarkAsSplitter() {
this->markedAsSplitter = false;
}
template<typename DataType>
std::size_t Block<DataType>::getId() const {
return this->id;
}
template<typename DataType>
void Block<DataType>::setAbsorbing(bool absorbing) {
this->absorbing = absorbing;
}
template<typename DataType>
bool Block<DataType>::isAbsorbing() const {
return this->absorbing;
}
template<typename DataType>
void Block<DataType>::setRepresentativeState(storm::storage::sparse::state_type representativeState) {
this->representativeState = representativeState;
}
template<typename DataType>
bool Block<DataType>::hasRepresentativeState() const {
return static_cast<bool>(representativeState);
}
template<typename DataType>
storm::storage::sparse::state_type Block<DataType>::getRepresentativeState() const {
STORM_LOG_THROW(representativeState, storm::exceptions::InvalidOperationException, "Unable to retrieve representative state for block.");
return representativeState.get();
}
// Retrieves whether the block is marked as a predecessor.
template<typename DataType>
bool Block<DataType>::needsRefinement() const {
return needsRefinementFlag;
}
// Marks the block as needing refinement (or not).
template<typename DataType>
void Block<DataType>::setNeedsRefinement(bool value) {
needsRefinementFlag = value;
}
template<typename DataType>
DataType& Block<DataType>::data() {
return mData;
@ -184,6 +130,11 @@ namespace storm {
DataType const& Block<DataType>::data() const {
return mData;
}
template<typename DataType>
void Block<DataType>::resetMarkers() {
mData.resetMarkers(*this);
}
template class Block<DeterministicBlockData>;

54
src/storage/bisimulation/Block.h

@ -69,45 +69,18 @@ namespace storm {
// 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 needsRefinement() const;
// 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);
// 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;
// Retrieves the additional data associated with this block.
DataType& data();
// Retrieves the additional data associated with this block.
DataType const& data() const;
// Resets all markers.
void resetMarkers();
// Retrieves the ID of the block.
std::size_t getId() const;
private:
// Sets the beginning index of the block.
void setBeginIndex(storm::storage::sparse::state_type beginIndex);
@ -123,22 +96,9 @@ namespace storm {
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 needing refinement.
bool needsRefinementFlag;
// 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;
// A member that stores additional data that depends on the kind of bisimulation.
DataType mData;
};

93
src/storage/bisimulation/DeterministicBlockData.cpp

@ -1,44 +1,101 @@
#include "src/storage/bisimulation/DeterministicBlockData.h"
#include <iostream>
#include "src/exceptions/InvalidOperationException.h"
#include "src/utility/macros.h"
namespace storm {
namespace storage {
namespace bisimulation {
DeterministicBlockData::DeterministicBlockData() : newBeginIndex(0), newEndIndex(0) {
DeterministicBlockData::DeterministicBlockData() : DeterministicBlockData(0, 0) {
// Intentionally left empty.
}
DeterministicBlockData::DeterministicBlockData(uint_fast64_t newBeginIndex, uint_fast64_t newEndIndex) : newBeginIndex(newBeginIndex), newEndIndex(newEndIndex) {
DeterministicBlockData::DeterministicBlockData(uint_fast64_t marker1, uint_fast64_t marker2) : valMarker1(marker1), valMarker2(marker2), splitterFlag(false), needsRefinementFlag(false), absorbingFlag(false), valRepresentativeState() {
// Intentionally left empty.
}
uint_fast64_t DeterministicBlockData::getNewBeginIndex() const {
return newBeginIndex;
uint_fast64_t DeterministicBlockData::marker1() const {
return valMarker1;
}
void DeterministicBlockData::increaseNewBeginIndex() {
++newBeginIndex;
void DeterministicBlockData::setMarker1(uint_fast64_t newMarker1) {
valMarker1 = newMarker1;
}
uint_fast64_t DeterministicBlockData::getNewEndIndex() const {
return newEndIndex;
void DeterministicBlockData::incrementMarker1() {
++valMarker1;
}
void DeterministicBlockData::decreaseNewEndIndex() {
--newEndIndex;
void DeterministicBlockData::decrementMarker1() {
--valMarker1;
}
void DeterministicBlockData::increaseNewEndIndex() {
++newEndIndex;
uint_fast64_t DeterministicBlockData::marker2() const {
return valMarker2;
}
void DeterministicBlockData::setMarker2(uint_fast64_t newMarker2) {
valMarker2 = newMarker2;
}
bool DeterministicBlockData::notify(Block<DeterministicBlockData> const& block) {
bool result = block.getBeginIndex() != this->newBeginIndex || block.getEndIndex() != this->newEndIndex;
this->newBeginIndex = block.getBeginIndex();
this->newEndIndex = block.getEndIndex();
void DeterministicBlockData::incrementMarker2() {
++valMarker2;
}
void DeterministicBlockData::decrementMarker2() {
--valMarker2;
}
bool DeterministicBlockData::resetMarkers(Block<DeterministicBlockData> const& block) {
bool result = block.getBeginIndex() != this->valMarker1 || block.getBeginIndex() != this->valMarker2;
this->valMarker1 = this->valMarker2 = block.getBeginIndex();
return result;
}
bool DeterministicBlockData::splitter() const {
return this->splitterFlag;
}
void DeterministicBlockData::setSplitter(bool value) {
this->splitterFlag = value;
}
void DeterministicBlockData::setAbsorbing(bool absorbing) {
this->absorbingFlag = absorbing;
}
bool DeterministicBlockData::absorbing() const {
return this->absorbingFlag;
}
void DeterministicBlockData::setRepresentativeState(storm::storage::sparse::state_type representativeState) {
this->valRepresentativeState = representativeState;
}
bool DeterministicBlockData::hasRepresentativeState() const {
return static_cast<bool>(valRepresentativeState);
}
storm::storage::sparse::state_type DeterministicBlockData::representativeState() const {
STORM_LOG_THROW(valRepresentativeState, storm::exceptions::InvalidOperationException, "Unable to retrieve representative state for block.");
return valRepresentativeState.get();
}
bool DeterministicBlockData::needsRefinement() const {
return needsRefinementFlag;
}
void DeterministicBlockData::setNeedsRefinement(bool value) {
needsRefinementFlag = value;
}
std::ostream& operator<<(std::ostream& out, DeterministicBlockData const& data) {
out << "m1: " << data.marker1() << ", m2: " << data.marker2();
return out;
}
}
}
}

92
src/storage/bisimulation/DeterministicBlockData.h

@ -11,52 +11,76 @@ namespace storm {
class DeterministicBlockData {
public:
DeterministicBlockData();
DeterministicBlockData(uint_fast64_t newBeginIndex, uint_fast64_t newEndIndex);
DeterministicBlockData(uint_fast64_t marker1, uint_fast64_t marker2);
/*!
* Retrieves the new begin index.
*
* @return The new begin index.
*/
uint_fast64_t getNewBeginIndex() const;
/*!
* Increases the new begin index by one.
*/
void increaseNewBeginIndex();
uint_fast64_t marker1() const;
void setMarker1(uint_fast64_t newMarker1);
void incrementMarker1();
void decrementMarker1();
/*!
* Retrieves the new end index.
*
* @return The new end index.
*/
uint_fast64_t getNewEndIndex() const;
/*!
* Decreases the new end index.
*/
void decreaseNewEndIndex();
/*!
* Increases the new end index.
*/
void increaseNewEndIndex();
uint_fast64_t marker2() const;
void setMarker2(uint_fast64_t newMarker2);
void incrementMarker2();
void decrementMarker2();
/*!
* This method needs to be called whenever the block was modified to notify the data of the change.
* This method needs to be called whenever the block was modified to reset the data of the change.
*
* @param block The block that this data belongs to.
* @return True iff the data changed as a consequence of notifying it.
*/
bool notify(Block<DeterministicBlockData> const& block);
bool resetMarkers(Block<DeterministicBlockData> const& block);
// Checks whether the block is marked as a splitter.
bool splitter() const;
// Marks the block as being a splitter.
void setSplitter(bool value = true);
// Retrieves whether the block is marked as a predecessor.
bool needsRefinement() const;
// 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);
// Retrieves whether the block is to be interpreted as absorbing.
bool absorbing() 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 representativeState() const;
friend std::ostream& operator<<(std::ostream& out, DeterministicBlockData const& data);
public:
// A marker that can be used to mark a the new beginning of the block.
uint_fast64_t newBeginIndex;
// Two markers that can be used for various purposes. Whenever the block is split, both the markers are
// set to the beginning index of the block.
uint_fast64_t valMarker1;
uint_fast64_t valMarker2;
// A flag that can be used for marking the block as being a splitter.
bool splitterFlag;
// A flag 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 absorbingFlag;
// A marker that can be used to mark a the new end of the block.
uint_fast64_t newEndIndex;
// 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> valRepresentativeState;
};
std::ostream& operator<<(std::ostream& out, DeterministicBlockData const& data);
}
}
}

203
src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp

@ -90,10 +90,10 @@ namespace storm {
// 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.
blockPtr->setAbsorbing(true);
blockPtr->data().setAbsorbing(true);
} else if (nondivergentStates.getNumberOfSetBits() == 0) {
// If there are only diverging states in the block, we need to make it absorbing.
blockPtr->setAbsorbing(true);
blockPtr->data().setAbsorbing(true);
}
}
}
@ -158,33 +158,50 @@ namespace storm {
// Depending on the actions we need to take, the block to refine changes, so we need to keep track of it.
Block<BlockDataType>* blockToRefineProbabilistically = block;
if (block->data().getNewBeginIndex() != block->getBeginIndex()) {
// If the new begin index has shifted to a non-trivial position, we need to split the block.
if (block->data().getNewBeginIndex() != block->getEndIndex()) {
auto result = this->partition.splitBlock(*block, block->data().getNewBeginIndex());
if (result.second) {
blockToRefineProbabilistically = block->getPreviousBlockPointer();
}
}
bool split = false;
// If the new begin index has shifted to a non-trivial position, we need to split the block.
if (block->getBeginIndex() != block->data().marker1() && block->getEndIndex() != block->data().marker1()) {
auto result = this->partition.splitBlock(*block, block->data().marker1());
STORM_LOG_ASSERT(result.second, "Expected to split block, but that did not happen.");
split = true;
blockToRefineProbabilistically = block->getPreviousBlockPointer();
}
split |= this->partition.splitBlock(*blockToRefineProbabilistically,
[this] (storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) {
return getProbabilityToSplitter(state1) < getProbabilityToSplitter(state2);
},
[&splitterQueue] (Block<BlockDataType>& block) {
splitterQueue.emplace_back(&block); block.data().setSplitter();
});
// If the predecessor block was split, we need to insert it into the splitter queue if it is not already
// marked as a splitter.
if (split && !blockToRefineProbabilistically->data().splitter()) {
splitterQueue.emplace_back(blockToRefineProbabilistically);
blockToRefineProbabilistically->data().setSplitter();
}
// If the block was *not* split, we need to reset the markers by notifying the data.
block->resetMarkers();
// Remember that we have refined the block.
block->setNeedsRefinement(false);
this->partition.check();
block->data().setNeedsRefinement(false);
}
}
template<typename ModelType>
bool DeterministicModelBisimulationDecomposition<ModelType>::possiblyNeedsRefinement(bisimulation::Block<BlockDataType> const& predecessorBlock) const {
return predecessorBlock.getNumberOfStates() <= 1 || predecessorBlock.isAbsorbing();
return predecessorBlock.getNumberOfStates() > 1 && !predecessorBlock.data().absorbing();
}
template<typename ModelType>
void DeterministicModelBisimulationDecomposition<ModelType>::increaseProbabilityToSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType> const& predecessorBlock, ValueType const& value) {
storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
// If the position of the state is between the new begin and end index, we have not yet seen this predecessor.
if (predecessorPosition >= predecessorBlock.data().getNewBeginIndex() && predecessorPosition < predecessorBlock.data().getNewEndIndex()) {
// If the position of the state is to the right of marker1, we have not seen it before.
if (predecessorPosition >= predecessorBlock.data().marker1()) {
// Then, we just set the value.
probabilitiesToCurrentSplitter[predecessor] = value;
} else {
@ -194,41 +211,54 @@ namespace storm {
}
template <typename ModelType>
void DeterministicModelBisimulationDecomposition<ModelType>::moveStateToNewBeginningOfBlock(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock) {
this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().getNewBeginIndex()));
predecessorBlock.data().increaseNewBeginIndex();
void DeterministicModelBisimulationDecomposition<ModelType>::moveStateToMarker1(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock) {
this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker1()));
predecessorBlock.data().incrementMarker1();
}
template <typename ModelType>
void DeterministicModelBisimulationDecomposition<ModelType>::moveStateToNewEndOfBlock(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock) {
this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().getNewEndIndex() - 1));
predecessorBlock.data().decreaseNewEndIndex();
void DeterministicModelBisimulationDecomposition<ModelType>::moveStateToMarker2(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock) {
this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker2()));
predecessorBlock.data().incrementMarker2();
}
template <typename ModelType>
void DeterministicModelBisimulationDecomposition<ModelType>::moveStateInSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter) {
void DeterministicModelBisimulationDecomposition<ModelType>::moveStateInSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter, uint_fast64_t& elementsToSkip) {
storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
// If the predecessor is one of the states for which we have already explored its predecessors, we can move
// it to the new beginning of the block like for any other block.
if (predecessorPosition <= currentPositionInSplitter) {
moveStateToNewBeginningOfBlock(predecessor, predecessorBlock);
// If the predecessors of the given predecessor were already explored, we can move it easily.
if (predecessorPosition <= currentPositionInSplitter + elementsToSkip) {
this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker1()));
predecessorBlock.data().incrementMarker1();
} else {
// Otherwise, we move it to the new end of the block in which we assemble all states that are predecessors
// of the splitter, but for which the predecessors still need to be explored.
moveStateToNewEndOfBlock(predecessor, predecessorBlock);
// Otherwise, we need to move the predecessor, but we need to make sure that we explore its
// predecessors later. We do this by moving it to a range at the beginning of the block that will hold
// all predecessors in the splitter whose predecessors have yet to be explored.
if (predecessorBlock.data().marker2() == predecessorBlock.data().marker1()) {
this->partition.swapStatesAtPositions(predecessorBlock.data().marker2(), predecessorPosition);
this->partition.swapStatesAtPositions(predecessorPosition, currentPositionInSplitter + elementsToSkip + 1);
} else {
this->partition.swapStatesAtPositions(predecessorBlock.data().marker2(), predecessorPosition);
this->partition.swapStatesAtPositions(predecessorPosition, predecessorBlock.data().marker1());
this->partition.swapStatesAtPositions(predecessorPosition, currentPositionInSplitter + elementsToSkip + 1);
}
// Since we had to move an already explored state to the right of the current position,
++elementsToSkip;
predecessorBlock.data().incrementMarker1();
predecessorBlock.data().incrementMarker2();
}
}
template <typename ModelType>
void DeterministicModelBisimulationDecomposition<ModelType>::explorePredecessorsOfNewEndOfSplitter(bisimulation::Block<BlockDataType>& splitter) {
for (auto splitterIt = this->partition.begin() + splitter.data().getNewEndIndex(), splitterIte = this->partition.end(splitter); splitterIt != splitterIte; ++splitterIt) {
void DeterministicModelBisimulationDecomposition<ModelType>::exploreRemainingStatesOfSplitter(bisimulation::Block<BlockDataType>& splitter) {
for (auto splitterIt = this->partition.begin(splitter), splitterIte = this->partition.begin(splitter) + (splitter.data().marker2() - splitter.getBeginIndex()); 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<BlockDataType>& predecessorBlock = this->partition.getBlock(predecessor);
// If the block does not need to be refined, we skip it.
if (!possiblyNeedsRefinement(predecessorBlock)) {
continue;
@ -236,84 +266,91 @@ namespace storm {
// We keep track of the probability of the predecessor moving to the splitter.
increaseProbabilityToSplitter(predecessor, predecessorBlock, predecessorEntry.getValue());
if (predecessorBlock != splitter) {
moveStateToNewBeginningOfBlock(predecessor, predecessorBlock);
} else {
storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
// In this case, we must only move the predecessor its predecessors were already explored.
// If we have not yet explored its predecessors, it has to be to the right of the currently
// considered state and will be transferred to the beginning of the block anyway.
if (predecessorPosition < splitter.data().getNewEndIndex()) {
moveStateToNewBeginningOfBlock(predecessor, predecessorBlock);
}
// Only move the state if it has not been seen as a predecessor before.
storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
if (predecessorPosition >= predecessorBlock.data().marker1()) {
moveStateToMarker1(predecessor, predecessorBlock);
}
}
// Now that we have explored its predecessors and know that the current state is itself a predecessor of
// the splitter, we can safely move it to the beginning of the block.
moveStateToNewBeginningOfBlock(currentState, splitter);
splitter.data().increaseNewEndIndex();
}
// Finally, we can reset the second marker.
splitter.data().setMarker2(splitter.getBeginIndex());
}
template<typename ModelType>
void DeterministicModelBisimulationDecomposition<ModelType>::refinePartitionBasedOnSplitter(bisimulation::Block<BlockDataType>& splitter, std::deque<bisimulation::Block<BlockDataType>*>& splitterQueue) {
// The outline of the refinement is as follows.
//
// (0) we prepare the environment for the splitting process.
// 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 so that after the iteration
// process we have the probabilities of all predecessors of the splitter of entering the splitter in one
// step. To directly separate the states having a transition into the splitter from the ones who do not,
// we move the states to certain locations. That is, on encountering a predecessor of the splitter, it is
// moved to the beginning of its block. If the predecessor is in the splitter itself, we have to be a bit
// careful about where to move states.
//
// (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.
// After this iteration, there may be states of the splitter whose predecessors have not yet been explored,
// so this needs to be done now.
//
// Finally, we use the information obtained in the first part for the actual splitting process in which all
// predecessor blocks of the splitter are split based on the probabilities computed earlier.
// (0)
// (1)
std::list<Block<BlockDataType>*> predecessorBlocks;
// (1)
// (2)
storm::storage::sparse::state_type currentPosition = splitter.getBeginIndex();
bool splitterIsItsOwnPredecessor = false;
for (auto splitterIt = this->partition.begin(splitter), splitterIte = this->partition.end(splitter); splitterIt != splitterIte && currentPosition < splitter.data().getNewEndIndex(); ++splitterIt, ++currentPosition) {
bool splitterIsPredecessorBlock = false;
for (auto splitterIt = this->partition.begin(splitter), splitterIte = this->partition.end(splitter); splitterIt != splitterIte; ++splitterIt, ++currentPosition) {
storm::storage::sparse::state_type currentState = *splitterIt;
uint_fast64_t elementsToSkip = 0;
for (auto const& predecessorEntry : this->backwardTransitions.getRow(currentState)) {
storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn();
storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
Block<BlockDataType>& predecessorBlock = this->partition.getBlock(predecessor);
// If the block does not need to be refined, we skip it.
if (!possiblyNeedsRefinement(predecessorBlock)) {
continue;
}
// We keep track of the probability of the predecessor moving to the splitter.
increaseProbabilityToSplitter(predecessor, predecessorBlock, predecessorEntry.getValue());
if (predecessorBlock != splitter) {
moveStateToNewBeginningOfBlock(predecessor, predecessorBlock);
} else {
splitterIsItsOwnPredecessor = true;
moveStateInSplitter(predecessor, predecessorBlock, currentPosition);
}
// Insert the block into the list of blocks to refine (if that has not already happened).
if (!predecessorBlock.needsRefinement()) {
predecessorBlocks.emplace_back(&predecessorBlock);
predecessorBlock.setNeedsRefinement();
// We only need to move the predecessor if its not already known as a predecessor already.
if (predecessorPosition >= predecessorBlock.data().marker1()) {
// If the predecessor block is not the splitter, we can move the state easily.
if (predecessorBlock != splitter) {
moveStateToMarker1(predecessor, predecessorBlock);
} else {
// If the predecessor is in the splitter, we need to be a bit more careful.
splitterIsPredecessorBlock = true;
moveStateInSplitter(predecessor, predecessorBlock, currentPosition, elementsToSkip);
}
// Insert the block into the list of blocks to refine (if that has not already happened).
if (!predecessorBlock.data().needsRefinement()) {
predecessorBlocks.emplace_back(&predecessorBlock);
predecessorBlock.data().setNeedsRefinement();
}
}
}
// If, as a consequence of shifting states, we need to skip some elements, do so now.
splitterIt += elementsToSkip;
currentPosition += elementsToSkip;
}
// If the splitter is its own predecessor block, we need to treat the states at the end of the block.
if (splitterIsItsOwnPredecessor) {
explorePredecessorsOfNewEndOfSplitter(splitter);
// If the splitter was a predecessor block of itself, we potentially need to explore some states that have
// not been explored yet.
if (splitterIsPredecessorBlock) {
exploreRemainingStatesOfSplitter(splitter);
}
// std::cout << "probs of splitter predecessors: " << std::endl;
// for (auto state : predecessorsOfCurrentSplitter) {
// std::cout << state << " [" << this->partition.getBlock(state).getId() << "]" << " -> " << probabilitiesToCurrentSplitter[state] << std::endl;
// }
// Finally, we split the block based on the precomputed probabilities and the chosen bisimulation type.
if (this->options.type == BisimulationType::Strong || this->model.getType() == storm::models::ModelType::Ctmc) {
refinePredecessorBlocksOfSplitter(predecessorBlocks, splitterQueue);
} else {
@ -368,12 +405,12 @@ namespace storm {
Block<BlockDataType> const& oldBlock = this->partition.getBlock(representativeState);
// If the block is absorbing, we simply add a self-loop.
if (oldBlock.isAbsorbing()) {
if (oldBlock.data().absorbing()) {
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();
if (oldBlock.data().hasRepresentativeState()) {
representativeState = oldBlock.data().representativeState();
}
// Add all of the selected atomic propositions that hold in the representative state to the state

17
src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h

@ -72,21 +72,20 @@ namespace storm {
// Retrieves whether the given predecessor of the splitters possibly needs refinement.
bool possiblyNeedsRefinement(bisimulation::Block<BlockDataType> const& predecessorBlock) const;
// Moves the given state to the new begin index of the given block and increases the new begin.
void moveStateToNewBeginningOfBlock(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock);
// Moves the given state to the position marked by marker1 moves the marker one step further.
void moveStateToMarker1(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock);
// Moves the given state to the new end index of the given block and decreases the new end.
void moveStateToNewEndOfBlock(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock);
// Moves the given state to the position marked by marker2 the marker one step further.
void moveStateToMarker2(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock);
// Moves the given state to the new begin or new end of the block, depending on where the predecessor is located.
void moveStateInSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter);
// Moves the given state to a proper place in the splitter, depending on where the predecessor is located.
void moveStateInSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter, uint_fast64_t& elementsToSkip);
// Increases the probability of moving to the current splitter for the given state.
void increaseProbabilityToSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType> const& predecessorBlock, ValueType const& value);
// Explores the predecessors of the states that were identified as predecessors themselves that have not yet
// been explored.
void explorePredecessorsOfNewEndOfSplitter(bisimulation::Block<BlockDataType>& splitter);
// Explores the remaining predecessors of the splitter.
void exploreRemainingStatesOfSplitter(bisimulation::Block<BlockDataType>& splitter);
// 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.

33
src/storage/bisimulation/Partition.cpp

@ -38,13 +38,12 @@ namespace storm {
stateToBlockMapping[state] = firstBlock;
++position;
}
firstBlock->setAbsorbing(true);
firstBlock->markAsSplitter();
firstBlock->data().setAbsorbing(true);
}
if (!prob1States.empty()) {
blocks.emplace_back(new Block<DataType>(position, position + prob1States.getNumberOfSetBits(), firstBlock, nullptr, blocks.size()));
secondBlock = blocks[1].get();
secondBlock = blocks.back().get();
for (auto state : prob1States) {
states[position] = state;
@ -52,15 +51,14 @@ namespace storm {
stateToBlockMapping[state] = secondBlock;
++position;
}
secondBlock->setAbsorbing(true);
secondBlock->setRepresentativeState(representativeProb1State.get());
secondBlock->markAsSplitter();
secondBlock->data().setAbsorbing(true);
secondBlock->data().setRepresentativeState(representativeProb1State.get());
}
storm::storage::BitVector otherStates = ~(prob0States | prob1States);
if (!otherStates.empty()) {
blocks.emplace_back(new Block<DataType>(position, numberOfStates, secondBlock, nullptr, blocks.size()));
thirdBlock = blocks[2].get();
thirdBlock = blocks.back().get();
for (auto state : otherStates) {
states[position] = state;
@ -68,7 +66,6 @@ namespace storm {
stateToBlockMapping[state] = thirdBlock;
++position;
}
thirdBlock->markAsSplitter();
}
}
@ -82,7 +79,6 @@ namespace storm {
void Partition<DataType>::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;
@ -180,8 +176,6 @@ namespace storm {
std::pair<typename std::vector<std::unique_ptr<Block<DataType>>>::iterator, bool> Partition<DataType>::splitBlock(Block<DataType>& 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()) {
@ -195,13 +189,8 @@ namespace storm {
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();
(*newBlockIt)->markAsSplitter();
// Update the mapping of the states in the newly created block.
this->mapStatesToBlock(**newBlockIt, this->begin(**newBlockIt), this->end(**newBlockIt));
@ -210,23 +199,11 @@ namespace storm {
template<typename DataType>
bool Partition<DataType>::splitBlock(Block<DataType>& block, std::function<bool (storm::storage::sparse::state_type, storm::storage::sparse::state_type)> const& less, std::function<void (Block<DataType>&)> 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), 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.

3
src/storage/bisimulation/Partition.h

@ -135,11 +135,10 @@ namespace storm {
// Update the state to position for the states in the given block.
void mapStatesToPositions(Block<DataType> 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);
private:
// 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.

4
test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp

@ -21,9 +21,9 @@ TEST(DeterministicModelBisimulationDecomposition, Die) {
EXPECT_EQ(20ul, result->getNumberOfTransitions());
#ifdef WINDOWS
storm::storage::BisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options;
storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options;
#else
typename storm::storage::BisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options;
typename storm::storage::DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>::Options options;
#endif
options.respectedAtomicPropositions = std::set<std::string>({"one"});
Loading…
Cancel
Save