From 96954ddd15707f2b4f253f4c5bfae2484c068b70 Mon Sep 17 00:00:00 2001 From: dehnert Date: Sat, 24 Oct 2015 23:00:47 +0200 Subject: [PATCH 01/24] refactoring of bisimulation class in the prospect of extending it to (CT)MDPs, not yet done Former-commit-id: 09f47ad97709ca158a2dbcaa45e196867948bedd --- src/CMakeLists.txt | 2 + ...ministicModelBisimulationDecomposition.cpp | 1538 ----------------- ...erministicModelBisimulationDecomposition.h | 590 ------- .../BisimulationDecomposition.cpp | 259 +++ .../bisimulation/BisimulationDecomposition.h | 222 +++ src/storage/bisimulation/Block.cpp | 132 ++ src/storage/bisimulation/Block.h | 132 ++ ...ministicModelBisimulationDecomposition.cpp | 681 ++++++++ ...erministicModelBisimulationDecomposition.h | 142 ++ src/storage/bisimulation/Partition.cpp | 273 +++ src/storage/bisimulation/Partition.h | 142 ++ src/storage/expressions/Expression.cpp | 6 + src/storage/expressions/Expression.h | 7 + src/utility/storm.h | 2 +- ...sticModelBisimulationDecompositionTest.cpp | 2 +- 15 files changed, 2000 insertions(+), 2130 deletions(-) delete mode 100644 src/storage/DeterministicModelBisimulationDecomposition.cpp delete mode 100644 src/storage/DeterministicModelBisimulationDecomposition.h create mode 100644 src/storage/bisimulation/BisimulationDecomposition.cpp create mode 100644 src/storage/bisimulation/BisimulationDecomposition.h create mode 100644 src/storage/bisimulation/Block.cpp create mode 100644 src/storage/bisimulation/Block.h create mode 100644 src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp create mode 100644 src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h create mode 100644 src/storage/bisimulation/Partition.cpp create mode 100644 src/storage/bisimulation/Partition.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 88e0a396f..05d262b65 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,7 @@ file(GLOB STORM_SETTINGS_FILES ${PROJECT_SOURCE_DIR}/src/settings/*.h ${PROJECT_ file(GLOB STORM_SETTINGS_MODULES_FILES ${PROJECT_SOURCE_DIR}/src/settings/modules/*.h ${PROJECT_SOURCE_DIR}/src/settings/modules/*.cpp) file(GLOB_RECURSE STORM_SOLVER_FILES ${PROJECT_SOURCE_DIR}/src/solver/*.h ${PROJECT_SOURCE_DIR}/src/solver/*.cpp) file(GLOB STORM_STORAGE_FILES ${PROJECT_SOURCE_DIR}/src/storage/*.h ${PROJECT_SOURCE_DIR}/src/storage/*.cpp) +file(GLOB STORM_STORAGE_BISIMULATION_FILES ${PROJECT_SOURCE_DIR}/src/storage/bisimulation/*.h ${PROJECT_SOURCE_DIR}/src/storage/bisimulation/*.cpp) file(GLOB_RECURSE STORM_STORAGE_DD_FILES ${PROJECT_SOURCE_DIR}/src/storage/dd/*.h ${PROJECT_SOURCE_DIR}/src/storage/dd/*.cpp) file(GLOB_RECURSE STORM_STORAGE_EXPRESSIONS_FILES ${PROJECT_SOURCE_DIR}/src/storage/expressions/*.h ${PROJECT_SOURCE_DIR}/src/storage/expressions/*.cpp) file(GLOB_RECURSE STORM_STORAGE_PRISM_FILES ${PROJECT_SOURCE_DIR}/src/storage/prism/*.h ${PROJECT_SOURCE_DIR}/src/storage/prism/*.cpp) @@ -77,6 +78,7 @@ source_group(settings FILES ${STORM_SETTINGS_FILES}) source_group(settings\\modules FILES ${STORM_SETTINGS_MODULES_FILES}) source_group(solver FILES ${STORM_SOLVER_FILES}) source_group(storage FILES ${STORM_STORAGE_FILES}) +source_group(storage\\bisimulation FILES ${STORM_STORAGE_BISIMULATION_FILES}) source_group(storage\\dd FILES ${STORM_STORAGE_DD_FILES}) source_group(storage\\expressions FILES ${STORM_STORAGE_EXPRESSIONS_FILES}) source_group(storage\\prism FILES ${STORM_STORAGE_PRISM_FILES}) diff --git a/src/storage/DeterministicModelBisimulationDecomposition.cpp b/src/storage/DeterministicModelBisimulationDecomposition.cpp deleted file mode 100644 index b861f8f2c..000000000 --- a/src/storage/DeterministicModelBisimulationDecomposition.cpp +++ /dev/null @@ -1,1538 +0,0 @@ -#include "src/storage/DeterministicModelBisimulationDecomposition.h" - -#include -#include -#include -#include -#include - -#include "src/adapters/CarlAdapter.h" -#include "src/modelchecker/propositional/SparsePropositionalModelChecker.h" -#include "src/modelchecker/results/ExplicitQualitativeCheckResult.h" - -#include "src/models/sparse/StandardRewardModel.h" - -#include "src/utility/graph.h" -#include "src/utility/constants.h" -#include "src/utility/ConstantsComparator.h" -#include "src/exceptions/IllegalFunctionCallException.h" -#include "src/exceptions/InvalidOptionException.h" -#include "src/exceptions/InvalidArgumentException.h" - -#include "src/settings/SettingsManager.h" -#include "src/settings/modules/GeneralSettings.h" - -namespace storm { - namespace storage { - - template - DeterministicModelBisimulationDecomposition::Block::Block(storm::storage::sparse::state_type begin, storm::storage::sparse::state_type end, Block* prev, Block* next, std::size_t id) : next(next), prev(prev), begin(begin), end(end), markedAsSplitter(false), markedAsPredecessorBlock(false), markedPosition(begin), absorbing(false), id(id) { - if (next != nullptr) { - next->prev = this; - } - if (prev != nullptr) { - prev->next = this; - } - STORM_LOG_ASSERT(begin < end, "Unable to create block of illegal size."); - } - - template - void DeterministicModelBisimulationDecomposition::Block::print(Partition const& partition) const { - std::cout << "block " << this->getId() << " with marked position " << this->getMarkedPosition() << " and original begin " << this->getOriginalBegin() << std::endl; - std::cout << "begin: " << this->begin << " and end: " << this->end << " (number of states: " << this->getNumberOfStates() << ")" << std::endl; - std::cout << "states:" << std::endl; - for (storm::storage::sparse::state_type index = this->begin; index < this->end; ++index) { - std::cout << partition.statesAndValues[index].first << " "; - } - std::cout << std::endl << "original: " << std::endl; - for (storm::storage::sparse::state_type index = this->getOriginalBegin(); index < this->end; ++index) { - std::cout << partition.statesAndValues[index].first << " "; - } - std::cout << std::endl << "values:" << std::endl; - for (storm::storage::sparse::state_type index = this->begin; index < this->end; ++index) { - std::cout << std::setprecision(3) << partition.statesAndValues[index].second << " "; - } - if (partition.keepSilentProbabilities) { - std::cout << std::endl << "silent:" << std::endl; - for (storm::storage::sparse::state_type index = this->begin; index < this->end; ++index) { - std::cout << std::setprecision(3) << partition.silentProbabilities[partition.statesAndValues[index].first] << " "; - } - } - std::cout << std::endl; - } - - template - void DeterministicModelBisimulationDecomposition::Block::setBegin(storm::storage::sparse::state_type begin) { - this->begin = begin; - this->markedPosition = begin; - STORM_LOG_ASSERT(begin < end, "Unable to resize block to illegal size."); - } - - template - void DeterministicModelBisimulationDecomposition::Block::setEnd(storm::storage::sparse::state_type end) { - this->end = end; - STORM_LOG_ASSERT(begin < end, "Unable to resize block to illegal size."); - } - - template - void DeterministicModelBisimulationDecomposition::Block::incrementBegin() { - ++this->begin; - STORM_LOG_ASSERT(begin <= end, "Unable to resize block to illegal size."); - } - - template - storm::storage::sparse::state_type DeterministicModelBisimulationDecomposition::Block::getBegin() const { - return this->begin; - } - - template - storm::storage::sparse::state_type DeterministicModelBisimulationDecomposition::Block::getOriginalBegin() const { - if (this->hasPreviousBlock()) { - return this->getPreviousBlock().getEnd(); - } else { - return 0; - } - } - - template - storm::storage::sparse::state_type DeterministicModelBisimulationDecomposition::Block::getEnd() const { - return this->end; - } - - template - void DeterministicModelBisimulationDecomposition::Block::setIterator(iterator it) { - this->selfIt = it; - } - - template - typename DeterministicModelBisimulationDecomposition::Block::iterator DeterministicModelBisimulationDecomposition::Block::getIterator() const { - return this->selfIt; - } - - template - typename DeterministicModelBisimulationDecomposition::Block::iterator DeterministicModelBisimulationDecomposition::Block::getNextIterator() const { - return this->getNextBlock().getIterator(); - } - - template - typename DeterministicModelBisimulationDecomposition::Block::iterator DeterministicModelBisimulationDecomposition::Block::getPreviousIterator() const { - return this->getPreviousBlock().getIterator(); - } - - template - typename DeterministicModelBisimulationDecomposition::Block& DeterministicModelBisimulationDecomposition::Block::getNextBlock() { - return *this->next; - } - - template - typename DeterministicModelBisimulationDecomposition::Block const& DeterministicModelBisimulationDecomposition::Block::getNextBlock() const { - return *this->next; - } - - template - bool DeterministicModelBisimulationDecomposition::Block::hasNextBlock() const { - return this->next != nullptr; - } - - template - typename DeterministicModelBisimulationDecomposition::Block& DeterministicModelBisimulationDecomposition::Block::getPreviousBlock() { - return *this->prev; - } - - template - typename DeterministicModelBisimulationDecomposition::Block* DeterministicModelBisimulationDecomposition::Block::getPreviousBlockPointer() { - return this->prev; - } - - template - typename DeterministicModelBisimulationDecomposition::Block* DeterministicModelBisimulationDecomposition::Block::getNextBlockPointer() { - return this->next; - } - - template - typename DeterministicModelBisimulationDecomposition::Block const& DeterministicModelBisimulationDecomposition::Block::getPreviousBlock() const { - return *this->prev; - } - - template - bool DeterministicModelBisimulationDecomposition::Block::hasPreviousBlock() const { - return this->prev != nullptr; - } - - template - bool DeterministicModelBisimulationDecomposition::Block::check() const { - assert(this->begin < this->end); - assert(this->prev == nullptr || this->prev->next == this); - assert(this->next == nullptr || this->next->prev == this); - return true; - } - - template - std::size_t DeterministicModelBisimulationDecomposition::Block::getNumberOfStates() const { - // We need to take the original begin here, because the begin is temporarily moved. - return (this->end - this->getOriginalBegin()); - } - - template - bool DeterministicModelBisimulationDecomposition::Block::isMarkedAsSplitter() const { - return this->markedAsSplitter; - } - - template - void DeterministicModelBisimulationDecomposition::Block::markAsSplitter() { - this->markedAsSplitter = true; - } - - template - void DeterministicModelBisimulationDecomposition::Block::unmarkAsSplitter() { - this->markedAsSplitter = false; - } - - template - std::size_t DeterministicModelBisimulationDecomposition::Block::getId() const { - return this->id; - } - - template - void DeterministicModelBisimulationDecomposition::Block::setAbsorbing(bool absorbing) { - this->absorbing = absorbing; - } - - template - bool DeterministicModelBisimulationDecomposition::Block::isAbsorbing() const { - return this->absorbing; - } - - template - void DeterministicModelBisimulationDecomposition::Block::setRepresentativeState(storm::storage::sparse::state_type representativeState) { - this->representativeState = representativeState; - } - - template - bool DeterministicModelBisimulationDecomposition::Block::hasRepresentativeState() const { - return static_cast(representativeState); - } - - template - storm::storage::sparse::state_type DeterministicModelBisimulationDecomposition::Block::getRepresentativeState() const { - STORM_LOG_THROW(representativeState, storm::exceptions::InvalidOperationException, "Unable to retrieve representative state for block."); - return representativeState.get(); - } - - template - storm::storage::sparse::state_type DeterministicModelBisimulationDecomposition::Block::getMarkedPosition() const { - return this->markedPosition; - } - - template - void DeterministicModelBisimulationDecomposition::Block::setMarkedPosition(storm::storage::sparse::state_type position) { - this->markedPosition = position; - } - - template - void DeterministicModelBisimulationDecomposition::Block::resetMarkedPosition() { - this->markedPosition = this->begin; - } - - template - void DeterministicModelBisimulationDecomposition::Block::incrementMarkedPosition() { - ++this->markedPosition; - } - - template - void DeterministicModelBisimulationDecomposition::Block::markAsPredecessorBlock() { - this->markedAsPredecessorBlock = true; - } - - template - void DeterministicModelBisimulationDecomposition::Block::unmarkAsPredecessorBlock() { - this->markedAsPredecessorBlock = false; - } - - template - bool DeterministicModelBisimulationDecomposition::Block::isMarkedAsPredecessor() const { - return markedAsPredecessorBlock; - } - - template - DeterministicModelBisimulationDecomposition::Partition::Partition(std::size_t numberOfStates, bool keepSilentProbabilities) : numberOfBlocks(0), stateToBlockMapping(numberOfStates), statesAndValues(numberOfStates), positions(numberOfStates), keepSilentProbabilities(keepSilentProbabilities), silentProbabilities() { - // Create the block and give it an iterator to itself. - typename std::list::iterator it = blocks.emplace(this->blocks.end(), 0, numberOfStates, nullptr, nullptr, numberOfBlocks++); - it->setIterator(it); - - // Set up the different parts of the internal structure. - for (storm::storage::sparse::state_type state = 0; state < numberOfStates; ++state) { - statesAndValues[state] = std::make_pair(state, storm::utility::zero()); - positions[state] = state; - stateToBlockMapping[state] = &blocks.back(); - } - - // If we are requested to store silent probabilities, we need to prepare the vector. - if (this->keepSilentProbabilities) { - silentProbabilities = std::vector(numberOfStates); - } - } - - template - DeterministicModelBisimulationDecomposition::Partition::Partition(std::size_t numberOfStates, storm::storage::BitVector const& prob0States, storm::storage::BitVector const& prob1States, boost::optional representativeProb1State, bool keepSilentProbabilities) : numberOfBlocks(0), stateToBlockMapping(numberOfStates), statesAndValues(numberOfStates), positions(numberOfStates), keepSilentProbabilities(keepSilentProbabilities), silentProbabilities() { - storm::storage::sparse::state_type position = 0; - Block* firstBlock = nullptr; - Block* secondBlock = nullptr; - Block* thirdBlock = nullptr; - if (!prob0States.empty()) { - typename std::list::iterator firstIt = blocks.emplace(this->blocks.end(), 0, prob0States.getNumberOfSetBits(), nullptr, nullptr, numberOfBlocks++); - firstBlock = &(*firstIt); - firstBlock->setIterator(firstIt); - - for (auto state : prob0States) { - statesAndValues[position] = std::make_pair(state, storm::utility::zero()); - positions[state] = position; - stateToBlockMapping[state] = firstBlock; - ++position; - } - firstBlock->setAbsorbing(true); - } - - if (!prob1States.empty()) { - typename std::list::iterator secondIt = blocks.emplace(this->blocks.end(), position, position + prob1States.getNumberOfSetBits(), firstBlock, nullptr, numberOfBlocks++); - secondBlock = &(*secondIt); - secondBlock->setIterator(secondIt); - - for (auto state : prob1States) { - statesAndValues[position] = std::make_pair(state, storm::utility::zero()); - positions[state] = position; - stateToBlockMapping[state] = secondBlock; - ++position; - } - secondBlock->setAbsorbing(true); - secondBlock->setRepresentativeState(representativeProb1State.get()); - } - - storm::storage::BitVector otherStates = ~(prob0States | prob1States); - if (!otherStates.empty()) { - typename std::list::iterator thirdIt = blocks.emplace(this->blocks.end(), position, numberOfStates, secondBlock, nullptr, numberOfBlocks++); - thirdBlock = &(*thirdIt); - thirdBlock->setIterator(thirdIt); - - for (auto state : otherStates) { - statesAndValues[position] = std::make_pair(state, storm::utility::zero()); - positions[state] = position; - stateToBlockMapping[state] = thirdBlock; - ++position; - } - } - - // If we are requested to store silent probabilities, we need to prepare the vector. - if (this->keepSilentProbabilities) { - silentProbabilities = std::vector(numberOfStates); - } - } - - template - void DeterministicModelBisimulationDecomposition::Partition::swapStates(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { - std::swap(this->statesAndValues[this->positions[state1]], this->statesAndValues[this->positions[state2]]); - std::swap(this->positions[state1], this->positions[state2]); - } - - template - void DeterministicModelBisimulationDecomposition::Partition::swapStatesAtPositions(storm::storage::sparse::state_type position1, storm::storage::sparse::state_type position2) { - storm::storage::sparse::state_type state1 = this->statesAndValues[position1].first; - storm::storage::sparse::state_type state2 = this->statesAndValues[position2].first; - - std::swap(this->statesAndValues[position1], this->statesAndValues[position2]); - - this->positions[state1] = position2; - this->positions[state2] = position1; - } - - template - storm::storage::sparse::state_type const& DeterministicModelBisimulationDecomposition::Partition::getPosition(storm::storage::sparse::state_type state) const { - return this->positions[state]; - } - - template - void DeterministicModelBisimulationDecomposition::Partition::setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position) { - this->positions[state] = position; - } - - template - storm::storage::sparse::state_type const& DeterministicModelBisimulationDecomposition::Partition::getState(storm::storage::sparse::state_type position) const { - return this->statesAndValues[position].first; - } - - template - ValueType const& DeterministicModelBisimulationDecomposition::Partition::getValue(storm::storage::sparse::state_type state) const { - return this->statesAndValues[this->positions[state]].second; - } - - template - ValueType const& DeterministicModelBisimulationDecomposition::Partition::getValueAtPosition(storm::storage::sparse::state_type position) const { - return this->statesAndValues[position].second; - } - - template - void DeterministicModelBisimulationDecomposition::Partition::setValue(storm::storage::sparse::state_type state, ValueType value) { - this->statesAndValues[this->positions[state]].second = value; - } - - template - void DeterministicModelBisimulationDecomposition::Partition::increaseValue(storm::storage::sparse::state_type state, ValueType value) { - this->statesAndValues[this->positions[state]].second += value; - } - - template - void DeterministicModelBisimulationDecomposition::Partition::updateBlockMapping(Block& block, typename std::vector>::iterator first, typename std::vector>::iterator last) { - for (; first != last; ++first) { - this->stateToBlockMapping[first->first] = █ - } - } - - template - typename DeterministicModelBisimulationDecomposition::Block& DeterministicModelBisimulationDecomposition::Partition::getFirstBlock() { - return *this->blocks.begin(); - } - - template - typename DeterministicModelBisimulationDecomposition::Block& DeterministicModelBisimulationDecomposition::Partition::getBlock(storm::storage::sparse::state_type state) { - return *this->stateToBlockMapping[state]; - } - - template - typename DeterministicModelBisimulationDecomposition::Block const& DeterministicModelBisimulationDecomposition::Partition::getBlock(storm::storage::sparse::state_type state) const { - return *this->stateToBlockMapping[state]; - } - - template - typename std::vector>::iterator DeterministicModelBisimulationDecomposition::Partition::getBegin(Block const& block) { - return this->statesAndValues.begin() + block.getBegin(); - } - - template - typename std::vector>::iterator DeterministicModelBisimulationDecomposition::Partition::getEnd(Block const& block) { - return this->statesAndValues.begin() + block.getEnd(); - } - - template - typename std::vector>::const_iterator DeterministicModelBisimulationDecomposition::Partition::getBegin(Block const& block) const { - return this->statesAndValues.begin() + block.getBegin(); - } - - template - typename std::vector>::const_iterator DeterministicModelBisimulationDecomposition::Partition::getEnd(Block const& block) const { - return this->statesAndValues.begin() + block.getEnd(); - } - - template - typename DeterministicModelBisimulationDecomposition::Block& DeterministicModelBisimulationDecomposition::Partition::splitBlock(Block& block, storm::storage::sparse::state_type position) { - // In case one of the resulting blocks would be empty, we simply return the current block and do not create - // a new one. - if (position == block.getBegin() || position == block.getEnd()) { - return block; - } - - // Actually create the new block and insert it at the correct position. - typename std::list::iterator selfIt = this->blocks.emplace(block.getIterator(), block.getBegin(), position, block.getPreviousBlockPointer(), &block, numberOfBlocks++); - selfIt->setIterator(selfIt); - Block& newBlock = *selfIt; - - // Resize the current block appropriately. - block.setBegin(position); - - // Update the mapping of the states in the newly created block. - this->updateBlockMapping(newBlock, this->getBegin(newBlock), this->getEnd(newBlock)); - - return newBlock; - } - - template - typename DeterministicModelBisimulationDecomposition::Block& DeterministicModelBisimulationDecomposition::Partition::insertBlock(Block& block) { - // Find the beginning of the new block. - storm::storage::sparse::state_type begin; - if (block.hasPreviousBlock()) { - begin = block.getPreviousBlock().getEnd(); - } else { - begin = 0; - } - - // Actually insert the new block. - typename std::list::iterator it = this->blocks.emplace(block.getIterator(), begin, block.getBegin(), block.getPreviousBlockPointer(), &block, numberOfBlocks++); - Block& newBlock = *it; - newBlock.setIterator(it); - - // Update the mapping of the states in the newly created block. - for (auto it = this->getBegin(newBlock), ite = this->getEnd(newBlock); it != ite; ++it) { - stateToBlockMapping[it->first] = &newBlock; - } - - return *it; - } - - template - void DeterministicModelBisimulationDecomposition::Partition::splitLabel(storm::storage::BitVector const& statesWithLabel) { - for (auto blockIterator = this->blocks.begin(), ite = this->blocks.end(); blockIterator != ite; ) { // The update of the loop was intentionally moved to the bottom of the loop. - Block& block = *blockIterator; - - // Sort the range of the block such that all states that have the label are moved to the front. - std::sort(this->getBegin(block), this->getEnd(block), [&statesWithLabel] (std::pair const& a, std::pair const& b) { return statesWithLabel.get(a.first) && !statesWithLabel.get(b.first); } ); - - // Update the positions vector. - storm::storage::sparse::state_type position = block.getBegin(); - for (auto stateIt = this->getBegin(block), stateIte = this->getEnd(block); stateIt != stateIte; ++stateIt, ++position) { - this->positions[stateIt->first] = position; - } - - // Now we can find the first position in the block that does not have the label and create new blocks. - typename std::vector>::iterator it = std::find_if(this->getBegin(block), this->getEnd(block), [&] (std::pair const& a) { return !statesWithLabel.get(a.first); }); - - // If not all the states agreed on the validity of the label, we need to split the block. - if (it != this->getBegin(block) && it != this->getEnd(block)) { - auto cutPoint = std::distance(this->statesAndValues.begin(), it); - this->splitBlock(block, cutPoint); - } else { - // Otherwise, we simply proceed to the next block. - ++blockIterator; - } - } - } - - template - bool DeterministicModelBisimulationDecomposition::Partition::isSilent(storm::storage::sparse::state_type state, storm::utility::ConstantsComparator const& comparator) const { - STORM_LOG_ASSERT(this->keepSilentProbabilities, "Unable to retrieve silentness of state, because silent probabilities are not tracked."); - return comparator.isOne(this->silentProbabilities[state]); - } - - template - bool DeterministicModelBisimulationDecomposition::Partition::hasSilentProbability(storm::storage::sparse::state_type state, storm::utility::ConstantsComparator const& comparator) const { - STORM_LOG_ASSERT(this->keepSilentProbabilities, "Unable to retrieve silentness of state, because silent probabilities are not tracked."); - return !comparator.isZero(this->silentProbabilities[state]); - } - - template - ValueType const& DeterministicModelBisimulationDecomposition::Partition::getSilentProbability(storm::storage::sparse::state_type state) const { - STORM_LOG_ASSERT(this->keepSilentProbabilities, "Unable to retrieve silent probability of state, because silent probabilities are not tracked."); - return this->silentProbabilities[state]; - } - - template - void DeterministicModelBisimulationDecomposition::Partition::setSilentProbabilities(typename std::vector>::iterator first, typename std::vector>::iterator last) { - STORM_LOG_ASSERT(this->keepSilentProbabilities, "Unable to set silent probability of state, because silent probabilities are not tracked."); - for (; first != last; ++first) { - this->silentProbabilities[first->first] = first->second; - } - } - - template - void DeterministicModelBisimulationDecomposition::Partition::setSilentProbabilitiesToZero(typename std::vector>::iterator first, typename std::vector>::iterator last) { - STORM_LOG_ASSERT(this->keepSilentProbabilities, "Unable to set silent probability of state, because silent probabilities are not tracked."); - for (; first != last; ++first) { - this->silentProbabilities[first->first] = storm::utility::zero(); - } - } - - template - void DeterministicModelBisimulationDecomposition::Partition::setSilentProbability(storm::storage::sparse::state_type state, ValueType const& value) { - STORM_LOG_ASSERT(this->keepSilentProbabilities, "Unable to set silent probability of state, because silent probabilities are not tracked."); - this->silentProbabilities[state] = value; - } - - template - std::list::Block> const& DeterministicModelBisimulationDecomposition::Partition::getBlocks() const { - return this->blocks; - } - - template - std::vector>& DeterministicModelBisimulationDecomposition::Partition::getStatesAndValues() { - return this->statesAndValues; - } - - template - std::list::Block>& DeterministicModelBisimulationDecomposition::Partition::getBlocks() { - return this->blocks; - } - - template - bool DeterministicModelBisimulationDecomposition::Partition::check() const { - for (uint_fast64_t state = 0; state < this->positions.size(); ++state) { - if (this->statesAndValues[this->positions[state]].first != state) { - assert(false); - } - } - for (auto const& block : this->blocks) { - assert(block.check()); - for (auto stateIt = this->getBegin(block), stateIte = this->getEnd(block); stateIt != stateIte; ++stateIt) { - if (this->stateToBlockMapping[stateIt->first] != &block) { - assert(false); - } - } - } - return true; - } - - template - void DeterministicModelBisimulationDecomposition::Partition::print() const { - for (auto const& block : this->blocks) { - block.print(*this); - } - std::cout << "states in partition" << std::endl; - for (auto const& stateValue : statesAndValues) { - std::cout << stateValue.first << " "; - } - std::cout << std::endl << "positions: " << std::endl; - for (auto const& index : positions) { - std::cout << index << " "; - } - std::cout << std::endl << "state to block mapping: " << std::endl; - for (auto const& block : stateToBlockMapping) { - std::cout << block << "[" << block->getId() <<"] "; - } - std::cout << std::endl; - if (this->keepSilentProbabilities) { - std::cout << "silent probabilities" << std::endl; - for (auto const& prob : silentProbabilities) { - std::cout << prob << " "; - } - std::cout << std::endl; - } - std::cout << "size: " << numberOfBlocks << std::endl; - assert(this->check()); - } - - template - std::size_t DeterministicModelBisimulationDecomposition::Partition::size() const { - return numberOfBlocks; - } - - template - DeterministicModelBisimulationDecomposition::Options::Options(storm::models::sparse::Model const& model, storm::logic::Formula const& formula) : Options() { - this->preserveSingleFormula(model, formula); - } - - template - DeterministicModelBisimulationDecomposition::Options::Options(storm::models::sparse::Model const& model, std::vector> const& formulas) : Options() { - if (formulas.size() == 1) { - this->preserveSingleFormula(model, *formulas.front()); - } else { - for (auto const& formula : formulas) { - std::vector> atomicExpressionFormulas = formula->getAtomicExpressionFormulas(); - std::vector> atomicLabelFormulas = formula->getAtomicLabelFormulas(); - - std::set labelsToRespect; - for (auto const& labelFormula : atomicLabelFormulas) { - labelsToRespect.insert(labelFormula->getLabel()); - } - for (auto const& expressionFormula : atomicExpressionFormulas) { - std::stringstream stream; - stream << *expressionFormula; - labelsToRespect.insert(stream.str()); - } - if (!respectedAtomicPropositions) { - respectedAtomicPropositions = labelsToRespect; - } else { - respectedAtomicPropositions.get().insert(labelsToRespect.begin(), labelsToRespect.end()); - } - } - } - } - - template - DeterministicModelBisimulationDecomposition::Options::Options() : measureDrivenInitialPartition(false), phiStates(), psiStates(), respectedAtomicPropositions(), keepRewards(true), weak(false), bounded(true), buildQuotient(true) { - // Intentionally left empty. - } - - template - void DeterministicModelBisimulationDecomposition::Options::preserveSingleFormula(storm::models::sparse::Model const& model, storm::logic::Formula const& formula) { - if (!formula.containsRewardOperator()) { - this->keepRewards = false; - } - - // As a tradeoff, we compute a strong bisimulation, because that is typically much faster. If the number of - // states is to be minimized, this should be set to weak by the calling site. - weak = false; - - // We need to preserve bounded properties iff the formula contains a bounded until or a next subformula. - bounded = formula.containsBoundedUntilFormula() || formula.containsNextFormula(); - - std::vector> atomicExpressionFormulas = formula.getAtomicExpressionFormulas(); - std::vector> atomicLabelFormulas = formula.getAtomicLabelFormulas(); - - std::set labelsToRespect; - for (auto const& labelFormula : atomicLabelFormulas) { - labelsToRespect.insert(labelFormula->getLabel()); - } - for (auto const& expressionFormula : atomicExpressionFormulas) { - std::stringstream stream; - stream << *expressionFormula; - labelsToRespect.insert(stream.str()); - } - respectedAtomicPropositions = labelsToRespect; - - std::shared_ptr newFormula = formula.asSharedPointer(); - - if (formula.isProbabilityOperatorFormula()) { - newFormula = formula.asProbabilityOperatorFormula().getSubformula().asSharedPointer(); - } else if (formula.isRewardOperatorFormula()) { - newFormula = formula.asRewardOperatorFormula().getSubformula().asSharedPointer(); - } - - std::shared_ptr leftSubformula = std::make_shared(true); - std::shared_ptr rightSubformula; - if (newFormula->isUntilFormula()) { - leftSubformula = newFormula->asUntilFormula().getLeftSubformula().asSharedPointer(); - rightSubformula = newFormula->asUntilFormula().getRightSubformula().asSharedPointer(); - if (leftSubformula->isPropositionalFormula() && rightSubformula->isPropositionalFormula()) { - measureDrivenInitialPartition = true; - } - } else if (newFormula->isEventuallyFormula()) { - rightSubformula = newFormula->asEventuallyFormula().getSubformula().asSharedPointer(); - if (rightSubformula->isPropositionalFormula()) { - measureDrivenInitialPartition = true; - } - } else if (newFormula->isReachabilityRewardFormula()) { - rightSubformula = newFormula->asReachabilityRewardFormula().getSubformula().asSharedPointer(); - if (rightSubformula->isPropositionalFormula()) { - measureDrivenInitialPartition = true; - } - } - - if (measureDrivenInitialPartition) { - storm::modelchecker::SparsePropositionalModelChecker> checker(model); - std::unique_ptr phiStatesCheckResult = checker.check(*leftSubformula); - std::unique_ptr psiStatesCheckResult = checker.check(*rightSubformula); - phiStates = phiStatesCheckResult->asExplicitQualitativeCheckResult().getTruthValuesVector(); - psiStates = psiStatesCheckResult->asExplicitQualitativeCheckResult().getTruthValuesVector(); - } - } - - template - DeterministicModelBisimulationDecomposition::DeterministicModelBisimulationDecomposition(storm::models::sparse::Dtmc const& model, Options const& options) { - 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."); - storm::storage::SparseMatrix backwardTransitions = model.getBackwardTransitions(); - BisimulationType bisimulationType = options.weak ? BisimulationType::WeakDtmc : BisimulationType::Strong; - - std::set atomicPropositions; - if (options.respectedAtomicPropositions) { - atomicPropositions = options.respectedAtomicPropositions.get(); - } else { - atomicPropositions = model.getStateLabeling().getLabels(); - } - - Partition initialPartition; - storm::utility::ConstantsComparator comparator; - 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."); - initialPartition = getMeasureDrivenInitialPartition(model, backwardTransitions, options.phiStates.get(), options.psiStates.get(), bisimulationType, options.keepRewards, options.bounded, comparator); - } else { - initialPartition = getLabelBasedInitialPartition(model, backwardTransitions, bisimulationType, atomicPropositions, options.keepRewards, comparator); - } - - partitionRefinement(model, atomicPropositions, backwardTransitions, initialPartition, bisimulationType, options.keepRewards, options.buildQuotient, comparator); - } - - template - DeterministicModelBisimulationDecomposition::DeterministicModelBisimulationDecomposition(storm::models::sparse::Ctmc const& model, Options const& options) { - 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."); - storm::storage::SparseMatrix backwardTransitions = model.getBackwardTransitions(); - BisimulationType bisimulationType = options.weak ? BisimulationType::WeakCtmc : BisimulationType::Strong; - - std::set atomicPropositions; - if (options.respectedAtomicPropositions) { - atomicPropositions = options.respectedAtomicPropositions.get(); - } else { - atomicPropositions = model.getStateLabeling().getLabels(); - } - - Partition initialPartition; - storm::utility::ConstantsComparator comparator; - if (options.measureDrivenInitialPartition) { - STORM_LOG_THROW(options.phiStates, storm::exceptions::InvalidOptionException, "Unable to compute measure-driven initial partition without phi and psi states."); - STORM_LOG_THROW(options.psiStates, storm::exceptions::InvalidOptionException, "Unable to compute measure-driven initial partition without phi and psi states."); - initialPartition = getMeasureDrivenInitialPartition(model, backwardTransitions, options.phiStates.get(), options.psiStates.get(), bisimulationType, options.keepRewards, options.bounded, comparator); - } else { - initialPartition = getLabelBasedInitialPartition(model, backwardTransitions, bisimulationType, atomicPropositions, options.keepRewards, comparator); - } - - partitionRefinement(model, atomicPropositions, backwardTransitions, initialPartition, bisimulationType, options.keepRewards, options.buildQuotient, comparator); - } - - template - std::shared_ptr> DeterministicModelBisimulationDecomposition::getQuotient() const { - STORM_LOG_THROW(this->quotient != nullptr, storm::exceptions::IllegalFunctionCallException, "Unable to retrieve quotient model from bisimulation decomposition, because it was not built."); - return this->quotient; - } - - template - template - void DeterministicModelBisimulationDecomposition::buildQuotient(ModelType const& model, std::set const& selectedAtomicPropositions, Partition const& partition, BisimulationType bisimulationType, bool keepRewards, storm::utility::ConstantsComparator const& comparator) { - // In order to create the quotient model, we need to construct - // (a) the new transition matrix, - // (b) the new labeling, - // (c) the new reward structures. - - // Prepare a matrix builder for (a). - storm::storage::SparseMatrixBuilder builder(this->size(), this->size()); - - // Prepare the new state labeling for (b). - storm::models::sparse::StateLabeling newLabeling(this->size()); - std::set atomicPropositionsSet = selectedAtomicPropositions; - atomicPropositionsSet.insert("init"); - std::vector atomicPropositions = std::vector(atomicPropositionsSet.begin(), atomicPropositionsSet.end()); - for (auto const& ap : atomicPropositions) { - newLabeling.addLabel(ap); - } - - // If the model had state rewards, we need to build the state rewards for the quotient as well. - boost::optional> stateRewards; - if (keepRewards && model.hasRewardModel()) { - stateRewards = std::vector(this->blocks.size()); - } - - // Now build (a) and (b) by traversing all blocks. - for (uint_fast64_t blockIndex = 0; blockIndex < this->blocks.size(); ++blockIndex) { - auto const& block = this->blocks[blockIndex]; - - // Pick one representative state. For strong bisimulation it doesn't matter which state it is, because - // they all behave equally. - storm::storage::sparse::state_type representativeState = *block.begin(); - - // However, for weak bisimulation, we need to make sure the representative state is a non-silent one (if - // there is any such state). - if (bisimulationType == BisimulationType::WeakDtmc) { - for (auto const& state : block) { - if (!partition.isSilent(state, comparator)) { - representativeState = state; - break; - } - } - } - - Block const& oldBlock = partition.getBlock(representativeState); - - // If the block is absorbing, we simply add a self-loop. - if (oldBlock.isAbsorbing()) { - builder.addNextValue(blockIndex, blockIndex, storm::utility::one()); - - // If the block has a special representative state, we retrieve it now. - if (oldBlock.hasRepresentativeState()) { - representativeState = oldBlock.getRepresentativeState(); - } - - // Add all of the selected atomic propositions that hold in the representative state to the state - // representing the block. - for (auto const& ap : atomicPropositions) { - if (model.getStateLabeling().getStateHasLabel(ap, representativeState)) { - newLabeling.addLabelToState(ap, blockIndex); - } - } - } else { - // Compute the outgoing transitions of the block. - std::map blockProbability; - for (auto const& entry : model.getTransitionMatrix().getRow(representativeState)) { - storm::storage::sparse::state_type targetBlock = partition.getBlock(entry.getColumn()).getId(); - - // If we are computing a weak bisimulation quotient, there is no need to add self-loops. - if ((bisimulationType == BisimulationType::WeakDtmc || bisimulationType == BisimulationType::WeakCtmc) && targetBlock == blockIndex) { - continue; - } - - auto probIterator = blockProbability.find(targetBlock); - if (probIterator != blockProbability.end()) { - probIterator->second += entry.getValue(); - } else { - blockProbability[targetBlock] = entry.getValue(); - } - } - - // Now add them to the actual matrix. - for (auto const& probabilityEntry : blockProbability) { - if (bisimulationType == BisimulationType::WeakDtmc) { - builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second / (storm::utility::one() - partition.getSilentProbability(representativeState))); - } else { - builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second); - } - } - - // Otherwise add all atomic propositions to the equivalence class that the representative state - // satisfies. - for (auto const& ap : atomicPropositions) { - if (model.getStateLabeling().getStateHasLabel(ap, representativeState)) { - newLabeling.addLabelToState(ap, blockIndex); - } - } - } - - // If the model has state rewards, we simply copy the state reward of the representative state, because - // all states in a block are guaranteed to have the same state reward. - if (keepRewards && model.hasRewardModel()) { - typename std::unordered_map::const_iterator nameRewardModelPair = model.getUniqueRewardModel(); - stateRewards.get()[blockIndex] = nameRewardModelPair->second.getStateRewardVector()[representativeState]; - } - } - - // Now check which of the blocks of the partition contain at least one initial state. - for (auto initialState : model.getInitialStates()) { - Block const& initialBlock = partition.getBlock(initialState); - newLabeling.addLabelToState("init", initialBlock.getId()); - } - - // Construct the reward model mapping. - std::unordered_map rewardModels; - if (keepRewards && model.hasRewardModel()) { - typename std::unordered_map::const_iterator nameRewardModelPair = model.getUniqueRewardModel(); - rewardModels.insert(std::make_pair(nameRewardModelPair->first, typename ModelType::RewardModelType(stateRewards))); - } - - // Finally construct the quotient model. - this->quotient = std::shared_ptr>(new ModelType(builder.build(), std::move(newLabeling), std::move(rewardModels))); - } - - template - template - void DeterministicModelBisimulationDecomposition::partitionRefinement(ModelType const& model, std::set const& atomicPropositions, storm::storage::SparseMatrix const& backwardTransitions, Partition& partition, BisimulationType bisimulationType, bool keepRewards, bool buildQuotient, storm::utility::ConstantsComparator const& comparator) { - std::chrono::high_resolution_clock::time_point totalStart = std::chrono::high_resolution_clock::now(); - - // Initially, all blocks are potential splitter, so we insert them in the splitterQueue. - std::chrono::high_resolution_clock::time_point refinementStart = std::chrono::high_resolution_clock::now(); - std::deque splitterQueue; - std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (Block& a) { splitterQueue.push_back(&a); a.markAsSplitter(); }); - - // Then perform the actual splitting until there are no more splitters. - while (!splitterQueue.empty()) { - // Optionally: sort the splitter queue according to some criterion (here: prefer small splitters). - std::sort(splitterQueue.begin(), splitterQueue.end(), [] (Block const* b1, Block const* b2) { return b1->getNumberOfStates() < b2->getNumberOfStates() || (b1->getNumberOfStates() == b2->getNumberOfStates() && b1->getId() < b2->getId()); } ); - std::sort(splitterQueue.begin(), splitterQueue.end(), [] (Block const* b1, Block const* b2) { return b1->getNumberOfStates() < b2->getNumberOfStates(); } ); - - // Get and prepare the next splitter. - Block* splitter = splitterQueue.front(); - splitterQueue.pop_front(); - splitter->unmarkAsSplitter(); - - // Now refine the partition using the current splitter. - refinePartition(model.getTransitionMatrix(), backwardTransitions, *splitter, partition, bisimulationType, splitterQueue, comparator); - } - std::chrono::high_resolution_clock::duration refinementTime = std::chrono::high_resolution_clock::now() - refinementStart; - - // 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. - std::chrono::high_resolution_clock::time_point extractionStart = std::chrono::high_resolution_clock::now(); - this->blocks.resize(partition.size()); - for (auto const& block : partition.getBlocks()) { - // We need to sort the states to allow for rapid construction of the blocks. - std::sort(partition.getBegin(block), partition.getEnd(block), [] (std::pair const& a, std::pair const& b) { return a.first < b.first; }); - - // Convert the state-value-pairs to states only. - std::function const&)> projection = [] (std::pair const& a) -> storm::storage::sparse::state_type { return a.first; }; - this->blocks[block.getId()] = block_type(boost::make_transform_iterator(partition.getBegin(block), projection), boost::make_transform_iterator(partition.getEnd(block), projection), true); - } - - // If we are required to build the quotient model, do so now. - if (buildQuotient) { - this->buildQuotient(model, atomicPropositions, partition, bisimulationType, keepRewards, comparator); - } - - std::chrono::high_resolution_clock::duration extractionTime = std::chrono::high_resolution_clock::now() - extractionStart; - std::chrono::high_resolution_clock::duration totalTime = std::chrono::high_resolution_clock::now() - totalStart; - - if (storm::settings::generalSettings().isShowStatisticsSet()) { - std::chrono::milliseconds refinementTimeInMilliseconds = std::chrono::duration_cast(refinementTime); - std::chrono::milliseconds extractionTimeInMilliseconds = std::chrono::duration_cast(extractionTime); - std::chrono::milliseconds totalTimeInMilliseconds = std::chrono::duration_cast(totalTime); - std::cout << std::endl; - std::cout << "Time breakdown:" << std::endl; - std::cout << " * time for partitioning: " << refinementTimeInMilliseconds.count() << "ms" << std::endl; - std::cout << " * time for extraction: " << extractionTimeInMilliseconds.count() << "ms" << std::endl; - std::cout << "------------------------------------------" << std::endl; - std::cout << " * total time: " << totalTimeInMilliseconds.count() << "ms" << std::endl; - std::cout << std::endl; - } - } - - template - void DeterministicModelBisimulationDecomposition::refineBlockProbabilities(Block& block, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator) { - // Sort the states in the block based on their probabilities. - std::sort(partition.getBegin(block), partition.getEnd(block), [&partition] (std::pair const& a, std::pair 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>::const_iterator begin = partition.getBegin(block); - typename std::vector>::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 - void DeterministicModelBisimulationDecomposition::refineBlockWeak(Block& block, Partition& partition, storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator) { - std::vector 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 stateLabels(block.getEnd() - block.getBegin(), storm::storage::BitVector(splitPoints.size() - 1)); - - std::vector 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 const& a, std::pair 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>::const_iterator begin = partition.getBegin(block); - typename std::vector>::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(); - 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(); - 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 - std::vector DeterministicModelBisimulationDecomposition::getSplitPointsWeak(Block& block, Partition& partition, storm::utility::ConstantsComparator const& comparator) { - std::vector 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& stateValuePair) { - ValueType const& silentProbability = partition.getSilentProbability(stateValuePair.first); - if (!comparator.isOne(silentProbability) && !comparator.isZero(silentProbability)) { - stateValuePair.second /= storm::utility::one() - silentProbability; - } - }); - - // Now sort the states based on their probabilities. - std::sort(partition.getStatesAndValues().begin() + block.getOriginalBegin(), partition.getStatesAndValues().begin() + block.getBegin(), [&partition] (std::pair const& a, std::pair 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>::const_iterator begin = partition.getStatesAndValues().begin() + block.getOriginalBegin(); - typename std::vector>::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 - void DeterministicModelBisimulationDecomposition::refinePartition(storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, Block& splitter, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator) { - std::list 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 blocksToSplit; - - // Now, we can iterate over the predecessor blocks and see whether we have to create a new block for - // predecessors of the splitter. - for (auto blockPtr : predecessorBlocks) { - Block& block = *blockPtr; - - block.unmarkAsPredecessorBlock(); - block.resetMarkedPosition(); - - // If we have moved the begin of the block to somewhere in the middle of the block, we need to split it. - if (block.getBegin() != block.getEnd()) { - Block& newBlock = partition.insertBlock(block); - if (!newBlock.isMarkedAsSplitter()) { - splitterQueue.push_back(&newBlock); - newBlock.markAsSplitter(); - } - - // Schedule the block of predecessors for refinement based on probabilities. - blocksToSplit.emplace_back(&newBlock); - } else { - // In this case, we can keep the block by setting its begin to the old value. - block.setBegin(block.getOriginalBegin()); - blocksToSplit.emplace_back(&block); - } - } - - // Finally, we walk through the blocks that have a transition to the splitter and split them using - // probabilistic information. - for (auto blockPtr : blocksToSplit) { - if (blockPtr->getNumberOfStates() <= 1) { - continue; - } - - // In the case of weak bisimulation for CTMCs, we don't need to make sure the rate of staying inside - // the own block is the same. - if (bisimulationType == BisimulationType::WeakCtmc && blockPtr == &splitter) { - continue; - } - - refineBlockProbabilities(*blockPtr, partition, bisimulationType, splitterQueue, comparator); - } - } else { // In this case, we are computing a weak bisimulation on a DTMC. - // If the splitter was a predecessor of itself and we are computing a weak bisimulation, we need to update - // the silent probabilities. - if (splitterIsPredecessor) { - partition.setSilentProbabilities(partition.getStatesAndValues().begin() + splitter.getOriginalBegin(), partition.getStatesAndValues().begin() + splitter.getBegin()); - partition.setSilentProbabilitiesToZero(partition.getStatesAndValues().begin() + splitter.getBegin(), partition.getStatesAndValues().begin() + splitter.getEnd()); - } - - // Now refine all predecessor blocks in a weak manner. That is, we split according to the criterion of - // weak bisimulation. - for (auto blockPtr : predecessorBlocks) { - Block& block = *blockPtr; - - // If the splitter is also the predecessor block, we must not refine it at this point. - if (&block != &splitter) { - refineBlockWeak(block, partition, forwardTransitions, backwardTransitions, splitterQueue, comparator); - } else { - // Restore the begin of the block. - block.setBegin(block.getOriginalBegin()); - } - - block.unmarkAsPredecessorBlock(); - block.resetMarkedPosition(); - } - } - - STORM_LOG_ASSERT(partition.check(), "Partition became inconsistent."); - } - - template - template - typename DeterministicModelBisimulationDecomposition::Partition DeterministicModelBisimulationDecomposition::getMeasureDrivenInitialPartition(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, BisimulationType bisimulationType, bool keepRewards, bool bounded, storm::utility::ConstantsComparator const& comparator) { - std::pair statesWithProbability01 = storm::utility::graph::performProb01(backwardTransitions, phiStates, psiStates); - - boost::optional representativePsiState; - if (!psiStates.empty()) { - representativePsiState = *psiStates.begin(); - } - - Partition partition(model.getNumberOfStates(), statesWithProbability01.first, bounded || keepRewards ? psiStates : statesWithProbability01.second, representativePsiState, bisimulationType == BisimulationType::WeakDtmc); - - // If the model has state rewards, we need to consider them, because otherwise reward properties are not - // preserved. - if (keepRewards && model.hasRewardModel()) { - this->splitRewards(model.getUniqueRewardModel()->second.getStateRewardVector(), partition, comparator); - } - - // If we are creating the initial partition for weak bisimulation, we need to (a) split off all divergent - // states of each initial block and (b) initialize the vector of silent probabilities held by the partition. - if (bisimulationType == BisimulationType::WeakDtmc) { - this->splitOffDivergentStates(model, backwardTransitions, partition); - this->initializeSilentProbabilities(model, partition); - } - - return partition; - } - - template - template - void DeterministicModelBisimulationDecomposition::splitOffDivergentStates(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, Partition& partition) { - std::vector stateStack; - stateStack.reserve(model.getNumberOfStates()); - storm::storage::BitVector nondivergentStates(model.getNumberOfStates()); - for (auto& block : partition.getBlocks()) { - nondivergentStates.clear(); - - for (auto stateIt = partition.getBegin(block), stateIte = partition.getEnd(block); stateIt != stateIte; ++stateIt) { - if (nondivergentStates.get(stateIt->first)) { - continue; - } - - // Now traverse the forward transitions of the current state and check whether there is a - // transition to some other block. - bool isDirectlyNonDivergent = false; - for (auto const& successor : model.getRows(stateIt->first)) { - // If there is such a transition, then we can mark all states in the current block that can - // reach the state as non-divergent. - if (&partition.getBlock(successor.getColumn()) != &block) { - isDirectlyNonDivergent = true; - break; - } - } - - if (isDirectlyNonDivergent) { - stateStack.push_back(stateIt->first); - - while (!stateStack.empty()) { - storm::storage::sparse::state_type currentState = stateStack.back(); - stateStack.pop_back(); - nondivergentStates.set(currentState); - - for (auto const& predecessor : backwardTransitions.getRow(currentState)) { - if (&partition.getBlock(predecessor.getColumn()) == &block && !nondivergentStates.get(predecessor.getColumn())) { - stateStack.push_back(predecessor.getColumn()); - } - } - } - } - } - - - if (nondivergentStates.getNumberOfSetBits() > 0 && nondivergentStates.getNumberOfSetBits() != block.getNumberOfStates()) { - // Now that we have determined all (non)divergent states in the current block, we need to split them - // off. - std::sort(partition.getBegin(block), partition.getEnd(block), [&nondivergentStates] (std::pair const& a, std::pair const& b) { return nondivergentStates.get(a.first) && !nondivergentStates.get(b.first); } ); - // 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, split the block. - partition.splitBlock(block, block.getBegin() + nondivergentStates.getNumberOfSetBits()); - - // Since the remaining states in the block are divergent, we can mark the block as absorbing. - // This also guarantees that the self-loop will be added to the state of the quotient - // representing this block of states. - block.setAbsorbing(true); - } else if (nondivergentStates.getNumberOfSetBits() == 0) { - // If there are only diverging states in the block, we need to make it absorbing. - block.setAbsorbing(true); - } - } - } - - template - template - typename DeterministicModelBisimulationDecomposition::Partition DeterministicModelBisimulationDecomposition::getLabelBasedInitialPartition(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, BisimulationType bisimulationType, boost::optional> const& atomicPropositions, bool keepRewards, storm::utility::ConstantsComparator const& comparator) { - Partition partition(model.getNumberOfStates(), bisimulationType == BisimulationType::WeakDtmc); - - if (atomicPropositions) { - for (auto const& label : atomicPropositions.get()) { - if (label == "init") { - continue; - } - partition.splitLabel(model.getStates(label)); - } - } else { - for (auto const& label : model.getStateLabeling().getLabels()) { - if (label == "init") { - continue; - } - partition.splitLabel(model.getStates(label)); - } - } - - // If the model has state rewards, we need to consider them, because otherwise reward properties are not - // preserved. - if (keepRewards && model.hasRewardModel()) { - this->splitRewards(model.getUniqueRewardModel()->second.getStateRewardVector(), partition, comparator); - } - - // If we are creating the initial partition for weak bisimulation, we need to (a) split off all divergent - // states of each initial block and (b) initialize the vector of silent probabilities held by the partition. - if (bisimulationType == BisimulationType::WeakDtmc) { - this->splitOffDivergentStates(model, backwardTransitions, partition); - this->initializeSilentProbabilities(model, partition); - } - return partition; - } - - template - template - void DeterministicModelBisimulationDecomposition::initializeSilentProbabilities(ModelType const& model, Partition& partition) { - for (auto const& block : partition.getBlocks()) { - for (auto stateIt = partition.getBegin(block), stateIte = partition.getEnd(block); stateIt != stateIte; ++stateIt) { - ValueType silentProbability = storm::utility::zero(); - - for (auto const& successorEntry : model.getRows(stateIt->first)) { - if (&partition.getBlock(successorEntry.getColumn()) == &block) { - silentProbability += successorEntry.getValue(); - } - } - - partition.setSilentProbability(stateIt->first, silentProbability); - } - } - } - - template - void DeterministicModelBisimulationDecomposition::splitRewards(std::vector const& stateRewardVector, Partition& partition, storm::utility::ConstantsComparator const& comparator) { - for (auto& block : partition.getBlocks()) { - std::sort(partition.getBegin(block), partition.getEnd(block), [&stateRewardVector] (std::pair const& a, std::pair const& b) { return stateRewardVector[a.first] < stateRewardVector[b.first]; } ); - - // Update the positions vector and put the (state) reward values next to the states so we can easily compare them later. - 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); - stateIt->second = stateRewardVector[stateIt->first]; - } - - // Finally, we need to scan the ranges of states that agree on the probability. - typename std::vector>::const_iterator begin = partition.getBegin(block); - typename std::vector>::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 rewards 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 reward 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. - partition.splitBlock(block, currentIndex); - } - } - } - - template class DeterministicModelBisimulationDecomposition; - -#ifdef STORM_HAVE_CARL - template class DeterministicModelBisimulationDecomposition; -#endif - } -} diff --git a/src/storage/DeterministicModelBisimulationDecomposition.h b/src/storage/DeterministicModelBisimulationDecomposition.h deleted file mode 100644 index d66591f76..000000000 --- a/src/storage/DeterministicModelBisimulationDecomposition.h +++ /dev/null @@ -1,590 +0,0 @@ -#ifndef STORM_STORAGE_DeterministicModelBisimulationDecomposition_H_ -#define STORM_STORAGE_DeterministicModelBisimulationDecomposition_H_ - -#include -#include - -#include "src/storage/sparse/StateType.h" -#include "src/storage/Decomposition.h" -#include "src/storage/StateBlock.h" -#include "src/logic/Formulas.h" -#include "src/models/sparse/Dtmc.h" -#include "src/models/sparse/Ctmc.h" -#include "src/storage/Distribution.h" -#include "src/utility/constants.h" -#include "src/utility/OsDetection.h" -#include "src/exceptions/InvalidOperationException.h" - -namespace storm { - namespace utility { - template class ConstantsComparator; - } - - namespace storage { - - /*! - * This class represents the decomposition model into its (strong) bisimulation quotient. - */ - template - class DeterministicModelBisimulationDecomposition : public Decomposition { - public: - // A class that offers the possibility to customize the bisimulation. - struct Options { - // Creates an object representing the default values for all options. - Options(); - - /*! - * Creates an object representing the options necessary to obtain the quotient while still preserving - * the given formula. - * - * @param The model for which the quotient model shall be computed. This needs to be given in order to - * derive a suitable initial partition. - * @param formula The formula that is to be preserved. - */ - Options(storm::models::sparse::Model const& model, storm::logic::Formula const& formula); - - /*! - * Creates an object representing the options necessary to obtain the smallest quotient while still - * preserving the given formula. - * - * @param The model for which the quotient model shall be computed. This needs to be given in order to - * derive a suitable initial partition. - * @param formulas The formula that is to be preserved. - */ - Options(storm::models::sparse::Model const& model, std::vector> const& formulas); - - // A flag that indicates whether a measure driven initial partition is to be used. If this flag is set - // to true, the two optional pairs phiStatesAndLabel and psiStatesAndLabel must be set. Then, the - // measure driven initial partition wrt. to the states phi and psi is taken. - bool measureDrivenInitialPartition; - boost::optional phiStates; - boost::optional psiStates; - - // An optional set of strings that indicate which of the atomic propositions of the model are to be - // respected and which may be ignored. If not given, all atomic propositions of the model are respected. - boost::optional> respectedAtomicPropositions; - - // A flag that indicates whether or not the state-rewards of the model are to be respected (and should - // be kept in the quotient model, if one is built). - bool keepRewards; - - // A flag that indicates whether a strong or a weak bisimulation is to be computed. - bool weak; - - // A flag that indicates whether step-bounded properties are to be preserved. This may only be set to tru - // when computing strong bisimulation equivalence. - bool bounded; - - // A flag that governs whether the quotient model is actually built or only the decomposition is computed. - bool buildQuotient; - - private: - /*! - * Sets the options under the assumption that the given formula is the only one that is to be checked. - * - * @param model The model for which to preserve the formula. - * @param formula The only formula to check. - */ - void preserveSingleFormula(storm::models::sparse::Model const& model, storm::logic::Formula const& formula); - }; - - /*! - * Decomposes the given DTMC into equivalance classes of a bisimulation. Which kind of bisimulation is - * computed, is customizable via the given options. - * - * @param model The model to decompose. - * @param options The options that customize the computed bisimulation. - */ - DeterministicModelBisimulationDecomposition(storm::models::sparse::Dtmc const& model, Options const& options = Options()); - - /*! - * Decomposes the given CTMC into equivalance classes of a bisimulation. Which kind of bisimulation is - * computed, is customizable via the given options. - * - * @param model The model to decompose. - * @param options The options that customize the computed bisimulation. - */ - DeterministicModelBisimulationDecomposition(storm::models::sparse::Ctmc const& model, Options const& options = Options()); - - /*! - * Retrieves the quotient of the model under the previously computed bisimulation. - * - * @return The quotient model. - */ - std::shared_ptr> getQuotient() const; - - private: - enum class BisimulationType { Strong, WeakDtmc, WeakCtmc }; - - class Partition; - - class Block { - public: - typedef typename std::list::iterator iterator; - typedef typename std::list::const_iterator const_iterator; - - // Creates a new block with the given begin and end. - Block(storm::storage::sparse::state_type begin, storm::storage::sparse::state_type end, Block* prev, Block* next, std::size_t id); - - // Prints the block. - void print(Partition const& partition) const; - - // Sets the beginning index of the block. - void setBegin(storm::storage::sparse::state_type begin); - - // Moves the beginning index of the block one step further. - void incrementBegin(); - - // Sets the end index of the block. - void setEnd(storm::storage::sparse::state_type end); - - // Returns the beginning index of the block. - storm::storage::sparse::state_type getBegin() const; - - // Returns the beginning index of the block. - storm::storage::sparse::state_type getEnd() const; - - // Retrieves the original beginning index of the block in case the begin index has been moved. - storm::storage::sparse::state_type getOriginalBegin() const; - - // Returns the iterator the block in the list of overall blocks. - iterator getIterator() const; - - // Returns the iterator the block in the list of overall blocks. - void setIterator(iterator it); - - // Returns the iterator the next block in the list of overall blocks if it exists. - iterator getNextIterator() const; - - // Returns the iterator the next block in the list of overall blocks if it exists. - iterator getPreviousIterator() const; - - // Gets the next block (if there is one). - Block& getNextBlock(); - - // Gets the next block (if there is one). - Block const& getNextBlock() const; - - // Gets a pointer to the next block (if there is one). - Block* getNextBlockPointer(); - - // Retrieves whether the block as a successor block. - bool hasNextBlock() const; - - // Gets the previous block (if there is one). - Block& getPreviousBlock(); - - // Gets a pointer to the previous block (if there is one). - Block* getPreviousBlockPointer(); - - // Gets the next block (if there is one). - Block const& getPreviousBlock() const; - - // Retrieves whether the block as a successor block. - bool hasPreviousBlock() const; - - // Checks consistency of the information in the block. - bool check() const; - - // Retrieves the number of states in this block. - std::size_t getNumberOfStates() const; - - // Checks whether the block is marked as a splitter. - bool isMarkedAsSplitter() const; - - // Marks the block as being a splitter. - void markAsSplitter(); - - // Removes the mark. - void unmarkAsSplitter(); - - // Retrieves the ID of the block. - std::size_t getId() const; - - // Retrieves the marked position in the block. - storm::storage::sparse::state_type getMarkedPosition() const; - - // Sets the marked position to the given value.. - void setMarkedPosition(storm::storage::sparse::state_type position); - - // Increases the marked position by one. - void incrementMarkedPosition(); - - // Resets the marked position to the begin of the block. - void resetMarkedPosition(); - - // Retrieves whether the block is marked as a predecessor. - bool isMarkedAsPredecessor() const; - - // Marks the block as being a predecessor block. - void markAsPredecessorBlock(); - - // Removes the marking. - void unmarkAsPredecessorBlock(); - - // Sets whether or not the block is to be interpreted as absorbing. - void setAbsorbing(bool absorbing); - - // Retrieves whether the block is to be interpreted as absorbing. - bool isAbsorbing() const; - - // Sets the representative state of this block - void setRepresentativeState(storm::storage::sparse::state_type representativeState); - - // Retrieves the representative state for this block. - bool hasRepresentativeState() const; - - // Retrieves the representative state for this block. - storm::storage::sparse::state_type getRepresentativeState() const; - - private: - // An iterator to itself. This is needed to conveniently insert elements in the overall list of blocks - // kept by the partition. - iterator selfIt; - - // Pointers to the next and previous block. - Block* next; - Block* prev; - - // The begin and end indices of the block in terms of the state vector of the partition. - storm::storage::sparse::state_type begin; - storm::storage::sparse::state_type end; - - // A field that can be used for marking the block. - bool markedAsSplitter; - - // A field that can be used for marking the block as a predecessor block. - bool markedAsPredecessorBlock; - - // A position that can be used to store a certain position within the block. - storm::storage::sparse::state_type markedPosition; - - // A flag indicating whether the block is to be interpreted as absorbing or not. - bool absorbing; - - // The ID of the block. This is only used for debugging purposes. - std::size_t id; - - // An optional representative state for the block. If this is set, this state is used to derive the - // atomic propositions of the meta state in the quotient model. - boost::optional representativeState; - }; - - class Partition { - public: - friend class Block; - - /*! - * Creates a partition with one block consisting of all the states. - * - * @param numberOfStates The number of states the partition holds. - * @param keepSilentProbabilities A flag indicating whether or not silent probabilities are to be tracked. - */ - Partition(std::size_t numberOfStates, bool keepSilentProbabilities = false); - - /*! - * Creates a partition with three blocks: one with all phi states, one with all psi states and one with - * all other states. The former two blocks are marked as being absorbing, because their outgoing - * transitions shall not be taken into account for future refinement. - * - * @param numberOfStates The number of states the partition holds. - * @param prob0States The states which have probability 0 of satisfying phi until psi. - * @param prob1States The states which have probability 1 of satisfying phi until psi. - * @param representativeProb1State If the set of prob1States is non-empty, this needs to be a state - * that is representative for this block in the sense that the state representing this block in the quotient - * model will receive exactly the atomic propositions of the representative state. - * @param keepSilentProbabilities A flag indicating whether or not silent probabilities are to be tracked. - */ - Partition(std::size_t numberOfStates, storm::storage::BitVector const& prob0States, storm::storage::BitVector const& prob1States, boost::optional representativeProb1State, bool keepSilentProbabilities = false); - - Partition() = default; - Partition(Partition const& other) = default; - Partition& operator=(Partition const& other) = default; - - // Define move-construct and move-assignment explicitly to make sure they exist (as they are vital to - // keep validity of pointers. - Partition(Partition&& other) : blocks(std::move(other.blocks)), numberOfBlocks(other.numberOfBlocks), stateToBlockMapping(std::move(other.stateToBlockMapping)), statesAndValues(std::move(other.statesAndValues)), positions(std::move(other.positions)), keepSilentProbabilities(other.keepSilentProbabilities), silentProbabilities(std::move(other.silentProbabilities)) { - // Intentionally left empty. - } - - Partition& operator=(Partition&& other) { - if (this != &other) { - blocks = std::move(other.blocks); - numberOfBlocks = other.numberOfBlocks; - stateToBlockMapping = std::move(other.stateToBlockMapping); - statesAndValues = std::move(other.statesAndValues); - positions = std::move(other.positions); - keepSilentProbabilities = other.keepSilentProbabilities; - silentProbabilities = std::move(other.silentProbabilities); - } - - return *this; - } - - /*! - * Splits all blocks of the partition such that afterwards all blocks contain only states with the label - * or no labeled state at all. - */ - void splitLabel(storm::storage::BitVector const& statesWithLabel); - - // Retrieves the size of the partition, i.e. the number of blocks. - std::size_t size() const; - - // Prints the partition to the standard output. - void print() const; - - // Splits the block at the given position and inserts a new block after the current one holding the rest - // of the states. - Block& splitBlock(Block& block, storm::storage::sparse::state_type position); - - // Inserts a block before the given block. The new block will cover all states between the beginning - // of the given block and the end of the previous block. - Block& insertBlock(Block& block); - - // Retrieves the blocks of the partition. - std::list const& getBlocks() const; - - // Retrieves the blocks of the partition. - std::list& getBlocks(); - - // Checks the partition for internal consistency. - bool check() const; - - // Returns an iterator to the beginning of the states of the given block. - typename std::vector>::iterator getBegin(Block const& block); - - // Returns an iterator to the beginning of the states of the given block. - typename std::vector>::iterator getEnd(Block const& block); - - // Returns an iterator to the beginning of the states of the given block. - typename std::vector>::const_iterator getBegin(Block const& block) const; - - // Returns an iterator to the beginning of the states of the given block. - typename std::vector>::const_iterator getEnd(Block const& block) const; - - // Swaps the positions of the two given states. - void swapStates(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2); - - // Swaps the positions of the two states given by their positions. - void swapStatesAtPositions(storm::storage::sparse::state_type position1, storm::storage::sparse::state_type position2); - - // Retrieves the block of the given state. - Block& getBlock(storm::storage::sparse::state_type state); - - // Retrieves the block of the given state. - Block const& getBlock(storm::storage::sparse::state_type state) const; - - // Retrieves the position of the given state. - storm::storage::sparse::state_type const& getPosition(storm::storage::sparse::state_type state) const; - - // Retrieves the position of the given state. - void setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position); - - // Sets the position of the state to the given position. - storm::storage::sparse::state_type const& getState(storm::storage::sparse::state_type position) const; - - // Retrieves the value for the given state. - ValueType const& getValue(storm::storage::sparse::state_type state) const; - - // Retrieves the value at the given position. - ValueType const& getValueAtPosition(storm::storage::sparse::state_type position) const; - - // Sets the given value for the given state. - void setValue(storm::storage::sparse::state_type state, ValueType value); - - // Retrieves the vector with the probabilities going into the current splitter. - std::vector>& getStatesAndValues(); - - // Increases the value for the given state by the specified amount. - void increaseValue(storm::storage::sparse::state_type state, ValueType value); - - // Updates the block mapping for the given range of states to the specified block. - void updateBlockMapping(Block& block, typename std::vector>::iterator first, typename std::vector>::iterator last); - - // Retrieves the first block of the partition. - Block& getFirstBlock(); - - // Retrieves whether the given state is fully silent (only in case the silent probabilities are tracked). - bool isSilent(storm::storage::sparse::state_type state, storm::utility::ConstantsComparator const& comparator) const; - - // Retrieves whether the given state has a non-zero silent probability. - bool hasSilentProbability(storm::storage::sparse::state_type state, storm::utility::ConstantsComparator const& comparator) const; - - // Retrieves the silent probability (i.e. the probability to stay within the own equivalence class). - ValueType const& getSilentProbability(storm::storage::sparse::state_type state) const; - - // Sets the silent probabilities for all the states in the range to their values in the range. - void setSilentProbabilities(typename std::vector>::iterator first, typename std::vector>::iterator last); - - // Sets the silent probabilities for all states in the range to zero. - void setSilentProbabilitiesToZero(typename std::vector>::iterator first, typename std::vector>::iterator last); - - // Sets the silent probability for the given state to the given value. - void setSilentProbability(storm::storage::sparse::state_type state, ValueType const& value); - - private: - // The list of blocks in the partition. - std::list blocks; - - // The number of blocks of the partition. Although this could be retrieved via block.size(), we want to - // guarantee constant access time (which is currently not guaranteed by gcc'c standard libary). - uint_fast64_t numberOfBlocks; - - // A mapping of states to their blocks. - std::vector stateToBlockMapping; - - // A vector containing all the states and their values. It is ordered in a special way such that the - // blocks only need to define their start/end indices. - std::vector> statesAndValues; - - // This vector keeps track of the position of each state in the state vector. - std::vector positions; - - // A flag that indicates whether or not the vector with silent probabilities exists. - bool keepSilentProbabilities; - - // This vector keeps track of the silent probabilities (i.e. the probabilities going into the very own - // equivalence class) of each state. This means that a state is silent iff its entry is non-zero. - std::vector silentProbabilities; - }; - - /*! - * Performs the partition refinement on the model and thereby computes the equivalence classes under strong - * bisimulation equivalence. If required, the quotient model is built and may be retrieved using - * getQuotient(). - * - * @param model The model on whose state space to compute the coarses strong bisimulation relation. - * @param atomicPropositions The set of atomic propositions that the bisimulation considers. - * @param backwardTransitions The backward transitions of the model. - * @param The initial partition. - * @param bisimulationType The kind of bisimulation that is to be computed. - * @param buildQuotient If set, the quotient model is built and may be retrieved using the getQuotient() - * method. - * @param comparator A comparator used for comparing constants. - */ - template - void partitionRefinement(ModelType const& model, std::set const& atomicPropositions, storm::storage::SparseMatrix const& backwardTransitions, Partition& partition, BisimulationType bisimulationType, bool keepRewards, bool buildQuotient, storm::utility::ConstantsComparator const& comparator); - - /*! - * Refines the partition based on the provided splitter. After calling this method all blocks are stable - * with respect to the splitter. - * - * @param forwardTransitions The forward transitions of the model. - * @param backwardTransitions A matrix that can be used to retrieve the predecessors (and their - * probabilities). - * @param splitter The splitter to use. - * @param partition The partition to split. - * @param bisimulationType The kind of bisimulation that is to be computed. - * @param splitterQueue A queue into which all blocks that were split are inserted so they can be treated - * as splitters in the future. - * @param comparator A comparator used for comparing constants. - */ - void refinePartition(storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, Block& splitter, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator); - - /*! - * Refines the block based on their probability values (leading into the splitter). - * - * @param block The block to refine. - * @param partition The partition that contains the block. - * @param bisimulationType The kind of bisimulation that is to be computed. - * @param splitterQueue A queue into which all blocks that were split are inserted so they can be treated - * as splitters in the future. - * @param comparator A comparator used for comparing constants. - */ - void refineBlockProbabilities(Block& block, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator); - - void refineBlockWeak(Block& block, Partition& partition, storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator); - - /*! - * Determines the split offsets in the given block. - * - * @param block The block that is to be analyzed for splits. - * @param partition The partition that contains the block. - * @param comparator A comparator used for comparing constants. - */ - std::vector getSplitPointsWeak(Block& block, Partition& partition, storm::utility::ConstantsComparator const& comparator); - - /*! - * Builds the quotient model based on the previously computed equivalence classes (stored in the blocks - * of the decomposition. - * - * @param model The model whose state space was used for computing the equivalence classes. This is used for - * determining the transitions of each equivalence class. - * @param selectedAtomicPropositions The set of atomic propositions that was considered by the bisimulation. - * The quotient will only have these atomic propositions. - * @param partition The previously computed partition. This is used for quickly retrieving the block of a - * state. - * @param bisimulationType The kind of bisimulation that is to be computed. - * @param comparator A comparator used for comparing constants. - */ - template - void buildQuotient(ModelType const& model, std::set const& selectedAtomicPropositions, Partition const& partition, BisimulationType bisimulationType, bool keepRewards, storm::utility::ConstantsComparator 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 - Partition getMeasureDrivenInitialPartition(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, BisimulationType bisimulationType, bool keepRewards = true, bool bounded = false, storm::utility::ConstantsComparator const& comparator = storm::utility::ConstantsComparator()); - - /*! - * 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 - Partition getLabelBasedInitialPartition(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, BisimulationType bisimulationType, boost::optional> const& atomicPropositions = boost::optional>(), bool keepRewards = true, storm::utility::ConstantsComparator const& comparator = storm::utility::ConstantsComparator()); - - /*! - * Splits all blocks of the given partition into a block that contains all divergent states and another block - * containing the non-divergent states. - * - * @param model The model from which to look-up the probabilities. - * @param backwardTransitions The backward transitions of the model. - * @param partition The partition that holds the silent probabilities. - */ - template - void splitOffDivergentStates(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, Partition& partition); - - /*! - * Initializes the silent probabilities by traversing all blocks and adding the probability of going to - * the very own equivalence class for each state. - * - * @param model The model from which to look-up the probabilities. - * @param partition The partition that holds the silent probabilities. - */ - template - void initializeSilentProbabilities(ModelType const& model, Partition& partition); - - /*! - * Splits all blocks of the partition in a way such that the states of each block agree on the rewards. - * - * @param stateRewardVector The state reward vector of the model. - * @param partition The partition that is to be split. - * @param comparator A comparator used for comparing constants. - */ - void splitRewards(std::vector const& stateRewardVector, Partition& partition, storm::utility::ConstantsComparator const& comparator); - - // If required, a quotient model is built and stored in this member. - std::shared_ptr> quotient; - }; - } -} - -#endif /* STORM_STORAGE_DeterministicModelBisimulationDecomposition_H_ */ \ No newline at end of file diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp new file mode 100644 index 000000000..06b33f55e --- /dev/null +++ b/src/storage/bisimulation/BisimulationDecomposition.cpp @@ -0,0 +1,259 @@ +#include "src/storage/bisimulation/BisimulationDecomposition.h" + +#include + +#include "src/models/sparse/Dtmc.h" +#include "src/models/sparse/Ctmc.h" + +#include "src/modelchecker/propositional/SparsePropositionalModelChecker.h" +#include "src/modelchecker/results/ExplicitQualitativeCheckResult.h" + +#include "src/settings/SettingsManager.h" +#include "src/settings/modules/GeneralSettings.h" + +#include "src/utility/macros.h" +#include "src/exceptions/IllegalFunctionCallException.h" + +namespace storm { + namespace storage { + + using namespace bisimulation; + + template + BisimulationDecomposition::Options::Options(ModelType const& model, storm::logic::Formula const& formula) : Options() { + this->preserveSingleFormula(model, formula); + } + + template + BisimulationDecomposition::Options::Options(ModelType const& model, std::vector> const& formulas) : Options() { + if (formulas.size() == 1) { + this->preserveSingleFormula(model, *formulas.front()); + } else { + for (auto const& formula : formulas) { + preserveFormula(model, *formula); + } + } + } + + template + BisimulationDecomposition::Options::Options() : measureDrivenInitialPartition(false), phiStates(), psiStates(), respectedAtomicPropositions(), keepRewards(false), type(BisimulationType::Strong), bounded(false), buildQuotient(true) { + // Intentionally left empty. + } + + template + void BisimulationDecomposition::Options::preserveFormula(ModelType const& model, storm::logic::Formula const& formula) { + // Disable the measure driven initial partition. + measureDrivenInitialPartition = false; + phiStates = boost::none; + psiStates = boost::none; + + // Preserve rewards if necessary. + keepRewards = keepRewards || formula.containsRewardOperator(); + + // Preserve bounded properties if necessary. + bounded = bounded || (formula.containsBoundedUntilFormula() || formula.containsNextFormula()); + + // Compute the relevant labels and expressions. + this->addToRespectedAtomicPropositions(formula.getAtomicExpressionFormulas(), formula.getAtomicLabelFormulas()); + } + + template + void BisimulationDecomposition::Options::preserveSingleFormula(ModelType const& model, storm::logic::Formula const& formula) { + keepRewards = formula.containsRewardOperator(); + + // We need to preserve bounded properties iff the formula contains a bounded until or a next subformula. + bounded = formula.containsBoundedUntilFormula() || formula.containsNextFormula(); + + // Compute the relevant labels and expressions. + this->addToRespectedAtomicPropositions(formula.getAtomicExpressionFormulas(), formula.getAtomicLabelFormulas()); + + // Check whether measure driven initial partition is possible and, if so, set it. + this->checkAndSetMeasureDrivenInitialPartition(model, formula); + } + + template + void BisimulationDecomposition::Options::checkAndSetMeasureDrivenInitialPartition(ModelType const& model, storm::logic::Formula const& formula) { + std::shared_ptr newFormula = formula.asSharedPointer(); + + if (formula.isProbabilityOperatorFormula()) { + newFormula = formula.asProbabilityOperatorFormula().getSubformula().asSharedPointer(); + } else if (formula.isRewardOperatorFormula()) { + newFormula = formula.asRewardOperatorFormula().getSubformula().asSharedPointer(); + } + + std::shared_ptr leftSubformula = std::make_shared(true); + std::shared_ptr rightSubformula; + if (newFormula->isUntilFormula()) { + leftSubformula = newFormula->asUntilFormula().getLeftSubformula().asSharedPointer(); + rightSubformula = newFormula->asUntilFormula().getRightSubformula().asSharedPointer(); + if (leftSubformula->isPropositionalFormula() && rightSubformula->isPropositionalFormula()) { + measureDrivenInitialPartition = true; + } + } else if (newFormula->isEventuallyFormula()) { + rightSubformula = newFormula->asEventuallyFormula().getSubformula().asSharedPointer(); + if (rightSubformula->isPropositionalFormula()) { + measureDrivenInitialPartition = true; + } + } else if (newFormula->isReachabilityRewardFormula()) { + rightSubformula = newFormula->asReachabilityRewardFormula().getSubformula().asSharedPointer(); + if (rightSubformula->isPropositionalFormula()) { + measureDrivenInitialPartition = true; + } + } + + if (measureDrivenInitialPartition) { + storm::modelchecker::SparsePropositionalModelChecker checker(model); + std::unique_ptr phiStatesCheckResult = checker.check(*leftSubformula); + std::unique_ptr psiStatesCheckResult = checker.check(*rightSubformula); + phiStates = phiStatesCheckResult->asExplicitQualitativeCheckResult().getTruthValuesVector(); + psiStates = psiStatesCheckResult->asExplicitQualitativeCheckResult().getTruthValuesVector(); + } + } + + template + void BisimulationDecomposition::Options::addToRespectedAtomicPropositions(std::vector> const& expressions, std::vector> const& labels) { + std::set labelsToRespect; + for (auto const& labelFormula : labels) { + labelsToRespect.insert(labelFormula->getLabel()); + } + for (auto const& expressionFormula : expressions) { + labelsToRespect.insert(expressionFormula->toString()); + } + if (!respectedAtomicPropositions) { + respectedAtomicPropositions = labelsToRespect; + } else { + respectedAtomicPropositions.get().insert(labelsToRespect.begin(), labelsToRespect.end()); + } + } + + template + BisimulationDecomposition::BisimulationDecomposition(ModelType const& model, Options const& options) : model(model), backwardTransitions(model.getBackwardTransitions()), options(options), partition(), comparator(), quotient(nullptr) { + // Fix the respected atomic propositions if they were not explicitly given. + if (!this->options.respectedAtomicPropositions) { + this->options.respectedAtomicPropositions = model.getStateLabeling().getLabels(); + } + } + + template + void BisimulationDecomposition::computeBisimulationDecomposition() { + std::chrono::high_resolution_clock::time_point totalStart = std::chrono::high_resolution_clock::now(); + + std::chrono::high_resolution_clock::time_point refinementStart = std::chrono::high_resolution_clock::now(); + this->performPartitionRefinement(); + std::chrono::high_resolution_clock::duration refinementTime = std::chrono::high_resolution_clock::now() - refinementStart; + + std::chrono::high_resolution_clock::time_point extractionStart = std::chrono::high_resolution_clock::now(); + this->extractDecompositionBlocks(); + std::chrono::high_resolution_clock::duration extractionTime = std::chrono::high_resolution_clock::now() - extractionStart; + + std::chrono::high_resolution_clock::time_point quotientBuildStart = std::chrono::high_resolution_clock::now(); + if (options.buildQuotient) { + this->buildQuotient(); + } + std::chrono::high_resolution_clock::duration quotientBuildTime = std::chrono::high_resolution_clock::now() - quotientBuildStart; + + std::chrono::high_resolution_clock::duration totalTime = std::chrono::high_resolution_clock::now() - totalStart; + + if (storm::settings::generalSettings().isShowStatisticsSet()) { + std::chrono::milliseconds refinementTimeInMilliseconds = std::chrono::duration_cast(refinementTime); + std::chrono::milliseconds extractionTimeInMilliseconds = std::chrono::duration_cast(extractionTime); + std::chrono::milliseconds quotientBuildTimeInMilliseconds = std::chrono::duration_cast(quotientBuildTime); + std::chrono::milliseconds totalTimeInMilliseconds = std::chrono::duration_cast(totalTime); + std::cout << std::endl; + std::cout << "Time breakdown:" << std::endl; + std::cout << " * time for partitioning: " << refinementTimeInMilliseconds.count() << "ms" << std::endl; + std::cout << " * time for extraction: " << extractionTimeInMilliseconds.count() << "ms" << std::endl; + std::cout << " * time for building quotient: " << quotientBuildTimeInMilliseconds.count() << "ms" << std::endl; + std::cout << "------------------------------------------" << std::endl; + std::cout << " * total time: " << totalTimeInMilliseconds.count() << "ms" << std::endl; + std::cout << std::endl; + } + } + + template + void BisimulationDecomposition::performPartitionRefinement() { + // Insert all blocks into the splitter queue that are initially marked as being a (potential) splitter. + std::deque splitterQueue; + std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (Block& a) { if (a.isMarkedAsSplitter()) { splitterQueue.push_back(&a); } } ); + + // Then perform the actual splitting until there are no more splitters. + while (!splitterQueue.empty()) { + // Optionally: sort the splitter queue according to some criterion (here: prefer small splitters). + std::sort(splitterQueue.begin(), splitterQueue.end(), [] (Block const* b1, Block const* b2) { return b1->getNumberOfStates() < b2->getNumberOfStates(); } ); + + // Get and prepare the next splitter. + Block* splitter = splitterQueue.front(); + splitterQueue.pop_front(); + splitter->unmarkAsSplitter(); + + // Now refine the partition using the current splitter. + refinePartitionBasedOnSplitter(*splitter, splitterQueue); + } + } + + template + std::shared_ptr BisimulationDecomposition::getQuotient() const { + STORM_LOG_THROW(this->quotient != nullptr, storm::exceptions::IllegalFunctionCallException, "Unable to retrieve quotient model from bisimulation decomposition, because it was not built."); + return this->quotient; + } + + template + void BisimulationDecomposition::splitInitialPartitionBasedOnStateRewards() { + std::vector const& stateRewardVector = model.getUniqueRewardModel()->second.getStateRewardVector(); + partition.split([&stateRewardVector] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return stateRewardVector[a] < stateRewardVector[b]; }, + [&stateRewardVector] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return stateRewardVector[a] != stateRewardVector[b]; }); + } + + template + void BisimulationDecomposition::initializeLabelBasedPartition() { + + partition = storm::storage::bisimulation::Partition(model.getNumberOfStates()); + for (auto const& label : options.respectedAtomicPropositions.get()) { + if (label == "init") { + continue; + } + partition.splitStates(model.getStates(label)); + } + + // If the model has state rewards, we need to consider them, because otherwise reward properties are not + // preserved. + if (options.keepRewards && model.hasRewardModel()) { + this->splitInitialPartitionBasedOnStateRewards(); + } + } + + template + void BisimulationDecomposition::initializeMeasureDrivenPartition() { + std::pair statesWithProbability01 = this->getStatesWithProbability01(backwardTransitions, options.phiStates.get(), options.psiStates.get()); + + boost::optional representativePsiState; + if (!options.psiStates.get().empty()) { + representativePsiState = *options.psiStates.get().begin(); + } + + partition = storm::storage::bisimulation::Partition(model.getNumberOfStates(), statesWithProbability01.first, options.bounded || options.keepRewards ? options.psiStates.get() : statesWithProbability01.second, representativePsiState); + + // If the model has state rewards, we need to consider them, because otherwise reward properties are not + // preserved. + if (options.keepRewards && model.hasRewardModel()) { + this->splitInitialPartitionBasedOnStateRewards(); + } + } + + template + void BisimulationDecomposition::extractDecompositionBlocks() { + // Now move the states from the internal partition into their final place in the decomposition. We do so in + // a way that maintains the block IDs as indices. + this->blocks.resize(partition.size()); + for (auto const& block : partition.getBlocks()) { + // We need to sort the states to allow for rapid construction of the blocks. + partition.sortBlock(block); + + // Convert the state-value-pairs to states only. + this->blocks[block.getId()] = block_type(partition.begin(block), partition.end(block), true); + } + } + + template class BisimulationDecomposition>; + } +} diff --git a/src/storage/bisimulation/BisimulationDecomposition.h b/src/storage/bisimulation/BisimulationDecomposition.h new file mode 100644 index 000000000..8796b5267 --- /dev/null +++ b/src/storage/bisimulation/BisimulationDecomposition.h @@ -0,0 +1,222 @@ +#ifndef STORM_STORAGE_BISIMULATIONDECOMPOSITION_H_ +#define STORM_STORAGE_BISIMULATIONDECOMPOSITION_H_ + +#include + +#include "src/storage/sparse/StateType.h" +#include "src/storage/Decomposition.h" +#include "src/storage/StateBlock.h" +#include "src/storage/bisimulation/Partition.h" + +#include "src/logic/Formulas.h" + +#include "src/utility/constants.h" +#include "src/utility/ConstantsComparator.h" + +namespace storm { + namespace utility { + template class ConstantsComparator; + } + + namespace logic { + class Formula; + } + + namespace storage { + + enum class BisimulationType { Strong, Weak }; + + /*! + * This class is the superclass of all decompositions of a sparse model into its bisimulation quotient. + */ + template + class BisimulationDecomposition : public Decomposition { + public: + typedef typename ModelType::ValueType ValueType; + typedef typename ModelType::RewardModelType RewardModelType; + + // A class that offers the possibility to customize the bisimulation. + struct Options { + // Creates an object representing the default values for all options. + Options(); + + /*! + * Creates an object representing the options necessary to obtain the quotient while still preserving + * the given formula. + * + * @param The model for which the quotient model shall be computed. This needs to be given in order to + * derive a suitable initial partition. + * @param formula The formula that is to be preserved. + */ + Options(ModelType const& model, storm::logic::Formula const& formula); + + /*! + * Creates an object representing the options necessary to obtain the smallest quotient while still + * preserving the given formulas. + * + * @param The model for which the quotient model shall be computed. This needs to be given in order to + * derive a suitable initial partition. + * @param formulas The formulas that need to be preserved. + */ + Options(ModelType const& model, std::vector> const& formulas); + + /*! + * Changes the options in a way that the given formula is preserved. + * + * @param model The model for which to preserve the formula. + * @param formula The only formula to check. + */ + void preserveFormula(ModelType const& model, storm::logic::Formula const& formula); + + // A flag that indicates whether a measure driven initial partition is to be used. If this flag is set + // to true, the two optional pairs phiStatesAndLabel and psiStatesAndLabel must be set. Then, the + // measure driven initial partition wrt. to the states phi and psi is taken. + bool measureDrivenInitialPartition; + boost::optional phiStates; + boost::optional psiStates; + + // An optional set of strings that indicate which of the atomic propositions of the model are to be + // respected and which may be ignored. If not given, all atomic propositions of the model are respected. + boost::optional> respectedAtomicPropositions; + + // A flag that indicates whether or not the state-rewards of the model are to be respected (and should + // be kept in the quotient model, if one is built). + bool keepRewards; + + // A flag that indicates whether a strong or a weak bisimulation is to be computed. + BisimulationType type; + + // A flag that indicates whether step-bounded properties are to be preserved. This may only be set to tru + // when computing strong bisimulation equivalence. + bool bounded; + + // A flag that governs whether the quotient model is actually built or only the decomposition is computed. + bool buildQuotient; + + private: + /*! + * Sets the options under the assumption that the given formula is the only one that is to be checked. + * + * @param model The model for which to preserve the formula. + * @param formula The only formula to check. + */ + void preserveSingleFormula(ModelType const& model, storm::logic::Formula const& formula); + + /*! + * Adds the given expressions and labels to the set of respected atomic propositions. + * + * @param expressions The expressions to respect. + * @param labels The labels to respect. + */ + void addToRespectedAtomicPropositions(std::vector> const& expressions, std::vector> const& labels); + + /* + * Checks whether a measure driven partition is possible with the given formula and sets the necessary + * data if this is the case. + * + * @param model The model for which to derive the data. + * @param formula The formula for which to derive the data for the measure driven initial partition (if + * applicable). + */ + void checkAndSetMeasureDrivenInitialPartition(ModelType const& model, storm::logic::Formula const& formula); + }; + + /*! + * Decomposes the given model into equivalance classes of a bisimulation. + * + * @param model The model to decompose. + * @param type The type of the bisimulation to compute. + * @param buildQuotient A flag specifying whether the quotient is to be build. + */ + BisimulationDecomposition(ModelType const& model, Options const& options); + + /*! + * Retrieves the quotient of the model under the computed bisimulation. + * + * @return The quotient model. + */ + std::shared_ptr getQuotient() const; + + protected: + /*! + * Computes the decomposition of the model into bisimulation equivalence classes. If requested, a quotient + * model is built. + */ + void computeBisimulationDecomposition(); + + /*! + * Performs the partition refinement on the model and thereby computes the equivalence classes under strong + * bisimulation equivalence. If required, the quotient model is built and may be retrieved using + * getQuotient(). + */ + void performPartitionRefinement(); + + /*! + * Refines the partition by considering the given splitter. All blocks that become potential splitters + * because of this refinement, are marked as splitters and inserted into the splitter queue. + * + * @param splitter The splitter to use. + * @param splitterQueue The queue into which to insert the newly discovered potential splitters. + */ + virtual void refinePartitionBasedOnSplitter(bisimulation::Block const& splitter, std::deque& splitterQueue); + + /*! + * Builds the quotient model based on the previously computed equivalence classes (stored in the blocks + * of the decomposition. + */ + virtual void buildQuotient() = 0; + + /*! + * Initializes the initial partition based on all respected labels. + */ + virtual void initializeLabelBasedPartition(); + + /*! + * Creates the measure-driven initial partition for reaching psi states from phi states. + */ + virtual void initializeMeasureDrivenPartition(); + + /*! + * Computes the set of states with probability 0/1 for satisfying phi until psi. This is used for the measure + * driven initial partition. + * + * @param backwardTransitions The backward transitions of the model. + * @param phiStates The phi states. + * @param psiStates The psi states. + * @return The states with probability 0 and 1. + */ + virtual std::pair getStatesWithProbability01(storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) = 0; + + /*! + * Splits the initial partition based on the (unique) state reward vector of the model. + */ + virtual void splitInitialPartitionBasedOnStateRewards(); + + /*! + * Constructs the blocks of the decomposition object based on the current partition. + */ + void extractDecompositionBlocks(); + + // The model to decompose. + ModelType const& model; + + // The backward transitions of the model. + storm::storage::SparseMatrix backwardTransitions; + + // The options used during construction. + Options options; + + // The current partition (used by partition refinement). + storm::storage::bisimulation::Partition partition; + + // A comparator used for comparing the distances of constants. + storm::utility::ConstantsComparator comparator; + + // The quotient, if it was build. Otherwhise a null pointer. + std::shared_ptr quotient; + }; + } +} + + +#endif /* STORM_STORAGE_BISIMULATIONDECOMPOSITION_H_ */ diff --git a/src/storage/bisimulation/Block.cpp b/src/storage/bisimulation/Block.cpp new file mode 100644 index 000000000..66183764c --- /dev/null +++ b/src/storage/bisimulation/Block.cpp @@ -0,0 +1,132 @@ +#include "src/storage/bisimulation/Block.h" + +#include +#include + +#include "src/storage/bisimulation/Partition.h" + +#include "src/utility/macros.h" + +namespace storm { + namespace storage { + namespace bisimulation { + + Block::Block(storm::storage::sparse::state_type beginIndex, storm::storage::sparse::state_type endIndex, Block* previousBlock, Block* nextBlock, std::size_t id) : nextBlock(nextBlock), previousBlock(previousBlock), beginIndex(beginIndex), endIndex(endIndex), markedAsSplitter(false), markedAsPredecessorBlock(false), absorbing(false), id(id) { + if (nextBlock != nullptr) { + nextBlock->previousBlock = this; + } + if (previousBlock != nullptr) { + previousBlock->nextBlock = this; + } + STORM_LOG_ASSERT(this->beginIndex < this->endIndex, "Unable to create block of illegal size."); + } + + void Block::print(Partition const& partition) const { + std::cout << "block " << this->id << " from " << this->beginIndex << " to " << this->endIndex; + } + + void Block::setBeginIndex(storm::storage::sparse::state_type beginIndex) { + this->beginIndex = beginIndex; + STORM_LOG_ASSERT(beginIndex < endIndex, "Unable to resize block to illegal size."); + } + + void Block::setEndIndex(storm::storage::sparse::state_type endIndex) { + this->endIndex = endIndex; + STORM_LOG_ASSERT(beginIndex < endIndex, "Unable to resize block to illegal size."); + } + + storm::storage::sparse::state_type Block::getBeginIndex() const { + return this->beginIndex; + } + + storm::storage::sparse::state_type Block::getEndIndex() const { + return this->endIndex; + } + + Block const& Block::getNextBlock() const { + return *this->nextBlock; + } + + bool Block::hasNextBlock() const { + return this->nextBlock != nullptr; + } + + Block const* Block::getNextBlockPointer() const { + return this->nextBlock; + } + + Block const& Block::getPreviousBlock() const { + return *this->previousBlock; + } + + Block const* Block::getPreviousBlockPointer() const { + return this->previousBlock; + } + + bool Block::hasPreviousBlock() const { + return this->previousBlock != nullptr; + } + + bool Block::check() const { + STORM_LOG_ASSERT(this->beginIndex < this->endIndex, "Block has negative size."); + STORM_LOG_ASSERT(!this->hasPreviousBlock() || this->getPreviousBlock().getNextBlockPointer() == this, "Illegal previous block."); + STORM_LOG_ASSERT(!this->hasNextBlock() || this->getNextBlock()().getPreviousBlockPointer() == this, "Illegal next block."); + return true; + } + + std::size_t Block::getNumberOfStates() const { + return (this->endIndex - this->beginIndex); + } + + bool Block::isMarkedAsSplitter() const { + return this->markedAsSplitter; + } + + void Block::markAsSplitter() { + this->markedAsSplitter = true; + } + + void Block::unmarkAsSplitter() { + this->markedAsSplitter = false; + } + + std::size_t Block::getId() const { + return this->id; + } + + void Block::setAbsorbing(bool absorbing) { + this->absorbing = absorbing; + } + + bool Block::isAbsorbing() const { + return this->absorbing; + } + + void Block::setRepresentativeState(storm::storage::sparse::state_type representativeState) { + this->representativeState = representativeState; + } + + bool Block::hasRepresentativeState() const { + return static_cast(representativeState); + } + + storm::storage::sparse::state_type Block::getRepresentativeState() const { + STORM_LOG_THROW(representativeState, storm::exceptions::InvalidOperationException, "Unable to retrieve representative state for block."); + return representativeState.get(); + } + + void Block::markAsPredecessorBlock() { + this->markedAsPredecessorBlock = true; + } + + void Block::unmarkAsPredecessorBlock() { + this->markedAsPredecessorBlock = false; + } + + bool Block::isMarkedAsPredecessor() const { + return markedAsPredecessorBlock; + } + + } + } +} \ No newline at end of file diff --git a/src/storage/bisimulation/Block.h b/src/storage/bisimulation/Block.h new file mode 100644 index 000000000..0758b1658 --- /dev/null +++ b/src/storage/bisimulation/Block.h @@ -0,0 +1,132 @@ +#ifndef STORM_STORAGE_BISIMULATION_BLOCK_H_ +#define STORM_STORAGE_BISIMULATION_BLOCK_H_ + +#include +#include + +#include "src/storage/sparse/StateType.h" + +namespace storm { + namespace storage { + namespace bisimulation { + // Forward-declare partition class. + class Partition; + + class Block { + public: + friend class Partition; + + // Creates a new block with the given begin and end. + Block(storm::storage::sparse::state_type beginIndex, storm::storage::sparse::state_type endIndex, Block* previous, Block* next, std::size_t id); + + Block() = default; + Block(Block const& other) = default; + Block& operator=(Block const& other) = default; + Block(Block&& other) = default; + Block& operator=(Block&& other) = default; + + // Prints the block to the standard output. + void print(Partition const& partition) const; + + // Returns the beginning index of the block. + storm::storage::sparse::state_type getBeginIndex() const; + + // Returns the beginning index of the block. + storm::storage::sparse::state_type getEndIndex() const; + + // Gets the next block (if there is one). + Block const& getNextBlock() const; + + // Gets a pointer to the next block (if there is one). + Block const* getNextBlockPointer() const; + + // Retrieves whether the block as a successor block. + bool hasNextBlock() const; + + // Gets the next block (if there is one). + Block const& getPreviousBlock() const; + + // Gets a pointer to the previous block (if there is one). + Block const* getPreviousBlockPointer() const; + + // Retrieves whether the block as a successor block. + bool hasPreviousBlock() const; + + // Checks consistency of the information in the block. + bool check() const; + + // Retrieves the number of states in this block. + std::size_t getNumberOfStates() const; + + // Checks whether the block is marked as a splitter. + bool isMarkedAsSplitter() const; + + // Marks the block as being a splitter. + void markAsSplitter(); + + // Removes the mark. + void unmarkAsSplitter(); + + // Retrieves the ID of the block. + std::size_t getId() const; + + // Retrieves whether the block is marked as a predecessor. + bool isMarkedAsPredecessor() const; + + // Marks the block as being a predecessor block. + void markAsPredecessorBlock(); + + // Removes the marking. + void unmarkAsPredecessorBlock(); + + // Sets whether or not the block is to be interpreted as absorbing. + void setAbsorbing(bool absorbing); + + // Retrieves whether the block is to be interpreted as absorbing. + bool isAbsorbing() const; + + // Sets the representative state of this block + void setRepresentativeState(storm::storage::sparse::state_type representativeState); + + // Retrieves whether this block has a representative state. + bool hasRepresentativeState() const; + + // Retrieves the representative state for this block. + storm::storage::sparse::state_type getRepresentativeState() const; + + private: + // Sets the beginning index of the block. + void setBeginIndex(storm::storage::sparse::state_type beginIndex); + + // Sets the end index of the block. + void setEndIndex(storm::storage::sparse::state_type endIndex); + + // Pointers to the next and previous block. + Block* nextBlock; + Block* previousBlock; + + // The begin and end indices of the block in terms of the state vector of the partition. + storm::storage::sparse::state_type beginIndex; + storm::storage::sparse::state_type endIndex; + + // A field that can be used for marking the block. + bool markedAsSplitter; + + // A field that can be used for marking the block as a predecessor block. + bool markedAsPredecessorBlock; + + // A flag indicating whether the block is to be interpreted as absorbing or not. + bool absorbing; + + // The ID of the block. This is only used for debugging purposes. + std::size_t id; + + // An optional representative state for the block. If this is set, this state is used to derive the + // atomic propositions of the meta state in the quotient model. + boost::optional representativeState; + }; + } + } +} + +#endif /* STORM_STORAGE_BISIMULATION_BLOCK_H_ */ diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp new file mode 100644 index 000000000..a60262a15 --- /dev/null +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp @@ -0,0 +1,681 @@ +#include "src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h" + +#include +#include +#include +#include +#include + +#include "src/adapters/CarlAdapter.h" +#include "src/modelchecker/results/ExplicitQualitativeCheckResult.h" + +#include "src/models/sparse/Dtmc.h" +#include "src/models/sparse/Ctmc.h" +#include "src/models/sparse/StandardRewardModel.h" + +#include "src/utility/graph.h" +#include "src/utility/constants.h" +#include "src/utility/ConstantsComparator.h" +#include "src/exceptions/IllegalFunctionCallException.h" +#include "src/exceptions/InvalidOptionException.h" +#include "src/exceptions/InvalidArgumentException.h" + +#include "src/settings/SettingsManager.h" +#include "src/settings/modules/GeneralSettings.h" + +namespace storm { + namespace storage { + + using namespace bisimulation; + + template + DeterministicModelBisimulationDecomposition::DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options) : BisimulationDecomposition(model, options.weak ? BisimulationType::Weak : BisimulationType::Strong) { + STORM_LOG_THROW(!model.hasRewardModel() || model.hasUniqueRewardModel(), storm::exceptions::IllegalFunctionCallException, "Bisimulation currently only supports models with at most one reward model."); + STORM_LOG_THROW(!model.hasRewardModel() || model.getUniqueRewardModel()->second.hasOnlyStateRewards(), storm::exceptions::IllegalFunctionCallException, "Bisimulation is currently supported for models with state rewards only. Consider converting the transition rewards to state rewards (via suitable function calls)."); + STORM_LOG_THROW(!options.weak || !options.bounded, storm::exceptions::IllegalFunctionCallException, "Weak bisimulation cannot preserve bounded properties."); + + // Extract the set of respected atomic propositions from the options. + if (options.respectedAtomicPropositions) { + this->selectedAtomicPropositions = options.respectedAtomicPropositions.get(); + } else { + this->selectedAtomicPropositions = model.getStateLabeling().getLabels(); + } + + // initialize the initial partition. + if (options.measureDrivenInitialPartition) { + STORM_LOG_THROW(options.phiStates, storm::exceptions::InvalidOptionException, "Unable to compute measure-driven initial partition without phi states."); + STORM_LOG_THROW(options.psiStates, storm::exceptions::InvalidOptionException, "Unable to compute measure-driven initial partition without psi states."); + this->initializeMeasureDrivenPartition(); + } else { + this->initializeLabelBasedPartition(); + } + + this->computeBisimulationDecomposition(); + } + + template + std::pair DeterministicModelBisimulationDecomposition::getStatesWithProbability01(storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) { + return storm::utility::graph::performProb01(backwardTransitions, phiStates, psiStates); + } + + template + void DeterministicModelBisimulationDecomposition::splitOffDivergentStates() { + std::vector stateStack; + stateStack.reserve(this->model.getNumberOfStates()); + storm::storage::BitVector nondivergentStates(this->model.getNumberOfStates()); + + for (auto& block : this->partition.getBlocks()) { + nondivergentStates.clear(); + + for (auto stateIt = this->partition.begin(block), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) { + if (nondivergentStates.get(stateIt->first)) { + continue; + } + + // Now traverse the forward transitions of the current state and check whether there is a + // transition to some other block. + bool isDirectlyNonDivergent = false; + for (auto const& successor : this->model.getRows(stateIt->first)) { + // If there is such a transition, then we can mark all states in the current block that can + // reach the state as non-divergent. + if (&this->partition.getBlock(successor.getColumn()) != &block) { + isDirectlyNonDivergent = true; + break; + } + } + + if (isDirectlyNonDivergent) { + stateStack.push_back(stateIt->first); + + while (!stateStack.empty()) { + storm::storage::sparse::state_type currentState = stateStack.back(); + stateStack.pop_back(); + nondivergentStates.set(currentState); + + for (auto const& predecessor : this->backwardTransitions.getRow(currentState)) { + if (&this->partition.getBlock(predecessor.getColumn()) == &block && !nondivergentStates.get(predecessor.getColumn())) { + stateStack.push_back(predecessor.getColumn()); + } + } + } + } + } + + + if (nondivergentStates.getNumberOfSetBits() > 0 && nondivergentStates.getNumberOfSetBits() != block.getNumberOfStates()) { + // After performing the split, the current block will contain the divergent states only. + this->partition.splitStates(block, nondivergentStates); + + // Since the remaining states in the block are divergent, we can mark the block as absorbing. + // This also guarantees that the self-loop will be added to the state of the quotient + // representing this block of states. + block.setAbsorbing(true); + } else if (nondivergentStates.getNumberOfSetBits() == 0) { + // If there are only diverging states in the block, we need to make it absorbing. + block.setAbsorbing(true); + } + } + } + + template + void DeterministicModelBisimulationDecomposition::initializeSilentProbabilities() { + silentProbabilities.resize(this->model.getNumberOfStates(), storm::utility::zero()); + for (storm::storage::sparse::state_type state = 0; state < this->model.getNumberOfStates(); ++state) { + Block const* currentBlockPtr = &this->partition.getBlock(state); + for (auto const& successorEntry : this->model.getRows(state)) { + if (&this->partition.getBlock(successorEntry.getColumn()) == currentBlockPtr) { + silentProbabilities[state] += successorEntry.getValue(); + } + } + } + } + + template + void DeterministicModelBisimulationDecomposition::initializeWeakDtmcBisimulation() { + // If we are creating the initial partition for weak bisimulation on DTMCs, we need to (a) split off all + // divergent states of each initial block and (b) initialize the vector of silent probabilities. + this->splitOffDivergentStates(); + this->initializeSilentProbabilities(); + } + + template + void DeterministicModelBisimulationDecomposition::initializeMeasureDrivenPartition() { + BisimulationDecomposition::initializeMeasureDrivenPartition(); + + if (this->options.type == BisimulationType::Weak && this->model.getModelType() == ModelType::Dtmc) { + this->initializeWeakDtmcBisimulation(); + } + } + + template + void DeterministicModelBisimulationDecomposition::initializeLabelBasedPartition() { + BisimulationDecomposition::initializeLabelBasedPartition(); + + if (this->options.type == BisimulationType::Weak && this->model.getModelType() == ModelType::Dtmc) { + this->initializeWeakDtmcBisimulation(); + } + } + + + + template + void DeterministicModelBisimulationDecomposition::buildQuotient() { + // In order to create the quotient model, we need to construct + // (a) the new transition matrix, + // (b) the new labeling, + // (c) the new reward structures. + + // Prepare a matrix builder for (a). + storm::storage::SparseMatrixBuilder builder(this->size(), this->size()); + + // Prepare the new state labeling for (b). + storm::models::sparse::StateLabeling newLabeling(this->size()); + std::set atomicPropositionsSet = this->options.selectedAtomicPropositions.get(); + atomicPropositionsSet.insert("init"); + std::vector atomicPropositions = std::vector(atomicPropositionsSet.begin(), atomicPropositionsSet.end()); + for (auto const& ap : atomicPropositions) { + newLabeling.addLabel(ap); + } + + // If the model had state rewards, we need to build the state rewards for the quotient as well. + boost::optional> stateRewards; + if (this->options.keepRewards && this->model.hasRewardModel()) { + stateRewards = std::vector(this->blocks.size()); + } + + // Now build (a) and (b) by traversing all blocks. + for (uint_fast64_t blockIndex = 0; blockIndex < this->blocks.size(); ++blockIndex) { + auto const& block = this->blocks[blockIndex]; + + // Pick one representative state. For strong bisimulation it doesn't matter which state it is, because + // they all behave equally. + storm::storage::sparse::state_type representativeState = *block.begin(); + + // However, for weak bisimulation, we need to make sure the representative state is a non-silent one (if + // there is any such state). + if (this->options.type == BisimulationType::Weak) { + for (auto const& state : block) { + if (!partition.isSilent(state, comparator)) { + representativeState = state; + break; + } + } + } + + Block const& oldBlock = partition.getBlock(representativeState); + + // If the block is absorbing, we simply add a self-loop. + if (oldBlock.isAbsorbing()) { + builder.addNextValue(blockIndex, blockIndex, storm::utility::one()); + + // If the block has a special representative state, we retrieve it now. + if (oldBlock.hasRepresentativeState()) { + representativeState = oldBlock.getRepresentativeState(); + } + + // Add all of the selected atomic propositions that hold in the representative state to the state + // representing the block. + for (auto const& ap : atomicPropositions) { + if (model.getStateLabeling().getStateHasLabel(ap, representativeState)) { + newLabeling.addLabelToState(ap, blockIndex); + } + } + } else { + // Compute the outgoing transitions of the block. + std::map blockProbability; + for (auto const& entry : model.getTransitionMatrix().getRow(representativeState)) { + storm::storage::sparse::state_type targetBlock = partition.getBlock(entry.getColumn()).getId(); + + // If we are computing a weak bisimulation quotient, there is no need to add self-loops. + if ((bisimulationType == BisimulationType::WeakDtmc || bisimulationType == BisimulationType::WeakCtmc) && targetBlock == blockIndex) { + continue; + } + + auto probIterator = blockProbability.find(targetBlock); + if (probIterator != blockProbability.end()) { + probIterator->second += entry.getValue(); + } else { + blockProbability[targetBlock] = entry.getValue(); + } + } + + // Now add them to the actual matrix. + for (auto const& probabilityEntry : blockProbability) { + if (bisimulationType == BisimulationType::WeakDtmc) { + builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second / (storm::utility::one() - partition.getSilentProbability(representativeState))); + } else { + builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second); + } + } + + // Otherwise add all atomic propositions to the equivalence class that the representative state + // satisfies. + for (auto const& ap : atomicPropositions) { + if (model.getStateLabeling().getStateHasLabel(ap, representativeState)) { + newLabeling.addLabelToState(ap, blockIndex); + } + } + } + + // If the model has state rewards, we simply copy the state reward of the representative state, because + // all states in a block are guaranteed to have the same state reward. + if (keepRewards && model.hasRewardModel()) { + typename std::unordered_map::const_iterator nameRewardModelPair = model.getUniqueRewardModel(); + stateRewards.get()[blockIndex] = nameRewardModelPair->second.getStateRewardVector()[representativeState]; + } + } + + // Now check which of the blocks of the partition contain at least one initial state. + for (auto initialState : model.getInitialStates()) { + Block const& initialBlock = partition.getBlock(initialState); + newLabeling.addLabelToState("init", initialBlock.getId()); + } + + // Construct the reward model mapping. + std::unordered_map rewardModels; + if (keepRewards && model.hasRewardModel()) { + typename std::unordered_map::const_iterator nameRewardModelPair = model.getUniqueRewardModel(); + rewardModels.insert(std::make_pair(nameRewardModelPair->first, typename ModelType::RewardModelType(stateRewards))); + } + + // Finally construct the quotient model. + this->quotient = std::shared_ptr>(new ModelType(builder.build(), std::move(newLabeling), std::move(rewardModels))); + } + + template + void DeterministicModelBisimulationDecomposition::refineBlockProbabilities(Block& block, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator) { + // Sort the states in the block based on their probabilities. + std::sort(partition.getBegin(block), partition.getEnd(block), [&partition] (std::pair const& a, std::pair 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>::const_iterator begin = partition.getBegin(block); + typename std::vector>::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 + void DeterministicModelBisimulationDecomposition::refineBlockWeak(Block& block, Partition& partition, storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator) { + std::vector 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 stateLabels(block.getEnd() - block.getBegin(), storm::storage::BitVector(splitPoints.size() - 1)); + + std::vector 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 const& a, std::pair 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>::const_iterator begin = partition.getBegin(block); + typename std::vector>::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(); + 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(); + 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 + std::vector DeterministicModelBisimulationDecomposition::getSplitPointsWeak(Block& block, Partition& partition, storm::utility::ConstantsComparator const& comparator) { + std::vector 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& stateValuePair) { + ValueType const& silentProbability = partition.getSilentProbability(stateValuePair.first); + if (!comparator.isOne(silentProbability) && !comparator.isZero(silentProbability)) { + stateValuePair.second /= storm::utility::one() - silentProbability; + } + }); + + // Now sort the states based on their probabilities. + std::sort(partition.getStatesAndValues().begin() + block.getOriginalBegin(), partition.getStatesAndValues().begin() + block.getBegin(), [&partition] (std::pair const& a, std::pair 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>::const_iterator begin = partition.getStatesAndValues().begin() + block.getOriginalBegin(); + typename std::vector>::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 + void DeterministicModelBisimulationDecomposition::refinePartition(storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, Block& splitter, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator) { + std::list 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 blocksToSplit; + + // Now, we can iterate over the predecessor blocks and see whether we have to create a new block for + // predecessors of the splitter. + for (auto blockPtr : predecessorBlocks) { + Block& block = *blockPtr; + + block.unmarkAsPredecessorBlock(); + block.resetMarkedPosition(); + + // If we have moved the begin of the block to somewhere in the middle of the block, we need to split it. + if (block.getBegin() != block.getEnd()) { + Block& newBlock = partition.insertBlock(block); + if (!newBlock.isMarkedAsSplitter()) { + splitterQueue.push_back(&newBlock); + newBlock.markAsSplitter(); + } + + // Schedule the block of predecessors for refinement based on probabilities. + blocksToSplit.emplace_back(&newBlock); + } else { + // In this case, we can keep the block by setting its begin to the old value. + block.setBegin(block.getOriginalBegin()); + blocksToSplit.emplace_back(&block); + } + } + + // Finally, we walk through the blocks that have a transition to the splitter and split them using + // probabilistic information. + for (auto blockPtr : blocksToSplit) { + if (blockPtr->getNumberOfStates() <= 1) { + continue; + } + + // In the case of weak bisimulation for CTMCs, we don't need to make sure the rate of staying inside + // the own block is the same. + if (bisimulationType == BisimulationType::WeakCtmc && blockPtr == &splitter) { + continue; + } + + refineBlockProbabilities(*blockPtr, partition, bisimulationType, splitterQueue, comparator); + } + } else { // In this case, we are computing a weak bisimulation on a DTMC. + // If the splitter was a predecessor of itself and we are computing a weak bisimulation, we need to update + // the silent probabilities. + if (splitterIsPredecessor) { + partition.setSilentProbabilities(partition.getStatesAndValues().begin() + splitter.getOriginalBegin(), partition.getStatesAndValues().begin() + splitter.getBegin()); + partition.setSilentProbabilitiesToZero(partition.getStatesAndValues().begin() + splitter.getBegin(), partition.getStatesAndValues().begin() + splitter.getEnd()); + } + + // Now refine all predecessor blocks in a weak manner. That is, we split according to the criterion of + // weak bisimulation. + for (auto blockPtr : predecessorBlocks) { + Block& block = *blockPtr; + + // If the splitter is also the predecessor block, we must not refine it at this point. + if (&block != &splitter) { + refineBlockWeak(block, partition, forwardTransitions, backwardTransitions, splitterQueue, comparator); + } else { + // Restore the begin of the block. + block.setBegin(block.getOriginalBegin()); + } + + block.unmarkAsPredecessorBlock(); + block.resetMarkedPosition(); + } + } + + STORM_LOG_ASSERT(partition.check(), "Partition became inconsistent."); + } + + template class DeterministicModelBisimulationDecomposition; + +#ifdef STORM_HAVE_CARL + template class DeterministicModelBisimulationDecomposition; +#endif + } +} diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h new file mode 100644 index 000000000..9bdb1dd48 --- /dev/null +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h @@ -0,0 +1,142 @@ +#ifndef STORM_STORAGE_BISIMULATION_DETERMINISTICMODELBISIMULATIONDECOMPOSITION_H_ +#define STORM_STORAGE_BISIMULATION_DETERMINISTICMODELBISIMULATIONDECOMPOSITION_H_ + +#include "src/storage/bisimulation/BisimulationDecomposition.h" + +namespace storm { + namespace utility { + template class ConstantsComparator; + } + + namespace storage { + + /*! + * This class represents the decomposition of a deterministic model into its bisimulation quotient. + */ + template + class DeterministicModelBisimulationDecomposition : public BisimulationDecomposition { + public: + typedef typename ModelType::ValueType ValueType; + typedef typename ModelType::RewardModelType RewardModelType; + + /*! + * Computes the bisimulation relation for the given model. Which kind of bisimulation is computed, is + * customizable via the given options. + * + * @param model The model to decompose. + * @param options The options that customize the computed bisimulation. + */ + DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options = Options()); + + private: + virtual std::pair getStatesWithProbability01(storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) override; + + virtual void initializeLabelBasedPartition() override; + + /*! + * Performs the necessary steps to compute a weak bisimulation on a DTMC. + */ + void initializeWeakDtmcBisimulation(); + + /*! + * Splits all blocks of the current partition into a block that contains all divergent states and another + * block containing the non-divergent states. + */ + void splitOffDivergentStates(); + + /*! + * Initializes the vector of silent probabilities. + */ + void initializeSilentProbabilities(); + + virtual void initializeMeasureDrivenPartition() override; + + virtual void initializeLabelBasedPartition() override; + + virtual void buildQuotient() override; + + /*! + * Refines the partition based on the provided splitter. After calling this method all blocks are stable + * with respect to the splitter. + * + * @param forwardTransitions The forward transitions of the model. + * @param backwardTransitions A matrix that can be used to retrieve the predecessors (and their + * probabilities). + * @param splitter The splitter to use. + * @param partition The partition to split. + * @param bisimulationType The kind of bisimulation that is to be computed. + * @param splitterQueue A queue into which all blocks that were split are inserted so they can be treated + * as splitters in the future. + * @param comparator A comparator used for comparing constants. + */ + void refinePartition(storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, Block& splitter, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator); + + /*! + * Refines the block based on their probability values (leading into the splitter). + * + * @param block The block to refine. + * @param partition The partition that contains the block. + * @param bisimulationType The kind of bisimulation that is to be computed. + * @param splitterQueue A queue into which all blocks that were split are inserted so they can be treated + * as splitters in the future. + * @param comparator A comparator used for comparing constants. + */ + void refineBlockProbabilities(Block& block, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator); + + void refineBlockWeak(Block& block, Partition& partition, storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator); + + /*! + * Determines the split offsets in the given block. + * + * @param block The block that is to be analyzed for splits. + * @param partition The partition that contains the block. + * @param comparator A comparator used for comparing constants. + */ + std::vector getSplitPointsWeak(Block& block, Partition& partition, storm::utility::ConstantsComparator 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 + Partition getMeasureDrivenInitialPartition(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, BisimulationType bisimulationType, bool keepRewards = true, bool bounded = false, storm::utility::ConstantsComparator const& comparator = storm::utility::ConstantsComparator()); + + /*! + * 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 + Partition getLabelBasedInitialPartition(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, BisimulationType bisimulationType, std::set const& atomicPropositions, bool keepRewards = true, storm::utility::ConstantsComparator const& comparator = storm::utility::ConstantsComparator()); + + // A vector mapping each state to its silent probability. + std::vector silentProbabilities; + }; + } +} + +#endif /* STORM_STORAGE_BISIMULATION_DETERMINISTICMODELBISIMULATIONDECOMPOSITION_H_ */ \ No newline at end of file diff --git a/src/storage/bisimulation/Partition.cpp b/src/storage/bisimulation/Partition.cpp new file mode 100644 index 000000000..eade12b0d --- /dev/null +++ b/src/storage/bisimulation/Partition.cpp @@ -0,0 +1,273 @@ +#include "src/storage/bisimulation/Partition.h" + +#include + +#include "src/utility/macros.h" +#include "src/exceptions/InvalidArgumentException.h" + +namespace storm { + namespace storage { + namespace bisimulation { + Partition::Partition(std::size_t numberOfStates) : stateToBlockMapping(numberOfStates), states(numberOfStates), positions(numberOfStates) { + blocks.emplace_back(0, numberOfStates, nullptr, nullptr, blocks.size()); + + // Set up the different parts of the internal structure. + for (storm::storage::sparse::state_type state = 0; state < numberOfStates; ++state) { + states[state] = state; + positions[state] = state; + stateToBlockMapping[state] = &blocks.back(); + } + } + + Partition::Partition(std::size_t numberOfStates, storm::storage::BitVector const& prob0States, storm::storage::BitVector const& prob1States, boost::optional representativeProb1State) : stateToBlockMapping(numberOfStates), states(numberOfStates), positions(numberOfStates) { + storm::storage::sparse::state_type position = 0; + Block* firstBlock = nullptr; + Block* secondBlock = nullptr; + Block* thirdBlock = nullptr; + if (!prob0States.empty()) { + blocks.emplace_back(0, prob0States.getNumberOfSetBits(), nullptr, nullptr, blocks.size()); + firstBlock = &blocks[0]; + + for (auto state : prob0States) { + states[position] = state; + positions[state] = position; + stateToBlockMapping[state] = firstBlock; + ++position; + } + firstBlock->setAbsorbing(true); + } + + if (!prob1States.empty()) { + blocks.emplace_back(position, position + prob1States.getNumberOfSetBits(), firstBlock, nullptr, blocks.size()); + secondBlock = &blocks[1]; + + for (auto state : prob1States) { + states[position] = state; + positions[state] = position; + stateToBlockMapping[state] = secondBlock; + ++position; + } + secondBlock->setAbsorbing(true); + secondBlock->setRepresentativeState(representativeProb1State.get()); + } + + storm::storage::BitVector otherStates = ~(prob0States | prob1States); + if (!otherStates.empty()) { + blocks.emplace_back(position, numberOfStates, secondBlock, nullptr, blocks.size()); + thirdBlock = &blocks[2]; + + for (auto state : otherStates) { + states[position] = state; + positions[state] = position; + stateToBlockMapping[state] = thirdBlock; + ++position; + } + } + } + + void Partition::swapStates(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { + std::swap(this->states[this->positions[state1]], this->states[this->positions[state2]]); + std::swap(this->positions[state1], this->positions[state2]); + } + + void Partition::swapStatesAtPositions(storm::storage::sparse::state_type position1, storm::storage::sparse::state_type position2) { + storm::storage::sparse::state_type state1 = this->states[position1]; + storm::storage::sparse::state_type state2 = this->states[position2]; + + std::swap(this->states[position1], this->states[position2]); + this->positions[state1] = position2; + this->positions[state2] = position1; + } + + storm::storage::sparse::state_type const& Partition::getPosition(storm::storage::sparse::state_type state) const { + return this->positions[state]; + } + + void Partition::setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position) { + this->positions[state] = position; + } + + storm::storage::sparse::state_type const& Partition::getState(storm::storage::sparse::state_type position) const { + return this->states[position]; + } + + void Partition::mapStatesToBlock(Block& block, std::vector::iterator first, std::vector::iterator last) { + for (; first != last; ++first) { + this->stateToBlockMapping[*first] = █ + } + } + + void Partition::mapStatesToPositions(Block const& block) { + storm::storage::sparse::state_type position = block.getBeginIndex(); + for (auto stateIt = this->begin(block), stateIte = this->end(block); stateIt != stateIte; ++stateIt, ++position) { + this->positions[*stateIt] = position; + } + } + + Block& Partition::getBlock(storm::storage::sparse::state_type state) { + return *this->stateToBlockMapping[state]; + } + + Block const& Partition::getBlock(storm::storage::sparse::state_type state) const { + return *this->stateToBlockMapping[state]; + } + + std::vector::const_iterator Partition::begin(Block const& block) const { + return this->states.begin() + block.getBeginIndex(); + } + + std::vector::const_iterator Partition::end(Block const& block) const { + return this->states.begin() + block.getEndIndex(); + } + + Block& Partition::splitBlock(Block& block, storm::storage::sparse::state_type position) { + STORM_LOG_THROW(position >= block.getBeginIndex() && position <= block.getEndIndex(), storm::exceptions::InvalidArgumentException, "Cannot split block at illegal position."); + + // In case one of the resulting blocks would be empty, we simply return the current block and do not create + // a new one. + if (position == block.getBeginIndex() || position == block.getEndIndex()) { + return block; + } + + // Actually create the new block. + blocks.emplace_back(block.getBeginIndex(), position, block.getPreviousBlockPointer(), &block, blocks.size()); + Block& newBlock = blocks.back(); + + // Resize the current block appropriately. + block.setBeginIndex(position); + + // Mark both blocks as splitters. + block.markAsSplitter(); + newBlock.markAsSplitter(); + + // Update the mapping of the states in the newly created block. + this->mapStatesToBlock(newBlock, this->begin(newBlock), this->end(newBlock)); + + return newBlock; + } + + void Partition::splitBlock(Block& block, std::function const& lessFunction, std::function const& changedFunction) { + // Sort the range of the block such that all states that have the label are moved to the front. + std::sort(this->begin(block), this->end(block), lessFunction); + + // Update the positions vector. + mapStatesToPositions(block); + + // Now we can check whether the block needs to be split, which is the case iff the changed function returns + // true for the first and last element of the remaining state range. + storm::storage::sparse::state_type begin = block.getBeginIndex(); + storm::storage::sparse::state_type end = block.getEndIndex() - 1; + while (changedFunction(begin, end)) { + // Now we scan for the first state in the block for which the changed function returns true. + // Note that we do not have to check currentIndex for staying within bounds, because we know the matching + // state is within bounds. + storm::storage::sparse::state_type currentIndex = begin + 1; + while (begin != end && !changedFunction(states[begin], states[currentIndex])) { + ++currentIndex; + } + begin = currentIndex; + + this->splitBlock(block, currentIndex); + } + } + + void Partition::split(std::function const& lessFunction, std::function const& changedFunction) { + for (auto& block : blocks) { + splitBlock(block, lessFunction, changedFunction); + } + } + + Block& Partition::splitStates(Block& block, storm::storage::BitVector const& states) { + // Sort the range of the block such that all states that have the label are moved to the front. + std::sort(this->begin(block), this->end(block), [&states] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return states.get(a) && !states.get(b); } ); + + // Update the positions vector. + mapStatesToPositions(block); + + // Now we can find the first position in the block that does not have the label and create new blocks. + std::vector::const_iterator it = std::find_if(this->begin(block), this->end(block), [&states] (storm::storage::sparse::state_type const& a) { return !states.get(a); }); + + if (it != this->begin(block) && it != this->end(block)) { + auto cutPoint = std::distance(this->states.cbegin(), it); + return this->splitBlock(block, cutPoint); + } else { + return block; + } + } + + void Partition::splitStates(storm::storage::BitVector const& states) { + for (auto& block : blocks) { + splitStates(block, states); + } + } + + void Partition::sortBlock(Block const& block) { + std::sort(this->begin(block), this->end(block), [] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return a < b; }); + mapStatesToPositions(block); + } + + Block& Partition::insertBlock(Block& block) { + // Find the beginning of the new block. + storm::storage::sparse::state_type begin = block.hasPreviousBlock() ? block.getPreviousBlock().getEndIndex() : 0; + + // Actually insert the new block. + blocks.emplace_back(begin, block.getBeginIndex(), block.getPreviousBlockPointer(), &block, blocks.size()); + Block& newBlock = blocks.back(); + + // Update the mapping of the states in the newly created block. + for (auto it = this->begin(newBlock), ite = this->end(newBlock); it != ite; ++it) { + stateToBlockMapping[*it] = &newBlock; + } + + return newBlock; + } + + std::vector const& Partition::getBlocks() const { + return this->blocks; + } + + std::vector& Partition::getBlocks() { + return this->blocks; + } + + bool Partition::check() const { + for (uint_fast64_t state = 0; state < this->positions.size(); ++state) { + STORM_LOG_ASSERT(this->states[this->positions[state]] == state, "Position mapping corrupted."); + } + for (auto const& block : this->blocks) { + STORM_LOG_ASSERT(block.check(), "Block corrupted."); + for (auto stateIt = this->begin(block), stateIte = this->end(block); stateIt != stateIte; ++stateIt) { + STORM_LOG_ASSERT(this->stateToBlockMapping[*stateIt] == &block, "Block mapping corrupted."); + } + } + return true; + } + + void Partition::print() const { + for (auto const& block : this->blocks) { + block.print(*this); + } + std::cout << "states in partition" << std::endl; + for (auto const& state : states) { + std::cout << state << " "; + } + std::cout << std::endl << "positions: " << std::endl; + for (auto const& index : positions) { + std::cout << index << " "; + } + std::cout << std::endl << "state to block mapping: " << std::endl; + for (auto const& block : stateToBlockMapping) { + std::cout << block << "[" << block->getId() <<"] "; + } + std::cout << std::endl; + std::cout << "size: " << blocks.size() << std::endl; + STORM_LOG_ASSERT(this->check(), "Partition corrupted."); + } + + std::size_t Partition::size() const { + return blocks.size(); + } + + } + } +} \ No newline at end of file diff --git a/src/storage/bisimulation/Partition.h b/src/storage/bisimulation/Partition.h new file mode 100644 index 000000000..6bd2cd8e1 --- /dev/null +++ b/src/storage/bisimulation/Partition.h @@ -0,0 +1,142 @@ +#ifndef STORM_STORAGE_BISIMULATION_PARTITION_H_ +#define STORM_STORAGE_BISIMULATION_PARTITION_H_ + +#include +#include + +#include "src/storage/bisimulation/Block.h" + +#include "src/storage/BitVector.h" + +namespace storm { + namespace storage { + namespace bisimulation { + + class Partition { + public: + /*! + * Creates a partition with one block consisting of all the states. + * + * @param numberOfStates The number of states the partition holds. + */ + Partition(std::size_t numberOfStates); + + /*! + * Creates a partition with three blocks: one with all phi states, one with all psi states and one with + * all other states. The former two blocks are marked as being absorbing, because their outgoing + * transitions shall not be taken into account for future refinement. + * + * @param numberOfStates The number of states the partition holds. + * @param prob0States The states which have probability 0 of satisfying phi until psi. + * @param prob1States The states which have probability 1 of satisfying phi until psi. + * @param representativeProb1State If the set of prob1States is non-empty, this needs to be a state + * that is representative for this block in the sense that the state representing this block in the quotient + * model will receive exactly the atomic propositions of the representative state. + */ + Partition(std::size_t numberOfStates, storm::storage::BitVector const& prob0States, storm::storage::BitVector const& prob1States, boost::optional representativeProb1State); + + Partition() = default; + Partition(Partition const& other) = default; + Partition& operator=(Partition const& other) = default; + Partition(Partition&& other) = default; + Partition& operator=(Partition&& other) = default; + + // Retrieves the size of the partition, i.e. the number of blocks. + std::size_t size() const; + + // Prints the partition to the standard output. + void print() const; + + // Splits the block at the given position and inserts a new block after the current one holding the rest + // of the states. + Block& splitBlock(Block& block, storm::storage::sparse::state_type position); + + // Splits the block by sorting the states according to the given function and then identifying the split + // points via the changed-function. + void splitBlock(Block& block, std::function const& lessFunction, std::function const& changedFunction); + + // Splits all blocks by using the sorting-based splitting. + void split(std::function const& lessFunction, std::function const& changedFunction); + + // Splits the block such that the resulting blocks contain only states in the given set or none at all. + // If the block is split, the given block will contain the states *not* in the given set and the newly + // created block will contain the states *in* the given set. + Block& splitStates(Block& block, storm::storage::BitVector const& states); + + /*! + * Splits all blocks of the partition such that afterwards all blocks contain only states within the given + * set of states or no such state at all. + */ + void splitStates(storm::storage::BitVector const& states); + + // Sorts the block based on the state indices. + void sortBlock(Block const& block); + + // Retrieves the blocks of the partition. + std::vector const& getBlocks() const; + + // Retrieves the blocks of the partition. + std::vector& 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::const_iterator begin(Block const& block) const; + + // Returns an iterator to the beginning of the states of the given block. + std::vector::const_iterator end(Block const& block) const; + + // Swaps the positions of the two given states. + void swapStates(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2); + + // Retrieves the block of the given state. + Block& getBlock(storm::storage::sparse::state_type state); + + // Retrieves the block of the given state. + Block const& getBlock(storm::storage::sparse::state_type state) const; + + // Retrieves the position of the given state. + storm::storage::sparse::state_type const& getPosition(storm::storage::sparse::state_type state) const; + + // Sets the position of the state to the given position. + storm::storage::sparse::state_type const& getState(storm::storage::sparse::state_type position) const; + + // Updates the block mapping for the given range of states to the specified block. + void mapStatesToBlock(Block& block, std::vector::iterator first, std::vector::iterator last); + + // Update the state to position for the states in the given block. + void mapStatesToPositions(Block const& block); + + private: + // FIXME: necessary? + // Swaps the positions of the two states given by their positions. + void swapStatesAtPositions(storm::storage::sparse::state_type position1, storm::storage::sparse::state_type position2); + + // FIXME: necessary? + // Inserts a block before the given block. The new block will cover all states between the beginning + // of the given block and the end of the previous block. + Block& insertBlock(Block& block); + + // FIXME: necessary? + // Sets the position of the given state. + void setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position); + + // The of blocks in the partition. + std::vector blocks; + + // A mapping of states to their blocks. + std::vector stateToBlockMapping; + + // A vector containing all the states. It is ordered in a way such that the blocks only need to define + // their start/end indices. + std::vector states; + + // This vector keeps track of the position of each state in the state vector. + std::vector positions; + }; + } + } +} + +#endif /* STORM_STORAGE_BISIMULATION_PARTITION_H_ */ diff --git a/src/storage/expressions/Expression.cpp b/src/storage/expressions/Expression.cpp index 725a41392..96cf57a31 100644 --- a/src/storage/expressions/Expression.cpp +++ b/src/storage/expressions/Expression.cpp @@ -162,6 +162,12 @@ namespace storm { return this->getBaseExpression().accept(visitor); } + std::string Expression::toString() { + std::stringstream stream; + stream << *this; + return stream.str(); + } + std::ostream& operator<<(std::ostream& stream, Expression const& expression) { stream << expression.getBaseExpression(); return stream; diff --git a/src/storage/expressions/Expression.h b/src/storage/expressions/Expression.h index c6ecdcbec..e8fe0e60b 100644 --- a/src/storage/expressions/Expression.h +++ b/src/storage/expressions/Expression.h @@ -292,6 +292,13 @@ namespace storm { */ boost::any accept(ExpressionVisitor& visitor) const; + /*! + * Converts the expression into a string. + * + * @return The string representation of the expression. + */ + std::string toString(); + friend std::ostream& operator<<(std::ostream& stream, Expression const& expression); private: diff --git a/src/utility/storm.h b/src/utility/storm.h index be76720fe..3281d342b 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -43,7 +43,7 @@ #include "src/builder/DdPrismModelBuilder.h" // Headers for model processing. -#include "src/storage/DeterministicModelBisimulationDecomposition.h" +#include "src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h" // Headers for model checking. #include "src/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" diff --git a/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp b/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp index 5081e9e02..eeb7298b7 100644 --- a/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp +++ b/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp @@ -1,7 +1,7 @@ #include "gtest/gtest.h" #include "storm-config.h" #include "src/parser/AutoParser.h" -#include "src/storage/DeterministicModelBisimulationDecomposition.h" +#include "src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h" #include "src/models/sparse/StandardRewardModel.h" TEST(DeterministicModelBisimulationDecomposition, Die) { From 11c21eb33869bd9db63f71803c9e9292ee2c3859 Mon Sep 17 00:00:00 2001 From: dehnert Date: Sun, 25 Oct 2015 22:06:58 +0100 Subject: [PATCH 02/24] on my way of making (the refactored version) bisimulation work again for deterministic models Former-commit-id: 79c089a693ad3c63234f540322d709435dda53b4 --- src/models/sparse/Model.h | 1 - .../BisimulationDecomposition.cpp | 41 +- .../bisimulation/BisimulationDecomposition.h | 9 +- src/storage/bisimulation/Block.cpp | 41 +- src/storage/bisimulation/Block.h | 22 +- ...ministicModelBisimulationDecomposition.cpp | 585 +++++------------- ...erministicModelBisimulationDecomposition.h | 106 +--- src/storage/bisimulation/Partition.cpp | 139 +++-- src/storage/bisimulation/Partition.h | 25 +- ...sticModelBisimulationDecompositionTest.cpp | 167 ++--- 10 files changed, 431 insertions(+), 705 deletions(-) diff --git a/src/models/sparse/Model.h b/src/models/sparse/Model.h index ce5a85b89..3d7776800 100644 --- a/src/models/sparse/Model.h +++ b/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. * diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp index 06b33f55e..ba35bc455 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.cpp +++ b/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 void BisimulationDecomposition::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(initialPartitionTime); std::chrono::milliseconds refinementTimeInMilliseconds = std::chrono::duration_cast(refinementTime); std::chrono::milliseconds extractionTimeInMilliseconds = std::chrono::duration_cast(extractionTime); std::chrono::milliseconds quotientBuildTimeInMilliseconds = std::chrono::duration_cast(quotientBuildTime); std::chrono::milliseconds totalTimeInMilliseconds = std::chrono::duration_cast(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::performPartitionRefinement() { // Insert all blocks into the splitter queue that are initially marked as being a (potential) splitter. std::deque 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 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 void BisimulationDecomposition::splitInitialPartitionBasedOnStateRewards() { std::vector 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 void BisimulationDecomposition::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 void BisimulationDecomposition::initializeMeasureDrivenPartition() { - std::pair statesWithProbability01 = this->getStatesWithProbability01(backwardTransitions, options.phiStates.get(), options.psiStates.get()); + std::pair statesWithProbability01 = this->getStatesWithProbability01(); boost::optional 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 @@ -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>; + +#ifdef STORM_HAVE_CARL + template class BisimulationDecomposition>; +#endif } } diff --git a/src/storage/bisimulation/BisimulationDecomposition.h b/src/storage/bisimulation/BisimulationDecomposition.h index 8796b5267..305638438 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.h +++ b/src/storage/bisimulation/BisimulationDecomposition.h @@ -137,13 +137,13 @@ namespace storm { */ std::shared_ptr 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& splitterQueue); + virtual void refinePartitionBasedOnSplitter(bisimulation::Block const& splitter, std::deque& 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 getStatesWithProbability01(storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) = 0; + virtual std::pair getStatesWithProbability01() = 0; /*! * Splits the initial partition based on the (unique) state reward vector of the model. diff --git a/src/storage/bisimulation/Block.cpp b/src/storage/bisimulation/Block.cpp index 66183764c..2157baeb3 100644 --- a/src/storage/bisimulation/Block.cpp +++ b/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; } - } } } \ No newline at end of file diff --git a/src/storage/bisimulation/Block.h b/src/storage/bisimulation/Block.h index 0758b1658..304da118a 100644 --- a/src/storage/bisimulation/Block.h +++ b/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; diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp index a60262a15..c30058475 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp +++ b/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 - DeterministicModelBisimulationDecomposition::DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options) : BisimulationDecomposition(model, options.weak ? BisimulationType::Weak : BisimulationType::Strong) { + DeterministicModelBisimulationDecomposition::DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options) : BisimulationDecomposition(model, options), probabilitiesToCurrentSplitter(model.getNumberOfStates(), storm::utility::zero()), 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 - std::pair DeterministicModelBisimulationDecomposition::getStatesWithProbability01(storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) { - return storm::utility::graph::performProb01(backwardTransitions, phiStates, psiStates); + std::pair DeterministicModelBisimulationDecomposition::getStatesWithProbability01() { + return storm::utility::graph::performProb01(this->backwardTransitions, this->options.phiStates.get(), this->options.psiStates.get()); } template @@ -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 void DeterministicModelBisimulationDecomposition::initializeMeasureDrivenPartition() { BisimulationDecomposition::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::initializeLabelBasedPartition() { BisimulationDecomposition::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 DeterministicModelBisimulationDecomposition::ValueType const& DeterministicModelBisimulationDecomposition::getProbabilityToSplitter(storm::storage::sparse::state_type const& state) const { + return probabilitiesToCurrentSplitter[state]; + } + + template + bool DeterministicModelBisimulationDecomposition::isSilent(storm::storage::sparse::state_type const& state) const { + return this->comparator.isOne(silentProbabilities[state]); + } + + template + typename DeterministicModelBisimulationDecomposition::ValueType DeterministicModelBisimulationDecomposition::getSilentProbability(storm::storage::sparse::state_type const& state) const { + return silentProbabilities[state]; + } + + template + bool DeterministicModelBisimulationDecomposition::isPredecessorOfCurrentSplitter(storm::storage::sparse::state_type const& state) const { + return this->predecessorsOfCurrentSplitter.get(state); + } + template + void DeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitter(std::list& predecessorBlocks, std::deque& 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 + void DeterministicModelBisimulationDecomposition::refinePartitionBasedOnSplitter(bisimulation::Block const& splitter, std::deque& 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 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 void DeterministicModelBisimulationDecomposition::buildQuotient() { @@ -170,7 +251,7 @@ namespace storm { // Prepare the new state labeling for (b). storm::models::sparse::StateLabeling newLabeling(this->size()); - std::set atomicPropositionsSet = this->options.selectedAtomicPropositions.get(); + std::set atomicPropositionsSet = this->options.respectedAtomicPropositions.get(); atomicPropositionsSet.insert("init"); std::vector atomicPropositions = std::vector(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 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() - 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() - 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::const_iterator nameRewardModelPair = model.getUniqueRewardModel(); + if (this->options.keepRewards && this->model.hasRewardModel()) { + typename std::unordered_map::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 rewardModels; - if (keepRewards && model.hasRewardModel()) { - typename std::unordered_map::const_iterator nameRewardModelPair = model.getUniqueRewardModel(); + if (this->options.keepRewards && this->model.hasRewardModel()) { + typename std::unordered_map::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>(new ModelType(builder.build(), std::move(newLabeling), std::move(rewardModels))); - } - - template - void DeterministicModelBisimulationDecomposition::refineBlockProbabilities(Block& block, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator) { - // Sort the states in the block based on their probabilities. - std::sort(partition.getBegin(block), partition.getEnd(block), [&partition] (std::pair const& a, std::pair 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>::const_iterator begin = partition.getBegin(block); - typename std::vector>::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 - void DeterministicModelBisimulationDecomposition::refineBlockWeak(Block& block, Partition& partition, storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator) { - std::vector 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 stateLabels(block.getEnd() - block.getBegin(), storm::storage::BitVector(splitPoints.size() - 1)); - - std::vector 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 const& a, std::pair 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>::const_iterator begin = partition.getBegin(block); - typename std::vector>::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(); - 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(); - 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 - std::vector DeterministicModelBisimulationDecomposition::getSplitPointsWeak(Block& block, Partition& partition, storm::utility::ConstantsComparator const& comparator) { - std::vector 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& stateValuePair) { - ValueType const& silentProbability = partition.getSilentProbability(stateValuePair.first); - if (!comparator.isOne(silentProbability) && !comparator.isZero(silentProbability)) { - stateValuePair.second /= storm::utility::one() - silentProbability; - } - }); - - // Now sort the states based on their probabilities. - std::sort(partition.getStatesAndValues().begin() + block.getOriginalBegin(), partition.getStatesAndValues().begin() + block.getBegin(), [&partition] (std::pair const& a, std::pair 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>::const_iterator begin = partition.getStatesAndValues().begin() + block.getOriginalBegin(); - typename std::vector>::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 - void DeterministicModelBisimulationDecomposition::refinePartition(storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, Block& splitter, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator const& comparator) { - std::list 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 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(new ModelType(builder.build(), std::move(newLabeling), std::move(rewardModels))); } - template class DeterministicModelBisimulationDecomposition; + template class DeterministicModelBisimulationDecomposition>; #ifdef STORM_HAVE_CARL - template class DeterministicModelBisimulationDecomposition; + template class DeterministicModelBisimulationDecomposition>; #endif } } diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h index 9bdb1dd48..27b12949d 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h +++ b/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::Options const& options = Options()); + DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options = typename BisimulationDecomposition::Options()); - private: - virtual std::pair getStatesWithProbability01(storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) override; + protected: + virtual std::pair getStatesWithProbability01() override; + + virtual void initializeMeasureDrivenPartition() override; virtual void initializeLabelBasedPartition() override; + virtual void buildQuotient() override; + + virtual void refinePartitionBasedOnSplitter(bisimulation::Block const& splitter, std::deque& splitterQueue) override; + + private: + virtual void refinePredecessorBlocksOfSplitter(std::list& predecessorBlocks, std::deque& 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 const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, Block& splitter, Partition& partition, BisimulationType bisimulationType, std::deque& splitterQueue, storm::utility::ConstantsComparator 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& splitterQueue, storm::utility::ConstantsComparator 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 probabilitiesToCurrentSplitter; - void refineBlockWeak(Block& block, Partition& partition, storm::storage::SparseMatrix const& forwardTransitions, storm::storage::SparseMatrix const& backwardTransitions, std::deque& splitterQueue, storm::utility::ConstantsComparator 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 getSplitPointsWeak(Block& block, Partition& partition, storm::utility::ConstantsComparator 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 - Partition getMeasureDrivenInitialPartition(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, BisimulationType bisimulationType, bool keepRewards = true, bool bounded = false, storm::utility::ConstantsComparator const& comparator = storm::utility::ConstantsComparator()); - - /*! - * 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 - Partition getLabelBasedInitialPartition(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, BisimulationType bisimulationType, std::set const& atomicPropositions, bool keepRewards = true, storm::utility::ConstantsComparator const& comparator = storm::utility::ConstantsComparator()); - // A vector mapping each state to its silent probability. std::vector silentProbabilities; }; diff --git a/src/storage/bisimulation/Partition.cpp b/src/storage/bisimulation/Partition.cpp index eade12b0d..cf9dfa8aa 100644 --- a/src/storage/bisimulation/Partition.cpp +++ b/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::iterator Partition::begin(Block const& block) { + auto it = this->states.begin(); + std::advance(it, block.getBeginIndex()); + return it; + } std::vector::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::iterator Partition::end(Block const& block) { + auto it = this->states.begin(); + std::advance(it, block.getEndIndex()); + return it; + } + std::vector::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>::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 const& lessFunction, std::function const& changedFunction) { + bool Partition::splitBlock(Block& block, std::function const& less, std::function 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 const& lessFunction, std::function const& changedFunction) { - for (auto& block : blocks) { - splitBlock(block, lessFunction, changedFunction); + bool Partition::split(std::function const& less, std::function 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::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 const& Partition::getBlocks() const { + std::vector> const& Partition::getBlocks() const { return this->blocks; } - std::vector& Partition::getBlocks() { + std::vector>& 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) { diff --git a/src/storage/bisimulation/Partition.h b/src/storage/bisimulation/Partition.h index 6bd2cd8e1..0fdffa4f6 100644 --- a/src/storage/bisimulation/Partition.h +++ b/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>::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 const& lessFunction, std::function const& changedFunction); + // points. The callback function is called for every newly created block. + bool splitBlock(Block& block, std::function const& less, std::function const& newBlockCallback = [] (Block&) {}); - // Splits all blocks by using the sorting-based splitting. - void split(std::function const& lessFunction, std::function const& changedFunction); + // Splits all blocks by using the sorting-based splitting. The callback is called for all newly created + // blocks. + bool split(std::function const& less, std::function 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 const& getBlocks() const; + std::vector> const& getBlocks() const; // Retrieves the blocks of the partition. - std::vector& getBlocks(); + std::vector>& 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::iterator begin(Block const& block); + // Returns an iterator to the beginning of the states of the given block. std::vector::const_iterator begin(Block const& block) const; + + // Returns an iterator to the beginning of the states of the given block. + std::vector::iterator end(Block const& block); // Returns an iterator to the beginning of the states of the given block. std::vector::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 blocks; + std::vector> blocks; // A mapping of states to their blocks. std::vector stateToBlockMapping; diff --git a/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp b/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp index eeb7298b7..f9ff877eb 100644 --- a/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp +++ b/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> dtmc = abstractModel->as>(); - storm::storage::DeterministicModelBisimulationDecomposition bisim(*dtmc); + storm::storage::DeterministicModelBisimulationDecomposition> bisim(*dtmc); + ASSERT_NO_THROW(bisim.computeBisimulationDecomposition()); std::shared_ptr> result; ASSERT_NO_THROW(result = bisim.getQuotient()); @@ -19,39 +21,42 @@ TEST(DeterministicModelBisimulationDecomposition, Die) { EXPECT_EQ(20ul, result->getNumberOfTransitions()); #ifdef WINDOWS - storm::storage::DeterministicModelBisimulationDecomposition::Options options; + storm::storage::BisimulationDecomposition>::Options options; #else - typename storm::storage::DeterministicModelBisimulationDecomposition::Options options; + typename storm::storage::BisimulationDecomposition>::Options options; #endif options.respectedAtomicPropositions = std::set({"one"}); - storm::storage::DeterministicModelBisimulationDecomposition bisim2(*dtmc, options); + storm::storage::DeterministicModelBisimulationDecomposition> 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 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> 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("one"); auto eventuallyFormula = std::make_shared(labelFormula); #ifdef WINDOWS - storm::storage::DeterministicModelBisimulationDecomposition::Options options2(*dtmc, *eventuallyFormula); + storm::storage::DeterministicModelBisimulationDecomposition>::Options options2(*dtmc, *eventuallyFormula); #else - typename storm::storage::DeterministicModelBisimulationDecomposition::Options options2(*dtmc, *eventuallyFormula); + typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options2(*dtmc, *eventuallyFormula); #endif - storm::storage::DeterministicModelBisimulationDecomposition bisim4(*dtmc, options2); + storm::storage::DeterministicModelBisimulationDecomposition> 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> dtmc = abstractModel->as>(); - storm::storage::DeterministicModelBisimulationDecomposition bisim(*dtmc); +// storm::storage::DeterministicModelBisimulationDecomposition> bisim(*dtmc); std::shared_ptr> 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::Options options; -#else - typename storm::storage::DeterministicModelBisimulationDecomposition::Options options; -#endif - options.respectedAtomicPropositions = std::set({"observe0Greater1"}); - - storm::storage::DeterministicModelBisimulationDecomposition 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 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>::Options options; +//#else +// typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options; +//#endif +// options.respectedAtomicPropositions = std::set({"observe0Greater1"}); +// +// storm::storage::DeterministicModelBisimulationDecomposition> 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> 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("observe0Greater1"); auto eventuallyFormula = std::make_shared(labelFormula); #ifdef WINDOWS - storm::storage::DeterministicModelBisimulationDecomposition::Options options2(*dtmc, *eventuallyFormula); + storm::storage::DeterministicModelBisimulationDecomposition>::Options options2(*dtmc, *eventuallyFormula); #else - typename storm::storage::DeterministicModelBisimulationDecomposition::Options options2(*dtmc, *eventuallyFormula); + typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options2(*dtmc, *eventuallyFormula); #endif - storm::storage::DeterministicModelBisimulationDecomposition bisim4(*dtmc, options2); + storm::storage::DeterministicModelBisimulationDecomposition> 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(eventuallyFormula); - -#ifdef WINDOWS - storm::storage::DeterministicModelBisimulationDecomposition::Options options3(*dtmc, *probabilityOperatorFormula); -#else - typename storm::storage::DeterministicModelBisimulationDecomposition::Options options3(*dtmc, *probabilityOperatorFormula); -#endif - storm::storage::DeterministicModelBisimulationDecomposition 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(std::make_shared(true), labelFormula, 50); - -#ifdef WINDOWS - storm::storage::DeterministicModelBisimulationDecomposition::Options options4(*dtmc, *boundedUntilFormula); -#else - typename storm::storage::DeterministicModelBisimulationDecomposition::Options options4(*dtmc, *boundedUntilFormula); -#endif - storm::storage::DeterministicModelBisimulationDecomposition 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(eventuallyFormula); +// +//#ifdef WINDOWS +// storm::storage::DeterministicModelBisimulationDecomposition>::Options options3(*dtmc, *probabilityOperatorFormula); +//#else +// typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options3(*dtmc, *probabilityOperatorFormula); +//#endif +// storm::storage::DeterministicModelBisimulationDecomposition> 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(std::make_shared(true), labelFormula, 50); +// +//#ifdef WINDOWS +// storm::storage::DeterministicModelBisimulationDecomposition>::Options options4(*dtmc, *boundedUntilFormula); +//#else +// typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options4(*dtmc, *boundedUntilFormula); +//#endif +// storm::storage::DeterministicModelBisimulationDecomposition> 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()); } From 1428f1647b1b2ccb977e7b974b37f9e22caedc91 Mon Sep 17 00:00:00 2001 From: dehnert Date: Mon, 26 Oct 2015 18:51:24 +0100 Subject: [PATCH 03/24] commented in some more tests, however the main entry points need to be fixed because of the new templating of the bisimulation class Former-commit-id: 7133025049f674fbda21bb12b9f6c95f0e6be148 --- src/cli/entrypoints.h | 3 +- src/utility/storm.h | 3 +- ...sticModelBisimulationDecompositionTest.cpp | 106 +++++++++--------- 3 files changed, 55 insertions(+), 57 deletions(-) diff --git a/src/cli/entrypoints.h b/src/cli/entrypoints.h index c17b2fda9..f541ef4d2 100644 --- a/src/cli/entrypoints.h +++ b/src/cli/entrypoints.h @@ -7,7 +7,6 @@ namespace storm { namespace cli { - template void verifySparseModel(std::shared_ptr> model, std::vector> const& formulas) { for (auto const& formula : formulas) { @@ -86,7 +85,6 @@ namespace storm { } } - template void buildAndCheckSymbolicModel(storm::prism::Program const& program, std::vector> const& formulas) { std::shared_ptr model = buildSymbolicModel(program, formulas); @@ -127,6 +125,7 @@ namespace storm { STORM_LOG_THROW(settings.isExplicitSet(), storm::exceptions::InvalidStateException, "Unable to build explicit model without model files."); std::shared_ptr model = buildExplicitModel(settings.getTransitionFilename(), settings.getLabelingFilename(), settings.isStateRewardsSet() ? settings.getStateRewardsFilename() : boost::optional(), settings.isTransitionRewardsSet() ? settings.getTransitionRewardsFilename() : boost::optional(), settings.isChoiceLabelingSet() ? settings.getChoiceLabelingFilename() : boost::optional()); + // Preprocess the model if needed. model = preprocessModel(model, formulas); diff --git a/src/utility/storm.h b/src/utility/storm.h index 3281d342b..c7fe8581d 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -131,7 +131,7 @@ namespace storm { options = typename storm::storage::DeterministicModelBisimulationDecomposition::Options(*sparseModel, formulas); } if (storm::settings::bisimulationSettings().isWeakBisimulationSet()) { - options.weak = true; + options.type = storm::storage::BisimulationType::Weak; options.bounded = false; } @@ -231,7 +231,6 @@ namespace storm { return result; } - #ifdef STORM_HAVE_CARL inline void exportParametricResultToFile(storm::RationalFunction const& result, storm::models::sparse::Dtmc::ConstraintCollector const& constraintCollector, std::string const& path) { diff --git a/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp b/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp index f9ff877eb..881e2514a 100644 --- a/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp +++ b/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp @@ -69,30 +69,30 @@ TEST(DeterministicModelBisimulationDecomposition, Crowds) { ASSERT_EQ(abstractModel->getType(), storm::models::ModelType::Dtmc); std::shared_ptr> dtmc = abstractModel->as>(); -// storm::storage::DeterministicModelBisimulationDecomposition> bisim(*dtmc); + storm::storage::DeterministicModelBisimulationDecomposition> bisim(*dtmc); std::shared_ptr> result; -// 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>::Options options; -//#else -// typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options; -//#endif -// options.respectedAtomicPropositions = std::set({"observe0Greater1"}); -// -// storm::storage::DeterministicModelBisimulationDecomposition> 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()); -// + 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>::Options options; +#else + typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options; +#endif + options.respectedAtomicPropositions = std::set({"observe0Greater1"}); + + storm::storage::DeterministicModelBisimulationDecomposition> 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; // @@ -119,34 +119,34 @@ TEST(DeterministicModelBisimulationDecomposition, Crowds) { EXPECT_EQ(storm::models::ModelType::Dtmc, result->getType()); EXPECT_EQ(64ul, result->getNumberOfStates()); EXPECT_EQ(104ul, result->getNumberOfTransitions()); -// -// auto probabilityOperatorFormula = std::make_shared(eventuallyFormula); -// -//#ifdef WINDOWS -// storm::storage::DeterministicModelBisimulationDecomposition>::Options options3(*dtmc, *probabilityOperatorFormula); -//#else -// typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options3(*dtmc, *probabilityOperatorFormula); -//#endif -// storm::storage::DeterministicModelBisimulationDecomposition> 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(std::make_shared(true), labelFormula, 50); -// -//#ifdef WINDOWS -// storm::storage::DeterministicModelBisimulationDecomposition>::Options options4(*dtmc, *boundedUntilFormula); -//#else -// typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options4(*dtmc, *boundedUntilFormula); -//#endif -// storm::storage::DeterministicModelBisimulationDecomposition> 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()); + + auto probabilityOperatorFormula = std::make_shared(eventuallyFormula); + +#ifdef WINDOWS + storm::storage::DeterministicModelBisimulationDecomposition>::Options options3(*dtmc, *probabilityOperatorFormula); +#else + typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options3(*dtmc, *probabilityOperatorFormula); +#endif + storm::storage::DeterministicModelBisimulationDecomposition> 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(std::make_shared(true), labelFormula, 50); + +#ifdef WINDOWS + storm::storage::DeterministicModelBisimulationDecomposition>::Options options4(*dtmc, *boundedUntilFormula); +#else + typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options4(*dtmc, *boundedUntilFormula); +#endif + storm::storage::DeterministicModelBisimulationDecomposition> 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()); } From 11b04c794032e5d2de3810fe18ca070f19e44c0d Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 27 Oct 2015 17:43:54 +0100 Subject: [PATCH 04/24] more work towards making the new bisim class available from the cl Former-commit-id: b177287e001f3864eb100ea990a5893f8b5d12a8 --- src/cli/entrypoints.h | 33 +++++++++++++++++++++++++++++---- src/models/ModelBase.cpp | 4 ++++ src/models/ModelBase.h | 8 ++++++++ src/utility/storm.h | 5 +---- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/cli/entrypoints.h b/src/cli/entrypoints.h index f541ef4d2..bcbf8cdfa 100644 --- a/src/cli/entrypoints.h +++ b/src/cli/entrypoints.h @@ -3,7 +3,6 @@ #include "src/utility/storm.h" - namespace storm { namespace cli { @@ -84,14 +83,40 @@ namespace storm { } } } - + +#define BRANCH_ON_MODELTYPE(result, model, value_type, dd_type, function, ...) \ + if (model->isSymbolicModel()) { \ + if (model->isOfType(storm::models::ModelType::Dtmc)) { \ + result = function>(model->as>(), __VA_ARGS__); \ + } else if (model->isOfType(storm::models::ModelType::Ctmc)) { \ + result = function>(model->as>(), __VA_ARGS__); \ + } else if (model->isOfType(storm::models::ModelType::Mdp)) { \ + result = function>(model->as>(), __VA_ARGS__); \ + } else { \ + STORM_LOG_ASSERT(false, "Unknown model type."); \ + } \ + } else { \ + STORM_LOG_ASSERT(model->isSparseModel(), "Unknown model type."); \ + if (model->isOfType(storm::models::ModelType::Dtmc)) { \ + result = function>(model->as>(), __VA_ARGS__); \ + } else if (model->isOfType(storm::models::ModelType::Ctmc)) { \ + result = function>(model->as>(), __VA_ARGS__); \ + } else if (model->isOfType(storm::models::ModelType::Mdp)) { \ + result = function>(model->as>(), __VA_ARGS__); \ + } else if (model->isOfType(storm::models::ModelType::MarkovAutomaton)) { \ + result = function>(model->as>(), __VA_ARGS__); \ + } else { \ + STORM_LOG_ASSERT(false, "Unknown model type."); \ + } \ + } + template void buildAndCheckSymbolicModel(storm::prism::Program const& program, std::vector> const& formulas) { std::shared_ptr model = buildSymbolicModel(program, formulas); STORM_LOG_THROW(model != nullptr, storm::exceptions::InvalidStateException, "Model could not be constructed for an unknown reason."); // Preprocess the model if needed. - model = preprocessModel(model, formulas); + BRANCH_ON_MODELTYPE(model, model, ValueType, storm::dd::DdType::CUDD, preprocessModel, formulas); // Print some information about the model. model->printModelInformationToStream(std::cout); @@ -127,7 +152,7 @@ namespace storm { std::shared_ptr model = buildExplicitModel(settings.getTransitionFilename(), settings.getLabelingFilename(), settings.isStateRewardsSet() ? settings.getStateRewardsFilename() : boost::optional(), settings.isTransitionRewardsSet() ? settings.getTransitionRewardsFilename() : boost::optional(), settings.isChoiceLabelingSet() ? settings.getChoiceLabelingFilename() : boost::optional()); // Preprocess the model if needed. - model = preprocessModel(model, formulas); + BRANCH_ON_MODELTYPE(model, model, ValueType, storm::dd::DdType::CUDD, preprocessModel, formulas); // Print some information about the model. model->printModelInformationToStream(std::cout); diff --git a/src/models/ModelBase.cpp b/src/models/ModelBase.cpp index 1eff87d18..b79d7a81f 100644 --- a/src/models/ModelBase.cpp +++ b/src/models/ModelBase.cpp @@ -13,5 +13,9 @@ namespace storm { bool ModelBase::isSymbolicModel() const { return false; } + + bool ModelBase::isOfType(storm::models::ModelType const& modelType) const { + return this->getType() == modelType; + } } } \ No newline at end of file diff --git a/src/models/ModelBase.h b/src/models/ModelBase.h index 4bbe31b64..d118e1516 100644 --- a/src/models/ModelBase.h +++ b/src/models/ModelBase.h @@ -89,6 +89,14 @@ namespace storm { */ virtual bool isSymbolicModel() const; + /*! + * Checks whether the model is of the given type. + * + * @param modelType The model type to check for. + * @return True iff the model is of the given type. + */ + bool isOfType(storm::models::ModelType const& modelType) const; + private: // The type of the model. ModelType modelType; diff --git a/src/utility/storm.h b/src/utility/storm.h index c7fe8581d..38e78f364 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -30,6 +30,7 @@ #include "src/models/ModelBase.h" #include "src/models/sparse/Model.h" #include "src/models/sparse/StandardRewardModel.h" +#include "src/models/sparse/MarkovAutomaton.h" #include "src/models/symbolic/Model.h" #include "src/models/symbolic/StandardRewardModel.h" @@ -80,8 +81,6 @@ namespace storm { std::vector> parseFormulasForExplicit(std::string const& inputString); std::vector> parseFormulasForProgram(std::string const& inputString, storm::prism::Program const& program); - - template std::shared_ptr buildSymbolicModel(storm::prism::Program const& program, std::vector> const& formulas) { std::shared_ptr result(nullptr); @@ -111,7 +110,6 @@ namespace storm { result = storm::builder::DdPrismModelBuilder::translateProgram(program, options); } - // Then, build the model from the symbolic description. return result; } @@ -263,7 +261,6 @@ namespace storm { return result; } - #endif template From 40a75baee7e6db893d75ec90443795111d8c55ac Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 27 Oct 2015 22:44:41 +0100 Subject: [PATCH 05/24] using some template magic to make it compile again Former-commit-id: 69888cac45af060b87dbc165b9a215f922dfa6b8 --- src/models/symbolic/Model.h | 1 + .../BisimulationDecomposition.cpp | 2 + ...ministicModelBisimulationDecomposition.cpp | 2 + src/utility/storm.h | 56 ++++++++++++------- 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/models/symbolic/Model.h b/src/models/symbolic/Model.h index b70c39b97..4dc6ff31e 100644 --- a/src/models/symbolic/Model.h +++ b/src/models/symbolic/Model.h @@ -38,6 +38,7 @@ namespace storm { template class Model : public storm::models::ModelBase { public: + static const storm::dd::DdType DdType = Type; typedef StandardRewardModel RewardModelType; Model(Model const& other) = default; diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp index ba35bc455..120f88f88 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.cpp +++ b/src/storage/bisimulation/BisimulationDecomposition.cpp @@ -276,9 +276,11 @@ namespace storm { } template class BisimulationDecomposition>; + template class BisimulationDecomposition>; #ifdef STORM_HAVE_CARL template class BisimulationDecomposition>; + template class BisimulationDecomposition>; #endif } } diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp index c30058475..9a739cea3 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp @@ -364,9 +364,11 @@ namespace storm { } template class DeterministicModelBisimulationDecomposition>; + template class DeterministicModelBisimulationDecomposition>; #ifdef STORM_HAVE_CARL template class DeterministicModelBisimulationDecomposition>; + template class DeterministicModelBisimulationDecomposition>; #endif } } diff --git a/src/utility/storm.h b/src/utility/storm.h index 38e78f364..2eea34738 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -112,34 +112,50 @@ namespace storm { return result; } - - template - std::shared_ptr preprocessModel(std::shared_ptr model, std::vector> const& formulas) { + + template + std::shared_ptr performSparseBisimulationMinimization(std::shared_ptr model, std::vector> const& formulas) { + std::cout << "Performing bisimulation minimization... "; + typename storm::storage::DeterministicModelBisimulationDecomposition::Options options; + if (!formulas.empty()) { + options = typename storm::storage::DeterministicModelBisimulationDecomposition::Options(*model, formulas); + } + if (storm::settings::bisimulationSettings().isWeakBisimulationSet()) { + options.type = storm::storage::BisimulationType::Weak; + options.bounded = false; + } + + storm::storage::DeterministicModelBisimulationDecomposition bisimulationDecomposition(*model, options); + model = bisimulationDecomposition.getQuotient(); + std::cout << "done." << std::endl << std::endl; + return model; + } + + template, ModelType>::value, bool>::type = 0> + std::shared_ptr preprocessModel(std::shared_ptr model, std::vector> const& formulas) { if (storm::settings::generalSettings().isBisimulationSet()) { + STORM_LOG_THROW(model->isSparseModel(), storm::exceptions::InvalidSettingsException, "Bisimulation minimization is currently only available for sparse models."); - std::shared_ptr> sparseModel = model->template as>(); - STORM_LOG_THROW(model->getType() == storm::models::ModelType::Dtmc || model->getType() == storm::models::ModelType::Ctmc, storm::exceptions::InvalidSettingsException, "Bisimulation minimization is currently only available for DTMCs."); - std::shared_ptr> dtmc = sparseModel->template as>(); + STORM_LOG_THROW(model->isOfType(storm::models::ModelType::Dtmc) || model->isOfType(storm::models::ModelType::Ctmc), storm::exceptions::InvalidSettingsException, "Bisimulation minimization is currently only available for DTMCs and CTMCs."); - dtmc->reduceToStateBasedRewards(); + model->reduceToStateBasedRewards(); - std::cout << "Performing bisimulation minimization... "; - typename storm::storage::DeterministicModelBisimulationDecomposition::Options options; - if (!formulas.empty()) { - options = typename storm::storage::DeterministicModelBisimulationDecomposition::Options(*sparseModel, formulas); - } - if (storm::settings::bisimulationSettings().isWeakBisimulationSet()) { - options.type = storm::storage::BisimulationType::Weak; - options.bounded = false; + if (model->isOfType(storm::models::ModelType::Dtmc)) { + return performSparseBisimulationMinimization>(model->template as>(), formulas); + } else { + return performSparseBisimulationMinimization>(model->template as>(), formulas); } - - storm::storage::DeterministicModelBisimulationDecomposition bisimulationDecomposition(*dtmc, options); - model = bisimulationDecomposition.getQuotient(); - std::cout << "done." << std::endl << std::endl; + } return model; } - + + template, ModelType >::value, bool>::type = 0> + std::shared_ptr preprocessModel(std::shared_ptr model, std::vector> const& formulas) { + // No preprocessing available yet. + return model; + } + template void generateCounterexample(storm::prism::Program const& program, std::shared_ptr> model, std::shared_ptr const& formula) { if (storm::settings::counterexampleGeneratorSettings().isMinimalCommandSetGenerationSet()) { From 2484a515a0112cd26a8c6089eb3dd1ee7ae294e8 Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 27 Oct 2015 23:30:51 +0100 Subject: [PATCH 06/24] some more work on bisim Former-commit-id: aaa8088b009de8b1f0469cc2171b2821f1c6656e --- src/models/sparse/StandardRewardModel.cpp | 4 +++- .../bisimulation/BisimulationDecomposition.cpp | 15 +++++++++------ src/storage/bisimulation/Block.cpp | 2 +- ...terministicModelBisimulationDecomposition.cpp | 16 ++++++++-------- src/storage/bisimulation/Partition.cpp | 6 +++--- src/utility/storm.h | 1 + 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/models/sparse/StandardRewardModel.cpp b/src/models/sparse/StandardRewardModel.cpp index 1f7db2b79..fe63c4b78 100644 --- a/src/models/sparse/StandardRewardModel.cpp +++ b/src/models/sparse/StandardRewardModel.cpp @@ -147,6 +147,7 @@ namespace storm { if (this->hasTransitionRewards()) { if (this->hasStateActionRewards()) { storm::utility::vector::addVectors(this->getStateActionRewardVector(), transitionMatrix.getPointwiseProductRowSumVector(this->getTransitionRewardMatrix()), this->getStateActionRewardVector()); + optionalStateActionRewardVector = boost::none; } else { this->optionalStateActionRewardVector = transitionMatrix.getPointwiseProductRowSumVector(this->getTransitionRewardMatrix()); } @@ -157,8 +158,9 @@ namespace storm { STORM_LOG_THROW(this->getStateRewardVector().size() == this->getStateActionRewardVector().size(), storm::exceptions::InvalidOperationException, "The reduction to state rewards is only possible of both the state and the state-action rewards have the same dimension."); storm::utility::vector::addVectors(this->getStateActionRewardVector(), this->getStateRewardVector(), this->getStateRewardVector()); } else { - this->optionalStateRewardVector = std::move(this->optionalStateRewardVector); + this->optionalStateRewardVector = std::move(this->optionalStateActionRewardVector); } + optionalStateActionRewardVector = boost::none; } } diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp index 120f88f88..d550c510c 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.cpp +++ b/src/storage/bisimulation/BisimulationDecomposition.cpp @@ -28,7 +28,10 @@ namespace storm { template BisimulationDecomposition::Options::Options(ModelType const& model, std::vector> const& formulas) : Options() { - if (formulas.size() == 1) { + if (formulas.empty()) { + this->respectedAtomicPropositions = model.getStateLabeling().getLabels(); + this->keepRewards = true; + } if (formulas.size() == 1) { this->preserveSingleFormula(model, *formulas.front()); } else { for (auto const& formula : formulas) { @@ -202,7 +205,7 @@ namespace storm { splitter->unmarkAsSplitter(); // Now refine the partition using the current splitter. - std::cout << "refining based on splitter " << splitter->getId() << std::endl; +// std::cout << "refining based on splitter " << splitter->getId() << std::endl; refinePartitionBasedOnSplitter(*splitter, splitterQueue); } } @@ -236,8 +239,8 @@ namespace storm { this->splitInitialPartitionBasedOnStateRewards(); } - std::cout << "successfully built (label) initial partition" << std::endl; - partition.print(); +// std::cout << "successfully built (label) initial partition" << std::endl; +// partition.print(); } template @@ -257,8 +260,8 @@ namespace storm { this->splitInitialPartitionBasedOnStateRewards(); } - std::cout << "successfully built (measure-driven) initial partition" << std::endl; - partition.print(); +// std::cout << "successfully built (measure-driven) initial partition" << std::endl; +// partition.print(); } template diff --git a/src/storage/bisimulation/Block.cpp b/src/storage/bisimulation/Block.cpp index 2157baeb3..2de49e62a 100644 --- a/src/storage/bisimulation/Block.cpp +++ b/src/storage/bisimulation/Block.cpp @@ -35,7 +35,7 @@ namespace storm { } void Block::setBeginIndex(storm::storage::sparse::state_type beginIndex) { - std::cout << "setting beg index to " << beginIndex << " [" << this << "]" << std::endl; +// 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."); } diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp index 9a739cea3..565dfef91 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp @@ -29,8 +29,8 @@ namespace storm { template DeterministicModelBisimulationDecomposition::DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options) : BisimulationDecomposition(model, options), probabilitiesToCurrentSplitter(model.getNumberOfStates(), storm::utility::zero()), 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.keepRewards || !model.hasRewardModel() || model.hasUniqueRewardModel(), storm::exceptions::IllegalFunctionCallException, "Bisimulation currently only supports models with at most one reward model."); + STORM_LOG_THROW(!options.keepRewards || !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.type != BisimulationType::Weak || !options.bounded, storm::exceptions::IllegalFunctionCallException, "Weak bisimulation cannot preserve bounded properties."); } @@ -160,7 +160,7 @@ namespace storm { template void DeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitter(std::list& predecessorBlocks, std::deque& splitterQueue) { for (auto block : predecessorBlocks) { - std::cout << "splitting predecessor block " << block->getId() << " of splitter" << std::endl; +// 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); @@ -175,7 +175,7 @@ namespace storm { splitterQueue.emplace_back(&block); }); // this->partition.print(); - std::cout << "size: " << this->partition.size() << std::endl; +// std::cout << "size: " << this->partition.size() << std::endl; // Remember that we have refined the block. block->setNeedsRefinement(false); @@ -227,10 +227,10 @@ namespace storm { } } - std::cout << "probs of splitter predecessors: " << std::endl; - for (auto state : predecessorsOfCurrentSplitter) { - std::cout << state << " [" << this->partition.getBlock(state).getId() << "]" << " -> " << probabilitiesToCurrentSplitter[state] << std::endl; - } +// 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); diff --git a/src/storage/bisimulation/Partition.cpp b/src/storage/bisimulation/Partition.cpp index cf9dfa8aa..bbe45fc0a 100644 --- a/src/storage/bisimulation/Partition.cpp +++ b/src/storage/bisimulation/Partition.cpp @@ -142,7 +142,7 @@ namespace storm { std::pair>::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; +// 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. @@ -157,7 +157,7 @@ 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; +// std::cout << "setting begin pos of block " << block.getId() << " to " << position << std::endl; block.setBeginIndex(position); // Mark both blocks as splitters. @@ -171,7 +171,7 @@ namespace storm { } bool Partition::splitBlock(Block& block, std::function const& less, std::function const& newBlockCallback) { - std::cout << "sorting the block [" << block.getId() << "]" << std::endl; +// 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); diff --git a/src/utility/storm.h b/src/utility/storm.h index 2eea34738..221545743 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -126,6 +126,7 @@ namespace storm { } storm::storage::DeterministicModelBisimulationDecomposition bisimulationDecomposition(*model, options); + bisimulationDecomposition.computeBisimulationDecomposition(); model = bisimulationDecomposition.getQuotient(); std::cout << "done." << std::endl << std::endl; return model; From 1f5110b90c89fefab61ae084173cf2eba2b5cf44 Mon Sep 17 00:00:00 2001 From: dehnert Date: Wed, 28 Oct 2015 18:06:44 +0100 Subject: [PATCH 07/24] work on making bisimulation fast again :( Former-commit-id: bb89091b2d929e8ae6c45ecff33b69db1cd1720f --- .../BisimulationDecomposition.cpp | 85 ++++----- .../bisimulation/BisimulationDecomposition.h | 6 +- src/storage/bisimulation/Block.cpp | 105 +++++++---- src/storage/bisimulation/Block.h | 15 +- .../bisimulation/DeterministicBlockData.cpp | 44 +++++ .../bisimulation/DeterministicBlockData.h | 64 +++++++ ...ministicModelBisimulationDecomposition.cpp | 164 +++++++++++++----- ...erministicModelBisimulationDecomposition.h | 33 +++- src/storage/bisimulation/Partition.cpp | 137 +++++++++++---- src/storage/bisimulation/Partition.h | 56 ++++-- 10 files changed, 529 insertions(+), 180 deletions(-) create mode 100644 src/storage/bisimulation/DeterministicBlockData.cpp create mode 100644 src/storage/bisimulation/DeterministicBlockData.h diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp index d550c510c..f22ddcc39 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.cpp +++ b/src/storage/bisimulation/BisimulationDecomposition.cpp @@ -6,6 +6,8 @@ #include "src/models/sparse/Ctmc.h" #include "src/models/sparse/StandardRewardModel.h" +#include "src/storage/bisimulation/DeterministicBlockData.h" + #include "src/modelchecker/propositional/SparsePropositionalModelChecker.h" #include "src/modelchecker/results/ExplicitQualitativeCheckResult.h" @@ -21,13 +23,13 @@ namespace storm { using namespace bisimulation; - template - BisimulationDecomposition::Options::Options(ModelType const& model, storm::logic::Formula const& formula) : Options() { + template + BisimulationDecomposition::Options::Options(ModelType const& model, storm::logic::Formula const& formula) : Options() { this->preserveSingleFormula(model, formula); } - template - BisimulationDecomposition::Options::Options(ModelType const& model, std::vector> const& formulas) : Options() { + template + BisimulationDecomposition::Options::Options(ModelType const& model, std::vector> const& formulas) : Options() { if (formulas.empty()) { this->respectedAtomicPropositions = model.getStateLabeling().getLabels(); this->keepRewards = true; @@ -40,13 +42,13 @@ namespace storm { } } - template - BisimulationDecomposition::Options::Options() : measureDrivenInitialPartition(false), phiStates(), psiStates(), respectedAtomicPropositions(), keepRewards(false), type(BisimulationType::Strong), bounded(false), buildQuotient(true) { + template + BisimulationDecomposition::Options::Options() : measureDrivenInitialPartition(false), phiStates(), psiStates(), respectedAtomicPropositions(), keepRewards(false), type(BisimulationType::Strong), bounded(false), buildQuotient(true) { // Intentionally left empty. } - template - void BisimulationDecomposition::Options::preserveFormula(ModelType const& model, storm::logic::Formula const& formula) { + template + void BisimulationDecomposition::Options::preserveFormula(ModelType const& model, storm::logic::Formula const& formula) { // Disable the measure driven initial partition. measureDrivenInitialPartition = false; phiStates = boost::none; @@ -62,8 +64,8 @@ namespace storm { this->addToRespectedAtomicPropositions(formula.getAtomicExpressionFormulas(), formula.getAtomicLabelFormulas()); } - template - void BisimulationDecomposition::Options::preserveSingleFormula(ModelType const& model, storm::logic::Formula const& formula) { + template + void BisimulationDecomposition::Options::preserveSingleFormula(ModelType const& model, storm::logic::Formula const& formula) { keepRewards = formula.containsRewardOperator(); // We need to preserve bounded properties iff the formula contains a bounded until or a next subformula. @@ -76,8 +78,8 @@ namespace storm { this->checkAndSetMeasureDrivenInitialPartition(model, formula); } - template - void BisimulationDecomposition::Options::checkAndSetMeasureDrivenInitialPartition(ModelType const& model, storm::logic::Formula const& formula) { + template + void BisimulationDecomposition::Options::checkAndSetMeasureDrivenInitialPartition(ModelType const& model, storm::logic::Formula const& formula) { std::shared_ptr newFormula = formula.asSharedPointer(); if (formula.isProbabilityOperatorFormula()) { @@ -115,8 +117,8 @@ namespace storm { } } - template - void BisimulationDecomposition::Options::addToRespectedAtomicPropositions(std::vector> const& expressions, std::vector> const& labels) { + template + void BisimulationDecomposition::Options::addToRespectedAtomicPropositions(std::vector> const& expressions, std::vector> const& labels) { std::set labelsToRespect; for (auto const& labelFormula : labels) { labelsToRespect.insert(labelFormula->getLabel()); @@ -131,16 +133,16 @@ namespace storm { } } - template - BisimulationDecomposition::BisimulationDecomposition(ModelType const& model, Options const& options) : model(model), backwardTransitions(model.getBackwardTransitions()), options(options), partition(), comparator(), quotient(nullptr) { + template + BisimulationDecomposition::BisimulationDecomposition(ModelType const& model, Options const& options) : model(model), backwardTransitions(model.getBackwardTransitions()), options(options), partition(), comparator(), quotient(nullptr) { // Fix the respected atomic propositions if they were not explicitly given. if (!this->options.respectedAtomicPropositions) { this->options.respectedAtomicPropositions = model.getStateLabeling().getLabels(); } } - template - void BisimulationDecomposition::computeBisimulationDecomposition() { + template + void BisimulationDecomposition::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(); @@ -188,19 +190,21 @@ namespace storm { } } - template - void BisimulationDecomposition::performPartitionRefinement() { + template + void BisimulationDecomposition::performPartitionRefinement() { // Insert all blocks into the splitter queue that are initially marked as being a (potential) splitter. - std::deque splitterQueue; - std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (std::unique_ptr const& block) { if (block->isMarkedAsSplitter()) { splitterQueue.push_back(block.get()); } } ); + std::deque*> splitterQueue; + std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (std::unique_ptr> const& block) { if (block->isMarkedAsSplitter()) { splitterQueue.push_back(block.get()); } } ); // Then perform the actual splitting until there are no more splitters. + uint_fast64_t iterations = 0; while (!splitterQueue.empty()) { + ++iterations; // Optionally: sort the splitter queue according to some criterion (here: prefer small splitters). - std::sort(splitterQueue.begin(), splitterQueue.end(), [] (Block const* b1, Block const* b2) { return b1->getNumberOfStates() < b2->getNumberOfStates(); } ); + std::sort(splitterQueue.begin(), splitterQueue.end(), [] (Block const* b1, Block const* b2) { return b1->getNumberOfStates() < b2->getNumberOfStates(); } ); // Get and prepare the next splitter. - Block* splitter = splitterQueue.front(); + Block* splitter = splitterQueue.front(); splitterQueue.pop_front(); splitter->unmarkAsSplitter(); @@ -208,23 +212,24 @@ namespace storm { // std::cout << "refining based on splitter " << splitter->getId() << std::endl; refinePartitionBasedOnSplitter(*splitter, splitterQueue); } + std::cout << "done within " << iterations << " iterations." << std::endl; } - template - std::shared_ptr BisimulationDecomposition::getQuotient() const { + template + std::shared_ptr BisimulationDecomposition::getQuotient() const { STORM_LOG_THROW(this->quotient != nullptr, storm::exceptions::IllegalFunctionCallException, "Unable to retrieve quotient model from bisimulation decomposition, because it was not built."); return this->quotient; } - template - void BisimulationDecomposition::splitInitialPartitionBasedOnStateRewards() { + template + void BisimulationDecomposition::splitInitialPartitionBasedOnStateRewards() { std::vector 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]; }); } - template - void BisimulationDecomposition::initializeLabelBasedPartition() { - partition = storm::storage::bisimulation::Partition(model.getNumberOfStates()); + template + void BisimulationDecomposition::initializeLabelBasedPartition() { + partition = storm::storage::bisimulation::Partition(model.getNumberOfStates()); for (auto const& label : options.respectedAtomicPropositions.get()) { if (label == "init") { @@ -243,8 +248,8 @@ namespace storm { // partition.print(); } - template - void BisimulationDecomposition::initializeMeasureDrivenPartition() { + template + void BisimulationDecomposition::initializeMeasureDrivenPartition() { std::pair statesWithProbability01 = this->getStatesWithProbability01(); boost::optional representativePsiState; @@ -252,7 +257,7 @@ namespace storm { representativePsiState = *options.psiStates.get().begin(); } - partition = storm::storage::bisimulation::Partition(model.getNumberOfStates(), statesWithProbability01.first, options.bounded || options.keepRewards ? options.psiStates.get() : statesWithProbability01.second, representativePsiState); + partition = storm::storage::bisimulation::Partition(model.getNumberOfStates(), statesWithProbability01.first, options.bounded || options.keepRewards ? options.psiStates.get() : statesWithProbability01.second, representativePsiState); // If the model has state rewards, we need to consider them, because otherwise reward properties are not // preserved. @@ -264,8 +269,8 @@ namespace storm { // partition.print(); } - template - void BisimulationDecomposition::extractDecompositionBlocks() { + template + void BisimulationDecomposition::extractDecompositionBlocks() { // Now move the states from the internal partition into their final place in the decomposition. We do so in // a way that maintains the block IDs as indices. this->blocks.resize(partition.size()); @@ -278,12 +283,12 @@ namespace storm { } } - template class BisimulationDecomposition>; - template class BisimulationDecomposition>; + template class BisimulationDecomposition, bisimulation::DeterministicBlockData>; + template class BisimulationDecomposition, bisimulation::DeterministicBlockData>; #ifdef STORM_HAVE_CARL - template class BisimulationDecomposition>; - template class BisimulationDecomposition>; + template class BisimulationDecomposition, bisimulation::DeterministicBlockData>; + template class BisimulationDecomposition, bisimulation::DeterministicBlockData>; #endif } } diff --git a/src/storage/bisimulation/BisimulationDecomposition.h b/src/storage/bisimulation/BisimulationDecomposition.h index 305638438..816dc7436 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.h +++ b/src/storage/bisimulation/BisimulationDecomposition.h @@ -29,7 +29,7 @@ namespace storm { /*! * This class is the superclass of all decompositions of a sparse model into its bisimulation quotient. */ - template + template class BisimulationDecomposition : public Decomposition { public: typedef typename ModelType::ValueType ValueType; @@ -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& splitterQueue) = 0; + virtual void refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) = 0; /*! * Builds the quotient model based on the previously computed equivalence classes (stored in the blocks @@ -204,7 +204,7 @@ namespace storm { Options options; // The current partition (used by partition refinement). - storm::storage::bisimulation::Partition partition; + storm::storage::bisimulation::Partition partition; // A comparator used for comparing the distances of constants. storm::utility::ConstantsComparator comparator; diff --git a/src/storage/bisimulation/Block.cpp b/src/storage/bisimulation/Block.cpp index 2de49e62a..52dafb06d 100644 --- a/src/storage/bisimulation/Block.cpp +++ b/src/storage/bisimulation/Block.cpp @@ -4,6 +4,7 @@ #include #include "src/storage/bisimulation/Partition.h" +#include "src/storage/bisimulation/DeterministicBlockData.h" #include "src/exceptions/InvalidOperationException.h" #include "src/utility/macros.h" @@ -12,136 +13,180 @@ 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), needsRefinementFlag(false), absorbing(false), id(id) { + template + 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), mData() { if (nextBlock != nullptr) { nextBlock->previousBlock = this; } if (previousBlock != nullptr) { previousBlock->nextBlock = this; } + data().notify(*this); STORM_LOG_ASSERT(this->beginIndex < this->endIndex, "Unable to create block of illegal size."); } - bool Block::operator==(Block const& other) const { + template + bool Block::operator==(Block const& other) const { return this == &other; } - bool Block::operator!=(Block const& other) const { + template + bool Block::operator!=(Block const& other) const { return this != &other; } - void Block::print(Partition const& partition) const { + template + void Block::print(Partition const& partition) const { 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; + template + void Block::setBeginIndex(storm::storage::sparse::state_type beginIndex) { this->beginIndex = beginIndex; + data().notify(*this); STORM_LOG_ASSERT(beginIndex < endIndex, "Unable to resize block to illegal size."); } - void Block::setEndIndex(storm::storage::sparse::state_type endIndex) { + template + void Block::setEndIndex(storm::storage::sparse::state_type endIndex) { this->endIndex = endIndex; + data().notify(*this); STORM_LOG_ASSERT(beginIndex < endIndex, "Unable to resize block to illegal size."); } - storm::storage::sparse::state_type Block::getBeginIndex() const { + template + storm::storage::sparse::state_type Block::getBeginIndex() const { return this->beginIndex; } - storm::storage::sparse::state_type Block::getEndIndex() const { + template + storm::storage::sparse::state_type Block::getEndIndex() const { return this->endIndex; } - Block const& Block::getNextBlock() const { + template + Block const& Block::getNextBlock() const { return *this->nextBlock; } - bool Block::hasNextBlock() const { + template + bool Block::hasNextBlock() const { return this->nextBlock != nullptr; } - Block* Block::getNextBlockPointer() { + template + Block* Block::getNextBlockPointer() { return this->nextBlock; } - Block const* Block::getNextBlockPointer() const { + template + Block const* Block::getNextBlockPointer() const { return this->nextBlock; } - Block const& Block::getPreviousBlock() const { + template + Block const& Block::getPreviousBlock() const { return *this->previousBlock; } - Block* Block::getPreviousBlockPointer() { + template + Block* Block::getPreviousBlockPointer() { return this->previousBlock; } - Block const* Block::getPreviousBlockPointer() const { + template + Block const* Block::getPreviousBlockPointer() const { return this->previousBlock; } - bool Block::hasPreviousBlock() const { + template + bool Block::hasPreviousBlock() const { return this->previousBlock != nullptr; } - bool Block::check() const { + template + bool Block::check() const { STORM_LOG_ASSERT(this->beginIndex < this->endIndex, "Block has negative size."); STORM_LOG_ASSERT(!this->hasPreviousBlock() || this->getPreviousBlock().getNextBlockPointer() == this, "Illegal previous block."); STORM_LOG_ASSERT(!this->hasNextBlock() || this->getNextBlock().getPreviousBlockPointer() == this, "Illegal next block."); return true; } - std::size_t Block::getNumberOfStates() const { + template + std::size_t Block::getNumberOfStates() const { return (this->endIndex - this->beginIndex); } - bool Block::isMarkedAsSplitter() const { + template + bool Block::isMarkedAsSplitter() const { return this->markedAsSplitter; } - void Block::markAsSplitter() { + template + void Block::markAsSplitter() { this->markedAsSplitter = true; } - void Block::unmarkAsSplitter() { + template + void Block::unmarkAsSplitter() { this->markedAsSplitter = false; } - std::size_t Block::getId() const { + template + std::size_t Block::getId() const { return this->id; } - void Block::setAbsorbing(bool absorbing) { + template + void Block::setAbsorbing(bool absorbing) { this->absorbing = absorbing; } - bool Block::isAbsorbing() const { + template + bool Block::isAbsorbing() const { return this->absorbing; } - void Block::setRepresentativeState(storm::storage::sparse::state_type representativeState) { + template + void Block::setRepresentativeState(storm::storage::sparse::state_type representativeState) { this->representativeState = representativeState; } - bool Block::hasRepresentativeState() const { + template + bool Block::hasRepresentativeState() const { return static_cast(representativeState); } - storm::storage::sparse::state_type Block::getRepresentativeState() const { + template + storm::storage::sparse::state_type Block::getRepresentativeState() const { STORM_LOG_THROW(representativeState, storm::exceptions::InvalidOperationException, "Unable to retrieve representative state for block."); return representativeState.get(); } // Retrieves whether the block is marked as a predecessor. - bool Block::needsRefinement() const { + template + bool Block::needsRefinement() const { return needsRefinementFlag; } // Marks the block as needing refinement (or not). - void Block::setNeedsRefinement(bool value) { + template + void Block::setNeedsRefinement(bool value) { needsRefinementFlag = value; } + + template + DataType& Block::data() { + return mData; + } + + template + DataType const& Block::data() const { + return mData; + } + + template class Block; + } } } \ No newline at end of file diff --git a/src/storage/bisimulation/Block.h b/src/storage/bisimulation/Block.h index 304da118a..dd504ed08 100644 --- a/src/storage/bisimulation/Block.h +++ b/src/storage/bisimulation/Block.h @@ -10,11 +10,13 @@ namespace storm { namespace storage { namespace bisimulation { // Forward-declare partition class. + template class Partition; + template class Block { public: - friend class Partition; + friend class Partition; // Creates a new block with the given begin and end. Block(storm::storage::sparse::state_type beginIndex, storm::storage::sparse::state_type endIndex, Block* previous, Block* next, std::size_t id); @@ -29,7 +31,7 @@ namespace storm { bool operator!=(Block const& other) const; // Prints the block to the standard output. - void print(Partition const& partition) const; + void print(Partition const& partition) const; // Returns the beginning index of the block. storm::storage::sparse::state_type getBeginIndex() const; @@ -100,6 +102,12 @@ namespace storm { // 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; + private: // Sets the beginning index of the block. void setBeginIndex(storm::storage::sparse::state_type beginIndex); @@ -130,6 +138,9 @@ namespace storm { // 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 representativeState; + + // A member that stores additional data that depends on the kind of bisimulation. + DataType mData; }; } } diff --git a/src/storage/bisimulation/DeterministicBlockData.cpp b/src/storage/bisimulation/DeterministicBlockData.cpp new file mode 100644 index 000000000..e85acf729 --- /dev/null +++ b/src/storage/bisimulation/DeterministicBlockData.cpp @@ -0,0 +1,44 @@ +#include "src/storage/bisimulation/DeterministicBlockData.h" + +namespace storm { + namespace storage { + namespace bisimulation { + + DeterministicBlockData::DeterministicBlockData() : newBeginIndex(0), newEndIndex(0) { + // Intentionally left empty. + } + + DeterministicBlockData::DeterministicBlockData(uint_fast64_t newBeginIndex, uint_fast64_t newEndIndex) : newBeginIndex(newBeginIndex), newEndIndex(newEndIndex) { + // Intentionally left empty. + } + + uint_fast64_t DeterministicBlockData::getNewBeginIndex() const { + return newBeginIndex; + } + + void DeterministicBlockData::increaseNewBeginIndex() { + ++newBeginIndex; + } + + uint_fast64_t DeterministicBlockData::getNewEndIndex() const { + return newEndIndex; + } + + void DeterministicBlockData::decreaseNewEndIndex() { + --newEndIndex; + } + + void DeterministicBlockData::increaseNewEndIndex() { + ++newEndIndex; + } + + bool DeterministicBlockData::notify(Block const& block) { + bool result = block.getBeginIndex() != this->newBeginIndex || block.getEndIndex() != this->newEndIndex; + this->newBeginIndex = block.getBeginIndex(); + this->newEndIndex = block.getEndIndex(); + return result; + } + + } + } +} diff --git a/src/storage/bisimulation/DeterministicBlockData.h b/src/storage/bisimulation/DeterministicBlockData.h new file mode 100644 index 000000000..4c02bf0ce --- /dev/null +++ b/src/storage/bisimulation/DeterministicBlockData.h @@ -0,0 +1,64 @@ +#ifndef STORM_STORAGE_BISIMULATION_DETERMINISTICBLOCKDATA_H_ +#define STORM_STORAGE_BISIMULATION_DETERMINISTICBLOCKDATA_H_ + +#include + +#include "src/storage/bisimulation/Block.h" + +namespace storm { + namespace storage { + namespace bisimulation { + class DeterministicBlockData { + public: + DeterministicBlockData(); + DeterministicBlockData(uint_fast64_t newBeginIndex, uint_fast64_t newEndIndex); + + /*! + * Retrieves the new begin index. + * + * @return The new begin index. + */ + uint_fast64_t getNewBeginIndex() const; + + /*! + * Increases the new begin index by one. + */ + void increaseNewBeginIndex(); + + /*! + * 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(); + + /*! + * This method needs to be called whenever the block was modified to notify 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 const& block); + + public: + // A marker that can be used to mark a the new beginning of the block. + uint_fast64_t newBeginIndex; + + // A marker that can be used to mark a the new end of the block. + uint_fast64_t newEndIndex; + }; + } + } +} + +#endif /* STORM_STORAGE_BISIMULATION_DETERMINISTICBLOCKDATA_H_ */ diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp index 565dfef91..3710755a0 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp @@ -28,7 +28,7 @@ namespace storm { using namespace bisimulation; template - DeterministicModelBisimulationDecomposition::DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options) : BisimulationDecomposition(model, options), probabilitiesToCurrentSplitter(model.getNumberOfStates(), storm::utility::zero()), predecessorsOfCurrentSplitter(model.getNumberOfStates()) { + DeterministicModelBisimulationDecomposition::DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options) : BisimulationDecomposition(model, options), probabilitiesToCurrentSplitter(model.getNumberOfStates(), storm::utility::zero()) { STORM_LOG_THROW(!options.keepRewards || !model.hasRewardModel() || model.hasUniqueRewardModel(), storm::exceptions::IllegalFunctionCallException, "Bisimulation currently only supports models with at most one reward model."); STORM_LOG_THROW(!options.keepRewards || !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.type != BisimulationType::Weak || !options.bounded, storm::exceptions::IllegalFunctionCallException, "Weak bisimulation cannot preserve bounded properties."); @@ -102,7 +102,7 @@ namespace storm { void DeterministicModelBisimulationDecomposition::initializeSilentProbabilities() { silentProbabilities.resize(this->model.getNumberOfStates(), storm::utility::zero()); for (storm::storage::sparse::state_type state = 0; state < this->model.getNumberOfStates(); ++state) { - Block const* currentBlockPtr = &this->partition.getBlock(state); + Block const* currentBlockPtr = &this->partition.getBlock(state); for (auto const& successorEntry : this->model.getRows(state)) { if (&this->partition.getBlock(successorEntry.getColumn()) == currentBlockPtr) { silentProbabilities[state] += successorEntry.getValue(); @@ -121,7 +121,7 @@ namespace storm { template void DeterministicModelBisimulationDecomposition::initializeMeasureDrivenPartition() { - BisimulationDecomposition::initializeMeasureDrivenPartition(); + BisimulationDecomposition::initializeMeasureDrivenPartition(); if (this->options.type == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc) { this->initializeWeakDtmcBisimulation(); @@ -130,7 +130,7 @@ namespace storm { template void DeterministicModelBisimulationDecomposition::initializeLabelBasedPartition() { - BisimulationDecomposition::initializeLabelBasedPartition(); + BisimulationDecomposition::initializeLabelBasedPartition(); if (this->options.type == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc) { this->initializeWeakDtmcBisimulation(); @@ -153,29 +153,20 @@ namespace storm { } template - bool DeterministicModelBisimulationDecomposition::isPredecessorOfCurrentSplitter(storm::storage::sparse::state_type const& state) const { - return this->predecessorsOfCurrentSplitter.get(state); - } - - template - void DeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitter(std::list& predecessorBlocks, std::deque& splitterQueue) { + void DeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitter(std::list*>& predecessorBlocks, std::deque*>& 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; + // Depending on the actions we need to take, the block to refine changes, so we need to keep track of it. + Block* 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(); + } } - }, [&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); @@ -184,7 +175,91 @@ namespace storm { } template - void DeterministicModelBisimulationDecomposition::refinePartitionBasedOnSplitter(bisimulation::Block const& splitter, std::deque& splitterQueue) { + bool DeterministicModelBisimulationDecomposition::possiblyNeedsRefinement(bisimulation::Block const& predecessorBlock) const { + return predecessorBlock.getNumberOfStates() <= 1 || predecessorBlock.isAbsorbing(); + } + + template + void DeterministicModelBisimulationDecomposition::increaseProbabilityToSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block 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()) { + // Then, we just set the value. + probabilitiesToCurrentSplitter[predecessor] = value; + } else { + // If the state was seen as a predecessor before, we add the value to the existing value. + probabilitiesToCurrentSplitter[predecessor] += value; + } + } + + template + void DeterministicModelBisimulationDecomposition::moveStateToNewBeginningOfBlock(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock) { + this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().getNewBeginIndex())); + predecessorBlock.data().increaseNewBeginIndex(); + } + + template + void DeterministicModelBisimulationDecomposition::moveStateToNewEndOfBlock(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock) { + this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().getNewEndIndex() - 1)); + predecessorBlock.data().decreaseNewEndIndex(); + } + + template + void DeterministicModelBisimulationDecomposition::moveStateInSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter) { + 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); + } 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); + } + } + + template + void DeterministicModelBisimulationDecomposition::explorePredecessorsOfNewEndOfSplitter(bisimulation::Block& splitter) { + for (auto splitterIt = this->partition.begin() + splitter.data().getNewEndIndex(), 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 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 { + 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); + } + } + } + + // 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(); + } + } + + template + void DeterministicModelBisimulationDecomposition::refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) { // The outline of the refinement is as follows. // // (0) we prepare the environment for the splitting process. @@ -194,32 +269,34 @@ namespace storm { // all predecessors of the splitter in a member bit vector. // (0) - predecessorsOfCurrentSplitter.clear(); - std::list predecessorBlocks; + std::list*> predecessorBlocks; // (1) - for (auto splitterIt = this->partition.begin(splitter), splitterIte = this->partition.end(splitter); splitterIt != splitterIte; ++splitterIt) { + 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) { 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); + 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()) { + // 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 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(); + if (predecessorBlock != splitter) { + moveStateToNewBeginningOfBlock(predecessor, predecessorBlock); } else { - // Otherwise, we increase the probability by the current transition. - probabilitiesToCurrentSplitter[predecessor] += predecessorEntry.getValue(); + 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(); @@ -227,6 +304,11 @@ namespace storm { } } + // If the splitter is its own predecessor block, we need to treat the states at the end of the block. + if (splitterIsItsOwnPredecessor) { + explorePredecessorsOfNewEndOfSplitter(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; @@ -283,7 +365,7 @@ namespace storm { } } - Block const& oldBlock = this->partition.getBlock(representativeState); + Block const& oldBlock = this->partition.getBlock(representativeState); // If the block is absorbing, we simply add a self-loop. if (oldBlock.isAbsorbing()) { @@ -348,7 +430,7 @@ namespace storm { // Now check which of the blocks of the partition contain at least one initial state. for (auto initialState : this->model.getInitialStates()) { - Block const& initialBlock = this->partition.getBlock(initialState); + Block const& initialBlock = this->partition.getBlock(initialState); newLabeling.addLabelToState("init", initialBlock.getId()); } diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h index 27b12949d..74cedebb1 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h @@ -2,6 +2,7 @@ #define STORM_STORAGE_BISIMULATION_DETERMINISTICMODELBISIMULATIONDECOMPOSITION_H_ #include "src/storage/bisimulation/BisimulationDecomposition.h" +#include "src/storage/bisimulation/DeterministicBlockData.h" namespace storm { namespace utility { @@ -14,8 +15,9 @@ namespace storm { * This class represents the decomposition of a deterministic model into its bisimulation quotient. */ template - class DeterministicModelBisimulationDecomposition : public BisimulationDecomposition { + class DeterministicModelBisimulationDecomposition : public BisimulationDecomposition { public: + typedef bisimulation::DeterministicBlockData BlockDataType; typedef typename ModelType::ValueType ValueType; typedef typename ModelType::RewardModelType RewardModelType; @@ -26,7 +28,7 @@ namespace storm { * @param model The model to decompose. * @param options The options that customize the computed bisimulation. */ - DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options = typename BisimulationDecomposition::Options()); + DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options = typename BisimulationDecomposition::Options()); protected: virtual std::pair getStatesWithProbability01() override; @@ -37,10 +39,10 @@ namespace storm { virtual void buildQuotient() override; - virtual void refinePartitionBasedOnSplitter(bisimulation::Block const& splitter, std::deque& splitterQueue) override; + virtual void refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) override; private: - virtual void refinePredecessorBlocksOfSplitter(std::list& predecessorBlocks, std::deque& splitterQueue); + virtual void refinePredecessorBlocksOfSplitter(std::list*>& predecessorBlocks, std::deque*>& splitterQueue); /*! * Performs the necessary steps to compute a weak bisimulation on a DTMC. @@ -67,16 +69,29 @@ namespace storm { // Retrieves whether the given state is silent. bool isSilent(storm::storage::sparse::state_type const& state) const; - // Retrieves whether the given state is a predecessor of the current splitter. - bool isPredecessorOfCurrentSplitter(storm::storage::sparse::state_type const& state) const; + // Retrieves whether the given predecessor of the splitters possibly needs refinement. + bool possiblyNeedsRefinement(bisimulation::Block 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& 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& 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& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter); + + // Increases the probability of moving to the current splitter for the given state. + void increaseProbabilityToSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block 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& 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. std::vector probabilitiesToCurrentSplitter; - // A bit vector storing the predecessors of the current splitter. - storm::storage::BitVector predecessorsOfCurrentSplitter; - // A vector mapping each state to its silent probability. std::vector silentProbabilities; }; diff --git a/src/storage/bisimulation/Partition.cpp b/src/storage/bisimulation/Partition.cpp index bbe45fc0a..9597935dc 100644 --- a/src/storage/bisimulation/Partition.cpp +++ b/src/storage/bisimulation/Partition.cpp @@ -2,14 +2,17 @@ #include +#include "src/storage/bisimulation/DeterministicBlockData.h" + #include "src/utility/macros.h" #include "src/exceptions/InvalidArgumentException.h" namespace storm { namespace storage { namespace bisimulation { - Partition::Partition(std::size_t numberOfStates) : stateToBlockMapping(numberOfStates), states(numberOfStates), positions(numberOfStates) { - blocks.emplace_back(new Block(0, numberOfStates, nullptr, nullptr, blocks.size())); + template + Partition::Partition(std::size_t numberOfStates) : stateToBlockMapping(numberOfStates), states(numberOfStates), positions(numberOfStates) { + 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) { @@ -19,13 +22,14 @@ namespace storm { } } - Partition::Partition(std::size_t numberOfStates, storm::storage::BitVector const& prob0States, storm::storage::BitVector const& prob1States, boost::optional representativeProb1State) : stateToBlockMapping(numberOfStates), states(numberOfStates), positions(numberOfStates) { + template + Partition::Partition(std::size_t numberOfStates, storm::storage::BitVector const& prob0States, storm::storage::BitVector const& prob1States, boost::optional representativeProb1State) : stateToBlockMapping(numberOfStates), states(numberOfStates), positions(numberOfStates) { storm::storage::sparse::state_type position = 0; - Block* firstBlock = nullptr; - Block* secondBlock = nullptr; - Block* thirdBlock = nullptr; + Block* firstBlock = nullptr; + Block* secondBlock = nullptr; + Block* thirdBlock = nullptr; if (!prob0States.empty()) { - blocks.emplace_back(new Block(0, prob0States.getNumberOfSetBits(), nullptr, nullptr, blocks.size())); + blocks.emplace_back(new Block(0, prob0States.getNumberOfSetBits(), nullptr, nullptr, blocks.size())); firstBlock = blocks.front().get(); for (auto state : prob0States) { @@ -39,7 +43,7 @@ namespace storm { } if (!prob1States.empty()) { - blocks.emplace_back(new Block(position, position + prob1States.getNumberOfSetBits(), firstBlock, nullptr, blocks.size())); + blocks.emplace_back(new Block(position, position + prob1States.getNumberOfSetBits(), firstBlock, nullptr, blocks.size())); secondBlock = blocks[1].get(); for (auto state : prob1States) { @@ -55,7 +59,7 @@ namespace storm { storm::storage::BitVector otherStates = ~(prob0States | prob1States); if (!otherStates.empty()) { - blocks.emplace_back(new Block(position, numberOfStates, secondBlock, nullptr, blocks.size())); + blocks.emplace_back(new Block(position, numberOfStates, secondBlock, nullptr, blocks.size())); thirdBlock = blocks[2].get(); for (auto state : otherStates) { @@ -68,12 +72,14 @@ namespace storm { } } - void Partition::swapStates(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { + template + void Partition::swapStates(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { std::swap(this->states[this->positions[state1]], this->states[this->positions[state2]]); std::swap(this->positions[state1], this->positions[state2]); } - void Partition::swapStatesAtPositions(storm::storage::sparse::state_type position1, storm::storage::sparse::state_type position2) { + template + void Partition::swapStatesAtPositions(storm::storage::sparse::state_type position1, storm::storage::sparse::state_type position2) { storm::storage::sparse::state_type state1 = this->states[position1]; storm::storage::sparse::state_type state2 = this->states[position2]; @@ -82,64 +88,96 @@ namespace storm { this->positions[state2] = position1; } - storm::storage::sparse::state_type const& Partition::getPosition(storm::storage::sparse::state_type state) const { + template + storm::storage::sparse::state_type const& Partition::getPosition(storm::storage::sparse::state_type state) const { return this->positions[state]; } - void Partition::setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position) { + template + void Partition::setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position) { this->positions[state] = position; } - storm::storage::sparse::state_type const& Partition::getState(storm::storage::sparse::state_type position) const { + template + storm::storage::sparse::state_type const& Partition::getState(storm::storage::sparse::state_type position) const { return this->states[position]; } - void Partition::mapStatesToBlock(Block& block, std::vector::iterator first, std::vector::iterator last) { + template + void Partition::mapStatesToBlock(Block& block, std::vector::iterator first, std::vector::iterator last) { for (; first != last; ++first) { this->stateToBlockMapping[*first] = █ } } - void Partition::mapStatesToPositions(Block const& block) { + template + void Partition::mapStatesToPositions(Block const& block) { storm::storage::sparse::state_type position = block.getBeginIndex(); for (auto stateIt = this->begin(block), stateIte = this->end(block); stateIt != stateIte; ++stateIt, ++position) { this->positions[*stateIt] = position; } } - Block& Partition::getBlock(storm::storage::sparse::state_type state) { + template + Block& Partition::getBlock(storm::storage::sparse::state_type state) { return *this->stateToBlockMapping[state]; } - Block const& Partition::getBlock(storm::storage::sparse::state_type state) const { + template + Block const& Partition::getBlock(storm::storage::sparse::state_type state) const { return *this->stateToBlockMapping[state]; } - std::vector::iterator Partition::begin(Block const& block) { + template + std::vector::iterator Partition::begin(Block const& block) { auto it = this->states.begin(); std::advance(it, block.getBeginIndex()); return it; } - std::vector::const_iterator Partition::begin(Block const& block) const { + template + std::vector::const_iterator Partition::begin(Block const& block) const { auto it = this->states.begin(); std::advance(it, block.getBeginIndex()); return it; } - std::vector::iterator Partition::end(Block const& block) { + template + std::vector::iterator Partition::end(Block const& block) { auto it = this->states.begin(); std::advance(it, block.getEndIndex()); return it; } - std::vector::const_iterator Partition::end(Block const& block) const { + template + std::vector::const_iterator Partition::end(Block const& block) const { auto it = this->states.begin(); std::advance(it, block.getEndIndex()); return it; } - std::pair>::iterator, bool> Partition::splitBlock(Block& block, storm::storage::sparse::state_type position) { + template + std::vector::iterator Partition::begin() { + return this->states.begin(); + } + + template + std::vector::const_iterator Partition::begin() const { + return this->states.begin(); + } + + template + std::vector::iterator Partition::end() { + return this->states.end(); + } + + template + std::vector::const_iterator Partition::end() const { + return this->states.end(); + } + + template + std::pair>>::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; @@ -153,7 +191,7 @@ namespace storm { } // Actually create the new block. - blocks.emplace_back(new Block(block.getBeginIndex(), position, block.getPreviousBlockPointer(), &block, blocks.size())); + blocks.emplace_back(new Block(block.getBeginIndex(), position, block.getPreviousBlockPointer(), &block, blocks.size())); auto newBlockIt = std::prev(blocks.end()); // Resize the current block appropriately. @@ -170,7 +208,8 @@ namespace storm { return std::make_pair(newBlockIt, true); } - bool Partition::splitBlock(Block& block, std::function const& less, std::function const& newBlockCallback) { + template + bool Partition::splitBlock(Block& block, std::function const& less, std::function&)> 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); @@ -213,7 +252,15 @@ namespace storm { return wasSplit; } - bool Partition::split(std::function const& less, std::function const& newBlockCallback) { + // Splits the block by sorting the states according to the given function and then identifying the split + // points. + template + bool Partition::splitBlock(Block& block, std::function const& less) { + return this->splitBlock(block, less, [] (Block& block) {}); + } + + template + bool Partition::split(std::function const& less, std::function&)> 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. @@ -224,26 +271,35 @@ namespace storm { return result; } - void Partition::splitStates(Block& block, storm::storage::BitVector const& states) { + template + bool Partition::split(std::function const& less) { + return this->split(less, [] (Block& block) {}); + } + + template + 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) { + template + void Partition::splitStates(storm::storage::BitVector const& 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) { + template + void Partition::sortBlock(Block const& block) { std::sort(this->begin(block), this->end(block), [] (storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) { return a < b; }); mapStatesToPositions(block); } - Block& Partition::insertBlock(Block& block) { + template + Block& Partition::insertBlock(Block& block) { // Find the beginning of the new block. storm::storage::sparse::state_type begin = block.hasPreviousBlock() ? block.getPreviousBlock().getEndIndex() : 0; // Actually insert the new block. - blocks.emplace_back(new Block(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) { @@ -253,15 +309,18 @@ namespace storm { return newBlock; } - std::vector> const& Partition::getBlocks() const { + template + std::vector>> const& Partition::getBlocks() const { return this->blocks; } - std::vector>& Partition::getBlocks() { + template + std::vector>>& Partition::getBlocks() { return this->blocks; } - bool Partition::check() const { + template + bool Partition::check() const { for (uint_fast64_t state = 0; state < this->positions.size(); ++state) { STORM_LOG_ASSERT(this->states[this->positions[state]] == state, "Position mapping corrupted."); } @@ -274,7 +333,8 @@ namespace storm { return true; } - void Partition::print() const { + template + void Partition::print() const { for (auto const& block : this->blocks) { block->print(*this); } @@ -295,10 +355,13 @@ namespace storm { STORM_LOG_ASSERT(this->check(), "Partition corrupted."); } - std::size_t Partition::size() const { + template + std::size_t Partition::size() const { return blocks.size(); } + template class Partition; + } } } \ No newline at end of file diff --git a/src/storage/bisimulation/Partition.h b/src/storage/bisimulation/Partition.h index 0fdffa4f6..056377ad9 100644 --- a/src/storage/bisimulation/Partition.h +++ b/src/storage/bisimulation/Partition.h @@ -12,6 +12,7 @@ namespace storm { namespace storage { namespace bisimulation { + template class Partition { public: /*! @@ -49,20 +50,27 @@ namespace storm { // Splits the block at the given position and inserts a new block after the current one holding the rest // of the states. - std::pair>::iterator, bool> splitBlock(Block& block, storm::storage::sparse::state_type position); + std::pair>>::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. The callback function is called for every newly created block. - bool splitBlock(Block& block, std::function const& less, std::function const& newBlockCallback = [] (Block&) {}); + bool splitBlock(Block& block, std::function const& less, std::function&)> const& newBlockCallback); + // Splits the block by sorting the states according to the given function and then identifying the split + // points. + bool splitBlock(Block& block, std::function const& less); + // Splits all blocks by using the sorting-based splitting. The callback is called for all newly created // blocks. - bool split(std::function const& less, std::function const& newBlockCallback = [] (Block&) {}); + bool split(std::function const& less, std::function&)> const& newBlockCallback); + + // Splits all blocks by using the sorting-based splitting. + bool split(std::function const& less); // 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. - void 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 @@ -71,37 +79,49 @@ namespace storm { void splitStates(storm::storage::BitVector const& states); // Sorts the block based on the state indices. - void sortBlock(Block const& block); + void sortBlock(Block const& block); // Retrieves the blocks of the partition. - std::vector> const& getBlocks() const; + std::vector>> const& getBlocks() const; // Retrieves the blocks of the partition. - std::vector>& getBlocks(); + std::vector>>& 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::iterator begin(Block const& block); + std::vector::iterator begin(Block const& block); // Returns an iterator to the beginning of the states of the given block. - std::vector::const_iterator begin(Block const& block) const; + std::vector::const_iterator begin(Block const& block) const; // Returns an iterator to the beginning of the states of the given block. - std::vector::iterator end(Block const& block); + std::vector::iterator end(Block const& block); // Returns an iterator to the beginning of the states of the given block. - std::vector::const_iterator end(Block const& block) const; + std::vector::const_iterator end(Block const& block) const; + + // Returns an iterator to the beginning of the states in the partition. + std::vector::iterator begin(); + + // Returns an iterator to the beginning of the states in the partition. + std::vector::const_iterator begin() const; + + // Returns an iterator to the end of the states in the partition. + std::vector::iterator end(); + + // Returns an iterator to the end of the states in the partition. + std::vector::const_iterator end() const; // Swaps the positions of the two given states. void swapStates(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2); // Retrieves the block of the given state. - Block& getBlock(storm::storage::sparse::state_type state); + Block& getBlock(storm::storage::sparse::state_type state); // Retrieves the block of the given state. - Block const& getBlock(storm::storage::sparse::state_type state) const; + Block const& getBlock(storm::storage::sparse::state_type state) const; // Retrieves the position of the given state. storm::storage::sparse::state_type const& getPosition(storm::storage::sparse::state_type state) const; @@ -110,10 +130,10 @@ namespace storm { storm::storage::sparse::state_type const& getState(storm::storage::sparse::state_type position) const; // Updates the block mapping for the given range of states to the specified block. - void mapStatesToBlock(Block& block, std::vector::iterator first, std::vector::iterator last); + void mapStatesToBlock(Block& block, std::vector::iterator first, std::vector::iterator last); // Update the state to position for the states in the given block. - void mapStatesToPositions(Block const& block); + void mapStatesToPositions(Block const& block); private: // FIXME: necessary? @@ -123,17 +143,17 @@ namespace storm { // FIXME: necessary? // Inserts a block before the given block. The new block will cover all states between the beginning // of the given block and the end of the previous block. - Block& insertBlock(Block& block); + Block& insertBlock(Block& block); // FIXME: necessary? // Sets the position of the given state. void setPosition(storm::storage::sparse::state_type state, storm::storage::sparse::state_type position); // The of blocks in the partition. - std::vector> blocks; + std::vector>> blocks; // A mapping of states to their blocks. - std::vector stateToBlockMapping; + std::vector*> stateToBlockMapping; // A vector containing all the states. It is ordered in a way such that the blocks only need to define // their start/end indices. From 46fee522ff113bb6a71e23a6b9a09705ebeecc44 Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 29 Oct 2015 16:35:18 +0100 Subject: [PATCH 08/24] made strong bisim for DTMCs work again Former-commit-id: e42bafef4d0828624948a1ab2b8f3ad64f5cf79b --- .../BisimulationDecomposition.cpp | 14 +- src/storage/bisimulation/Block.cpp | 81 ++----- src/storage/bisimulation/Block.h | 54 +---- .../bisimulation/DeterministicBlockData.cpp | 93 ++++++-- .../bisimulation/DeterministicBlockData.h | 92 +++++--- ...ministicModelBisimulationDecomposition.cpp | 203 +++++++++++------- ...erministicModelBisimulationDecomposition.h | 17 +- src/storage/bisimulation/Partition.cpp | 33 +-- src/storage/bisimulation/Partition.h | 3 +- ...sticModelBisimulationDecompositionTest.cpp | 4 +- 10 files changed, 295 insertions(+), 299 deletions(-) diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp index f22ddcc39..79d154b78 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.cpp +++ b/src/storage/bisimulation/BisimulationDecomposition.cpp @@ -192,9 +192,9 @@ namespace storm { template void BisimulationDecomposition::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*> splitterQueue; - std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (std::unique_ptr> const& block) { if (block->isMarkedAsSplitter()) { splitterQueue.push_back(block.get()); } } ); + std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (std::unique_ptr> 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* 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 @@ -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 @@ -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 diff --git a/src/storage/bisimulation/Block.cpp b/src/storage/bisimulation/Block.cpp index 52dafb06d..bb38b5f86 100644 --- a/src/storage/bisimulation/Block.cpp +++ b/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 - 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), mData() { + 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), 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 void Block::print(Partition 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 void Block::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 void Block::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 + std::size_t Block::getId() const { + return this->id; + } + template storm::storage::sparse::state_type Block::getBeginIndex() const { return this->beginIndex; @@ -116,65 +120,7 @@ namespace storm { std::size_t Block::getNumberOfStates() const { return (this->endIndex - this->beginIndex); } - - template - bool Block::isMarkedAsSplitter() const { - return this->markedAsSplitter; - } - - template - void Block::markAsSplitter() { - this->markedAsSplitter = true; - } - - template - void Block::unmarkAsSplitter() { - this->markedAsSplitter = false; - } - - template - std::size_t Block::getId() const { - return this->id; - } - - template - void Block::setAbsorbing(bool absorbing) { - this->absorbing = absorbing; - } - - template - bool Block::isAbsorbing() const { - return this->absorbing; - } - - template - void Block::setRepresentativeState(storm::storage::sparse::state_type representativeState) { - this->representativeState = representativeState; - } - - template - bool Block::hasRepresentativeState() const { - return static_cast(representativeState); - } - - template - storm::storage::sparse::state_type Block::getRepresentativeState() const { - STORM_LOG_THROW(representativeState, storm::exceptions::InvalidOperationException, "Unable to retrieve representative state for block."); - return representativeState.get(); - } - - // Retrieves whether the block is marked as a predecessor. - template - bool Block::needsRefinement() const { - return needsRefinementFlag; - } - - // Marks the block as needing refinement (or not). - template - void Block::setNeedsRefinement(bool value) { - needsRefinementFlag = value; - } - + template DataType& Block::data() { return mData; @@ -184,6 +130,11 @@ namespace storm { DataType const& Block::data() const { return mData; } + + template + void Block::resetMarkers() { + mData.resetMarkers(*this); + } template class Block; diff --git a/src/storage/bisimulation/Block.h b/src/storage/bisimulation/Block.h index dd504ed08..acb736eec 100644 --- a/src/storage/bisimulation/Block.h +++ b/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 representativeState; - + // A member that stores additional data that depends on the kind of bisimulation. DataType mData; }; diff --git a/src/storage/bisimulation/DeterministicBlockData.cpp b/src/storage/bisimulation/DeterministicBlockData.cpp index e85acf729..3a8edced4 100644 --- a/src/storage/bisimulation/DeterministicBlockData.cpp +++ b/src/storage/bisimulation/DeterministicBlockData.cpp @@ -1,44 +1,101 @@ #include "src/storage/bisimulation/DeterministicBlockData.h" +#include + +#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 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 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(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; + } } } } diff --git a/src/storage/bisimulation/DeterministicBlockData.h b/src/storage/bisimulation/DeterministicBlockData.h index 4c02bf0ce..518004efa 100644 --- a/src/storage/bisimulation/DeterministicBlockData.h +++ b/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 const& block); + bool resetMarkers(Block 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 valRepresentativeState; }; + + std::ostream& operator<<(std::ostream& out, DeterministicBlockData const& data); } } } diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp index 3710755a0..ac2128327 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp +++ b/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* 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& 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 bool DeterministicModelBisimulationDecomposition::possiblyNeedsRefinement(bisimulation::Block const& predecessorBlock) const { - return predecessorBlock.getNumberOfStates() <= 1 || predecessorBlock.isAbsorbing(); + return predecessorBlock.getNumberOfStates() > 1 && !predecessorBlock.data().absorbing(); } template void DeterministicModelBisimulationDecomposition::increaseProbabilityToSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block 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 - void DeterministicModelBisimulationDecomposition::moveStateToNewBeginningOfBlock(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock) { - this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().getNewBeginIndex())); - predecessorBlock.data().increaseNewBeginIndex(); + void DeterministicModelBisimulationDecomposition::moveStateToMarker1(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock) { + this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker1())); + predecessorBlock.data().incrementMarker1(); } - + template - void DeterministicModelBisimulationDecomposition::moveStateToNewEndOfBlock(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock) { - this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().getNewEndIndex() - 1)); - predecessorBlock.data().decreaseNewEndIndex(); + void DeterministicModelBisimulationDecomposition::moveStateToMarker2(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock) { + this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker2())); + predecessorBlock.data().incrementMarker2(); } template - void DeterministicModelBisimulationDecomposition::moveStateInSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter) { + void DeterministicModelBisimulationDecomposition::moveStateInSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block& 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 - void DeterministicModelBisimulationDecomposition::explorePredecessorsOfNewEndOfSplitter(bisimulation::Block& splitter) { - for (auto splitterIt = this->partition.begin() + splitter.data().getNewEndIndex(), splitterIte = this->partition.end(splitter); splitterIt != splitterIte; ++splitterIt) { + void DeterministicModelBisimulationDecomposition::exploreRemainingStatesOfSplitter(bisimulation::Block& 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& 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 void DeterministicModelBisimulationDecomposition::refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& 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*> 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& 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 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()); // 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 diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h index 74cedebb1..0a5153786 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h +++ b/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 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& 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& 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& 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& 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& 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& 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 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& splitter); + // Explores the remaining predecessors of the splitter. + void exploreRemainingStatesOfSplitter(bisimulation::Block& 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. diff --git a/src/storage/bisimulation/Partition.cpp b/src/storage/bisimulation/Partition.cpp index 9597935dc..8f60fd6ba 100644 --- a/src/storage/bisimulation/Partition.cpp +++ b/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(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(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::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>>::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()) { @@ -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 bool Partition::splitBlock(Block& block, std::function const& less, std::function&)> 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. diff --git a/src/storage/bisimulation/Partition.h b/src/storage/bisimulation/Partition.h index 056377ad9..5cb45b7e7 100644 --- a/src/storage/bisimulation/Partition.h +++ b/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 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. diff --git a/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp b/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp index 881e2514a..3bb1b97e7 100644 --- a/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp +++ b/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp @@ -21,9 +21,9 @@ TEST(DeterministicModelBisimulationDecomposition, Die) { EXPECT_EQ(20ul, result->getNumberOfTransitions()); #ifdef WINDOWS - storm::storage::BisimulationDecomposition>::Options options; + storm::storage::DeterministicModelBisimulationDecomposition>::Options options; #else - typename storm::storage::BisimulationDecomposition>::Options options; + typename storm::storage::DeterministicModelBisimulationDecomposition>::Options options; #endif options.respectedAtomicPropositions = std::set({"one"}); From 91bfdba528f0e39bc151a7d5eca5f506f63f4042 Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 29 Oct 2015 18:40:51 +0100 Subject: [PATCH 09/24] Scan for equal ranges of probabilities now uses std::equal_range and reduces the number of comparisons Former-commit-id: 3154d77a3fb35b91b5964839e363c80c90de5cdc --- src/storage/bisimulation/Partition.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/storage/bisimulation/Partition.cpp b/src/storage/bisimulation/Partition.cpp index 8f60fd6ba..d14034f53 100644 --- a/src/storage/bisimulation/Partition.cpp +++ b/src/storage/bisimulation/Partition.cpp @@ -212,16 +212,10 @@ namespace storm { 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 && !less(states[begin], states[currentIndex])) { - ++currentIndex; - } - begin = currentIndex; - auto result = this->splitBlock(block, currentIndex); + auto range = std::equal_range(states.begin() + begin, states.begin() + end, states[begin], less); + begin = std::distance(states.begin(), range.second); + auto result = this->splitBlock(block, begin); if (result.second) { newBlockCallback(**result.first); } From 29597e014fad341ae43cf0e42be5146aea0d8179 Mon Sep 17 00:00:00 2001 From: dehnert Date: Fri, 30 Oct 2015 16:58:09 +0100 Subject: [PATCH 10/24] more work on reimplementation of weak bisim Former-commit-id: 5bdd8ea139cee1f8222828347d1a04967b411e4b --- .../BisimulationDecomposition.cpp | 3 + src/storage/bisimulation/Block.h | 2 +- ...ministicModelBisimulationDecomposition.cpp | 219 ++++++++++++++++-- ...erministicModelBisimulationDecomposition.h | 28 ++- src/storage/bisimulation/Partition.cpp | 88 +++++-- src/storage/bisimulation/Partition.h | 12 + 6 files changed, 308 insertions(+), 44 deletions(-) diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp index 79d154b78..e42b56228 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.cpp +++ b/src/storage/bisimulation/BisimulationDecomposition.cpp @@ -160,6 +160,9 @@ namespace storm { this->performPartitionRefinement(); std::chrono::high_resolution_clock::duration refinementTime = std::chrono::high_resolution_clock::now() - refinementStart; + std::cout << "final partition: " << std::endl; + this->partition.print(); + std::chrono::high_resolution_clock::time_point extractionStart = std::chrono::high_resolution_clock::now(); this->extractDecompositionBlocks(); std::chrono::high_resolution_clock::duration extractionTime = std::chrono::high_resolution_clock::now() - extractionStart; diff --git a/src/storage/bisimulation/Block.h b/src/storage/bisimulation/Block.h index acb736eec..f9f46051f 100644 --- a/src/storage/bisimulation/Block.h +++ b/src/storage/bisimulation/Block.h @@ -20,7 +20,7 @@ namespace storm { // Creates a new block with the given begin and end. Block(storm::storage::sparse::state_type beginIndex, storm::storage::sparse::state_type endIndex, Block* previous, Block* next, std::size_t id); - + Block() = default; Block(Block const& other) = default; Block& operator=(Block const& other) = default; diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp index ac2128327..58296b254 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include "src/adapters/CarlAdapter.h" #include "src/modelchecker/results/ExplicitQualitativeCheckResult.h" @@ -45,10 +45,12 @@ namespace storm { stateStack.reserve(this->model.getNumberOfStates()); storm::storage::BitVector nondivergentStates(this->model.getNumberOfStates()); - for (auto const& blockPtr : this->partition.getBlocks()) { + uint_fast64_t currentSize = this->partition.size(); + for (uint_fast64_t blockIndex = 0; blockIndex < currentSize; ++blockIndex) { + auto& block = *this->partition.getBlocks()[blockIndex]; nondivergentStates.clear(); - for (auto stateIt = this->partition.begin(*blockPtr), stateIte = this->partition.end(*blockPtr); stateIt != stateIte; ++stateIt) { + for (auto stateIt = this->partition.begin(block), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) { if (nondivergentStates.get(*stateIt)) { continue; } @@ -59,7 +61,7 @@ namespace storm { 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()) != *blockPtr) { + if (this->partition.getBlock(successor.getColumn()) != block) { isDirectlyNonDivergent = true; break; } @@ -74,7 +76,7 @@ namespace storm { nondivergentStates.set(currentState); for (auto const& predecessor : this->backwardTransitions.getRow(currentState)) { - if (this->partition.getBlock(predecessor.getColumn()) == *blockPtr && !nondivergentStates.get(predecessor.getColumn())) { + if (this->partition.getBlock(predecessor.getColumn()) == block && !nondivergentStates.get(predecessor.getColumn())) { stateStack.push_back(predecessor.getColumn()); } } @@ -82,18 +84,17 @@ namespace storm { } } - - if (nondivergentStates.getNumberOfSetBits() > 0 && nondivergentStates.getNumberOfSetBits() != blockPtr->getNumberOfStates()) { + if (!nondivergentStates.empty() && nondivergentStates.getNumberOfSetBits() != block.getNumberOfStates()) { // After performing the split, the current block will contain the divergent states only. - this->partition.splitStates(*blockPtr, nondivergentStates); + this->partition.splitStates(block, nondivergentStates); // Since the remaining states in the block are divergent, we can mark the block as absorbing. // This also guarantees that the self-loop will be added to the state of the quotient // representing this block of states. - blockPtr->data().setAbsorbing(true); - } else if (nondivergentStates.getNumberOfSetBits() == 0) { + block.data().setAbsorbing(true); + } else if (nondivergentStates.empty()) { // If there are only diverging states in the block, we need to make it absorbing. - blockPtr->data().setAbsorbing(true); + block.data().setAbsorbing(true); } } } @@ -147,13 +148,18 @@ namespace storm { return this->comparator.isOne(silentProbabilities[state]); } + template + bool DeterministicModelBisimulationDecomposition::hasNonZeroSilentProbability(storm::storage::sparse::state_type const& state) const { + return !this->comparator.isZero(silentProbabilities[state]); + } + template typename DeterministicModelBisimulationDecomposition::ValueType DeterministicModelBisimulationDecomposition::getSilentProbability(storm::storage::sparse::state_type const& state) const { return silentProbabilities[state]; } template - void DeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitter(std::list*>& predecessorBlocks, std::deque*>& splitterQueue) { + void DeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitterStrong(std::list*> const& predecessorBlocks, std::deque*>& splitterQueue) { for (auto block : predecessorBlocks) { // Depending on the actions we need to take, the block to refine changes, so we need to keep track of it. Block* blockToRefineProbabilistically = block; @@ -161,9 +167,8 @@ namespace storm { 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; + this->partition.splitBlock(*block, block->data().marker1()); blockToRefineProbabilistically = block->getPreviousBlockPointer(); } @@ -243,7 +248,7 @@ namespace storm { this->partition.swapStatesAtPositions(predecessorPosition, currentPositionInSplitter + elementsToSkip + 1); } - // Since we had to move an already explored state to the right of the current position, + // Since we had to move an already explored state to the right of the current position, ++elementsToSkip; predecessorBlock.data().incrementMarker1(); predecessorBlock.data().incrementMarker2(); @@ -266,7 +271,7 @@ namespace storm { // We keep track of the probability of the predecessor moving to the splitter. increaseProbabilityToSplitter(predecessor, predecessorBlock, predecessorEntry.getValue()); - + // 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()) { @@ -279,6 +284,169 @@ namespace storm { splitter.data().setMarker2(splitter.getBeginIndex()); } + template + void DeterministicModelBisimulationDecomposition::updateSilentProbabilitiesBasedOnProbabilitiesToSplitter(bisimulation::Block& block) { + // For all states that do not have a successor in the block itself, we need to set the silent probability to 0. + std::for_each(silentProbabilities.begin() + block.data().marker1(), silentProbabilities.begin() + block.getEndIndex(), [] (ValueType& val) { val = storm::utility::zero(); } ); + + // For all states that do have a successor in the block, we set the silent probability to the probability + // stored in the vector of probabilities going to the splitter. + auto it = boost::make_zip_iterator(boost::make_tuple(silentProbabilities.begin() + block.getBeginIndex(), probabilitiesToCurrentSplitter.begin() + block.getBeginIndex())); + auto ite = boost::make_zip_iterator(boost::make_tuple(silentProbabilities.begin() + block.data().marker1(), probabilitiesToCurrentSplitter.begin() + block.data().marker1())); + std::for_each(it, ite, [] (boost::tuple const& tuple) { boost::get<0>(tuple) = boost::get<1>(tuple); } ); + } + + template + void DeterministicModelBisimulationDecomposition::updateSilentProbabilitiesBasedOnTransitions(bisimulation::Block& block) { + for (auto stateIt = this->partition.begin(block), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) { + if (hasNonZeroSilentProbability(*stateIt)) { + ValueType newSilentProbability = storm::utility::zero(); + for (auto const& successorEntry : this->model.getTransitionMatrix().getRow(*stateIt)) { + if (this->partition.getBlock(successorEntry.getColumn()) == block) { + newSilentProbability += successorEntry.getValue(); + } + } + silentProbabilities[*stateIt] = newSilentProbability; + } + } + } + + template + void DeterministicModelBisimulationDecomposition::computeConditionalProbabilitiesForNonSilentStates(bisimulation::Block& block) { + auto it = boost::make_zip_iterator(boost::make_tuple(silentProbabilities.begin() + block.getBeginIndex(), probabilitiesToCurrentSplitter.begin() + block.getBeginIndex())); + auto ite = boost::make_zip_iterator(boost::make_tuple(silentProbabilities.begin() + block.data().marker1(), probabilitiesToCurrentSplitter.begin() + block.data().marker1())); + std::for_each(it, ite, [this] (boost::tuple const& tuple) { + if (!this->comparator.isZero(boost::get<0>(tuple))) { + boost::get<1>(tuple) /= storm::utility::one() - boost::get<0>(tuple); + } + } ); + } + + template + std::vector DeterministicModelBisimulationDecomposition::computeNonSilentBlocks(bisimulation::Block& block) { + auto less = [this] (storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { return probabilitiesToCurrentSplitter[state1] < probabilitiesToCurrentSplitter[state2]; }; + this->partition.sortRange(block.getBeginIndex(), block.data().marker1(), less); + return this->partition.computeRangesOfEqualValue(block.getBeginIndex(), block.data().marker1(), less); + } + + template + std::vector DeterministicModelBisimulationDecomposition::computeWeakStateLabelingBasedOnNonSilentBlocks(bisimulation::Block const& block, std::vector const& nonSilentBlockIndices) { + // 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 stateLabels(block.getNumberOfStates(), storm::storage::BitVector(nonSilentBlockIndices.size() - 1)); + std::cout << "creating " << block.getNumberOfStates() << " labels " << std::endl; + + std::vector stateStack; + stateStack.reserve(block.getNumberOfStates()); + for (uint_fast64_t stateClassIndex = 0; stateClassIndex < nonSilentBlockIndices.size() - 1; ++stateClassIndex) { + for (auto stateIt = this->partition.begin() + nonSilentBlockIndices[stateClassIndex], stateIte = this->partition.begin() + nonSilentBlockIndices[stateClassIndex + 1]; stateIt != stateIte; ++stateIt) { + std::cout << "moving backward from state " << *stateIt << std::endl; + + stateStack.push_back(*stateIt); + stateLabels[this->partition.getPosition(*stateIt) - block.getBeginIndex()].set(stateClassIndex); + while (!stateStack.empty()) { + storm::storage::sparse::state_type currentState = stateStack.back(); + stateStack.pop_back(); + + for (auto const& predecessorEntry : this->backwardTransitions.getRow(currentState)) { + storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn(); + + std::cout << " found pred " << predecessor << " with value " << predecessorEntry.getValue() << std::endl; + if (this->comparator.isZero(predecessorEntry.getValue())) { + continue; + } + + // Only if the state is in the same block, is a silent state and it has not yet been + // labeled with the current label. + std::cout << "pred is at " << this->partition.getPosition(predecessor) << std::endl; + std::cout << "begin of block " << block.getBeginIndex() << std::endl; + std::cout << "accessing index " << (this->partition.getPosition(predecessor) - block.getBeginIndex()) << " of " << stateLabels.size() << std::endl; + std::cout << predecessor << " is in the same block as " << block.getId() << " ? " << (this->partition.getBlock(predecessor) == block) << std::endl; +// std::cout << isSilent(predecessor) << ", " << stateLabels[this->partition.getPosition(predecessor) - block.getBeginIndex()].get(stateClassIndex) << std::endl; + if (this->partition.getBlock(predecessor) == block && isSilent(predecessor) && !stateLabels[this->partition.getPosition(predecessor) - block.getBeginIndex()].get(stateClassIndex)) { + std::cout << "assigning label " << stateClassIndex << std::endl; + stateStack.push_back(predecessor); + stateLabels[this->partition.getPosition(predecessor) - block.getBeginIndex()].set(stateClassIndex); + } + } + } + } + } + + return stateLabels; + } + + template + void DeterministicModelBisimulationDecomposition::refinePredecessorBlockOfSplitterWeak(bisimulation::Block& block, std::deque*>& splitterQueue) { + computeConditionalProbabilitiesForNonSilentStates(block); + // First, we need to compute a labeling of the states that expresses which of the non-silent blocks they can reach. + std::vector nonSilentBlockIndices = computeNonSilentBlocks(block); + std::cout << "indices: " << std::endl; + for (auto el : nonSilentBlockIndices) { + std::cout << el << std::endl; + } + + for (int ind = 0; ind < nonSilentBlockIndices.size() - 1; ++ind) { + for (int inner = nonSilentBlockIndices[ind]; inner < nonSilentBlockIndices[ind + 1]; ++inner) { + std::cout << this->partition.getState(inner) << " is in class " << ind << std::endl; + } + } + + std::vector weakStateLabels = computeWeakStateLabelingBasedOnNonSilentBlocks(block, nonSilentBlockIndices); + + std::cout << "labels: " << std::endl; + for (auto el : weakStateLabels) { + std::cout << el << std::endl; + } + + // Then split the block according to this labeling. + // CAUTION: that this assumes that the positions of the states in the partition are not update until after + // the sorting is over. Otherwise, this interferes with the data used in the sorting process. + storm::storage::sparse::state_type originalBlockIndex = block.getBeginIndex(); + auto result = this->partition.splitBlock(block, + [&weakStateLabels,&block,originalBlockIndex,this] (storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { + std::cout << "comparing states " << state1 << " and " << state2 << std::endl; + std::cout << (this->partition.getPosition(state1) - originalBlockIndex) << " and " << (this->partition.getPosition(state2) - originalBlockIndex) << std::endl; + std::cout << "size: " << weakStateLabels.size() << std::endl; + std::cout << "begin is " << block.getBeginIndex() << std::endl; + std::cout << "orig begin " << originalBlockIndex << std::endl; + return weakStateLabels[this->partition.getPosition(state1) - originalBlockIndex] < weakStateLabels[this->partition.getPosition(state2) - originalBlockIndex]; + }, + [this, &splitterQueue] (bisimulation::Block& block) { + updateSilentProbabilitiesBasedOnTransitions(block); + + // Insert the new block as a splitter. + block.data().setSplitter(); + splitterQueue.emplace_back(&block); + }); + + // If the block was split, we also update the silent probabilities of the + if (result) { + updateSilentProbabilitiesBasedOnTransitions(block); + + // Insert the new block as a splitter. + block.data().setSplitter(); + splitterQueue.emplace_back(&block); + } else { + block.resetMarkers(); + } + } + + template + void DeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitterWeak(bisimulation::Block& splitter, std::list*> const& predecessorBlocks, std::deque*>& splitterQueue) { + for (auto block : predecessorBlocks) { + std::cout << "found predecessor block " << block->getId() << std::endl; + if (*block != splitter) { + refinePredecessorBlockOfSplitterWeak(*block, splitterQueue); + } else { + // If the block to split is the splitter itself, we must not do any splitting here. + block->resetMarkers(); + } + + block->data().setNeedsRefinement(false); + } + } + template void DeterministicModelBisimulationDecomposition::refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) { // The outline of the refinement is as follows. @@ -297,10 +465,11 @@ namespace storm { // 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. - // (1) - std::list*> predecessorBlocks; + std::cout << "current partition is" << std::endl; + this->partition.print(); + std::cout << "refining using splitter " << splitter.getId() << std::endl; - // (2) + std::list*> predecessorBlocks; storm::storage::sparse::state_type currentPosition = splitter.getBeginIndex(); bool splitterIsPredecessorBlock = false; for (auto splitterIt = this->partition.begin(splitter), splitterIte = this->partition.end(splitter); splitterIt != splitterIte; ++splitterIt, ++currentPosition) { @@ -330,7 +499,7 @@ namespace storm { 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); @@ -352,9 +521,15 @@ namespace storm { // 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); + refinePredecessorBlocksOfSplitterStrong(predecessorBlocks, splitterQueue); } else { - assert(false); + // If the splitter is a predecessor of we can use the computed probabilities to update the silent + // probabilities. + if (splitterIsPredecessorBlock) { + updateSilentProbabilitiesBasedOnProbabilitiesToSplitter(splitter); + } + + refinePredecessorBlocksOfSplitterWeak(splitter, predecessorBlocks, splitterQueue); } } diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h index 0a5153786..4f0c5dd76 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h @@ -42,7 +42,7 @@ namespace storm { virtual void refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) override; private: - virtual void refinePredecessorBlocksOfSplitter(std::list*>& predecessorBlocks, std::deque*>& splitterQueue); + virtual void refinePredecessorBlocksOfSplitterStrong(std::list*> const& predecessorBlocks, std::deque*>& splitterQueue); /*! * Performs the necessary steps to compute a weak bisimulation on a DTMC. @@ -69,6 +69,9 @@ namespace storm { // Retrieves whether the given state is silent. bool isSilent(storm::storage::sparse::state_type const& state) const; + // Retrieves whether the given state has a non-zero silent probability. + bool hasNonZeroSilentProbability(storm::storage::sparse::state_type const& state) const; + // Retrieves whether the given predecessor of the splitters possibly needs refinement. bool possiblyNeedsRefinement(bisimulation::Block const& predecessorBlock) const; @@ -87,6 +90,29 @@ namespace storm { // Explores the remaining predecessors of the splitter. void exploreRemainingStatesOfSplitter(bisimulation::Block& splitter); + // Updates the silent probabilities of the states in the block based on the probabilities of going to the splitter. + void updateSilentProbabilitiesBasedOnProbabilitiesToSplitter(bisimulation::Block& block); + + // Updates the silent probabilities of the states in the block based on a forward exploration of the transitions + // of the states. + void updateSilentProbabilitiesBasedOnTransitions(bisimulation::Block& block); + + // Refines the predecessor blocks of the splitter wrt. weak bisimulation in DTMCs. + void refinePredecessorBlocksOfSplitterWeak(bisimulation::Block& splitter, std::list*> const& predecessorBlocks, std::deque*>& splitterQueue); + + // Refines the given block wrt to weak bisimulation in DTMCs. + void refinePredecessorBlockOfSplitterWeak(bisimulation::Block& block, std::deque*>& splitterQueue); + + // Converts the one-step probabilities of going into the splitter into the conditional probabilities needed + // for weak bisimulation (on DTMCs). + void computeConditionalProbabilitiesForNonSilentStates(bisimulation::Block& block); + + // Computes the (indices of the) blocks of non-silent states within the block. + std::vector computeNonSilentBlocks(bisimulation::Block& block); + + // Computes a labeling for all states of the block that identifies in which block they need to end up. + std::vector computeWeakStateLabelingBasedOnNonSilentBlocks(bisimulation::Block const& block, std::vector const& nonSilentBlockIndices); + // 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 probabilitiesToCurrentSplitter; diff --git a/src/storage/bisimulation/Partition.cpp b/src/storage/bisimulation/Partition.cpp index d14034f53..ff432fa11 100644 --- a/src/storage/bisimulation/Partition.cpp +++ b/src/storage/bisimulation/Partition.cpp @@ -107,13 +107,18 @@ namespace storm { } template - void Partition::mapStatesToPositions(Block const& block) { - storm::storage::sparse::state_type position = block.getBeginIndex(); - for (auto stateIt = this->begin(block), stateIte = this->end(block); stateIt != stateIte; ++stateIt, ++position) { - this->positions[*stateIt] = position; + void Partition::mapStatesToPositions(std::vector::const_iterator first, std::vector::const_iterator last) { + storm::storage::sparse::state_type position = std::distance(this->states.cbegin(), first); + for (; first != last; ++first, ++position) { + this->positions[*first] = position; } } + template + void Partition::mapStatesToPositions(Block const& block) { + mapStatesToPositions(this->begin(block), this->end(block)); + } + template Block& Partition::getBlock(storm::storage::sparse::state_type state) { return *this->stateToBlockMapping[state]; @@ -172,10 +177,42 @@ namespace storm { return this->states.end(); } + template + void Partition::sortRange(storm::storage::sparse::state_type beginIndex, storm::storage::sparse::state_type endIndex, std::function const& less, bool updatePositions) { + std::sort(this->states.begin() + beginIndex, this->states.begin() + endIndex, less); + if (updatePositions) { + mapStatesToPositions(this->states.begin() + beginIndex, this->states.begin() + endIndex); + } + } + + template + void Partition::sortBlock(Block& block, std::function const& less, bool updatePositions) { + sortRange(block.getBeginIndex(), block.getEndIndex(), less, updatePositions); + } + + template + std::vector Partition::computeRangesOfEqualValue(uint_fast64_t startIndex, uint_fast64_t endIndex, std::function const& less) { + auto it = this->states.cbegin() + startIndex; + auto ite = this->states.cbegin() + endIndex; + + std::vector::const_iterator upperBound; + std::vector result; + result.push_back(startIndex); + do { + upperBound = std::upper_bound(it, ite, *it, less); + result.push_back(std::distance(this->states.cbegin(), upperBound)); + it = upperBound; + } while (upperBound != ite); + + return result; + } + template std::pair>>::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 " << block.getId() << " at pos " << position << 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()) { @@ -199,27 +236,38 @@ namespace storm { template bool Partition::splitBlock(Block& block, std::function const& less, std::function&)> const& newBlockCallback) { - // 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); + // Sort the block, but leave the positions untouched. + this->sortBlock(block, less, false); - // Update the positions vector. - mapStatesToPositions(block); + auto originalBegin = block.getBeginIndex(); + auto originalEnd = block.getEndIndex(); + + std::cout << "sorted block:" << std::endl; + for (auto stateIt = this->begin(block), stateIte = this->end(block); stateIt != stateIte; ++stateIt) { + std::cout << *stateIt << std::endl; + } + + auto it = this->states.cbegin() + block.getBeginIndex(); + auto ite = this->states.cbegin() + block.getEndIndex(); + std::cout << "splitting between " << block.getBeginIndex() << " and " << block.getEndIndex() << 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; bool wasSplit = false; - while (less(states[begin], states[end])) { - wasSplit = true; - - auto range = std::equal_range(states.begin() + begin, states.begin() + end, states[begin], less); - begin = std::distance(states.begin(), range.second); - auto result = this->splitBlock(block, begin); - if (result.second) { + std::vector::const_iterator upperBound; + do { + std::cout << "it (" << *it << ") less than ite-1 (" << *(ite-1) << "? " << less(*it, *(ite - 1)) << std::endl; + upperBound = std::upper_bound(it, ite, *it, less); + std::cout << "upper bound is " << std::distance(this->states.cbegin(), upperBound); + if (upperBound != ite) { + wasSplit = true; + auto result = this->splitBlock(block, std::distance(this->states.cbegin(), upperBound)); newBlockCallback(**result.first); } - } + it = upperBound; + } while (upperBound != ite); + + // Finally, repair the positions mapping. + mapStatesToPositions(this->states.begin() + originalBegin, this->states.begin() + originalEnd); + return wasSplit; } diff --git a/src/storage/bisimulation/Partition.h b/src/storage/bisimulation/Partition.h index 5cb45b7e7..b78ae62bb 100644 --- a/src/storage/bisimulation/Partition.h +++ b/src/storage/bisimulation/Partition.h @@ -52,6 +52,15 @@ namespace storm { // of the states. std::pair>>::iterator, bool> splitBlock(Block& block, storm::storage::sparse::state_type position); + // Sorts the given range of the partitition according to the given order. + void sortRange(storm::storage::sparse::state_type beginIndex, storm::storage::sparse::state_type endIndex, std::function const& less, bool updatePositions = true); + + // Sorts the block according to the given order. + void sortBlock(Block& block, std::function const& less, bool updatePositions = true); + + // Computes the start indices of equal ranges within the given range wrt. to the given less function. + std::vector computeRangesOfEqualValue(uint_fast64_t startIndex, uint_fast64_t endIndex, std::function const& less); + // Splits the block by sorting the states according to the given function and then identifying the split // points. The callback function is called for every newly created block. bool splitBlock(Block& block, std::function const& less, std::function&)> const& newBlockCallback); @@ -134,6 +143,9 @@ namespace storm { // Update the state to position for the states in the given block. void mapStatesToPositions(Block const& block); + + // Update the state to position for the states in the given range. + void mapStatesToPositions(std::vector::const_iterator first, std::vector::const_iterator last); // 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); From 9475d29164032eeb57a73f3edd738ee42abeab21 Mon Sep 17 00:00:00 2001 From: dehnert Date: Sat, 31 Oct 2015 15:23:09 +0100 Subject: [PATCH 11/24] fixed weak bisim for dtmc Former-commit-id: 05462b99ecda021afc7202f7fc84c4f3e085825a --- .../BisimulationDecomposition.cpp | 5 +- src/storage/bisimulation/Block.cpp | 5 + ...ministicModelBisimulationDecomposition.cpp | 110 +++++++----------- ...erministicModelBisimulationDecomposition.h | 5 +- src/storage/bisimulation/Partition.cpp | 10 -- 5 files changed, 51 insertions(+), 84 deletions(-) diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp index e42b56228..178b5a965 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.cpp +++ b/src/storage/bisimulation/BisimulationDecomposition.cpp @@ -160,9 +160,6 @@ namespace storm { this->performPartitionRefinement(); std::chrono::high_resolution_clock::duration refinementTime = std::chrono::high_resolution_clock::now() - refinementStart; - std::cout << "final partition: " << std::endl; - this->partition.print(); - std::chrono::high_resolution_clock::time_point extractionStart = std::chrono::high_resolution_clock::now(); this->extractDecompositionBlocks(); std::chrono::high_resolution_clock::duration extractionTime = std::chrono::high_resolution_clock::now() - extractionStart; @@ -197,7 +194,7 @@ namespace storm { void BisimulationDecomposition::performPartitionRefinement() { // Insert all blocks into the splitter queue as a (potential) splitter. std::deque*> splitterQueue; - std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (std::unique_ptr> const& block) { splitterQueue.push_back(block.get()); } ); + std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&] (std::unique_ptr> const& block) { block->data().setSplitter(); splitterQueue.push_back(block.get()); } ); // Then perform the actual splitting until there are no more splitters. uint_fast64_t iterations = 0; diff --git a/src/storage/bisimulation/Block.cpp b/src/storage/bisimulation/Block.cpp index bb38b5f86..812e3b89b 100644 --- a/src/storage/bisimulation/Block.cpp +++ b/src/storage/bisimulation/Block.cpp @@ -37,6 +37,11 @@ namespace storm { template void Block::print(Partition const& partition) const { std::cout << "block [" << this << "] " << this->id << " from " << this->beginIndex << " to " << this->endIndex << " with data [" << this->data() << "]" << std::endl; + std::cout << "states "; + for (auto stateIt = partition.begin(*this), stateIte = partition.end(*this); stateIt != stateIte; ++stateIt) { + std::cout << *stateIt << ", "; + } + std::cout << std::endl; } template diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp index 58296b254..5805fe15d 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp @@ -256,7 +256,7 @@ namespace storm { } template - void DeterministicModelBisimulationDecomposition::exploreRemainingStatesOfSplitter(bisimulation::Block& splitter) { + void DeterministicModelBisimulationDecomposition::exploreRemainingStatesOfSplitter(bisimulation::Block& splitter, std::list*>& predecessorBlocks) { 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; @@ -268,7 +268,7 @@ namespace storm { if (!possiblyNeedsRefinement(predecessorBlock)) { continue; } - + // We keep track of the probability of the predecessor moving to the splitter. increaseProbabilityToSplitter(predecessor, predecessorBlock, predecessorEntry.getValue()); @@ -277,6 +277,8 @@ namespace storm { if (predecessorPosition >= predecessorBlock.data().marker1()) { moveStateToMarker1(predecessor, predecessorBlock); } + + insertIntoPredecessorList(predecessorBlock, predecessorBlocks); } } @@ -286,14 +288,14 @@ namespace storm { template void DeterministicModelBisimulationDecomposition::updateSilentProbabilitiesBasedOnProbabilitiesToSplitter(bisimulation::Block& block) { - // For all states that do not have a successor in the block itself, we need to set the silent probability to 0. - std::for_each(silentProbabilities.begin() + block.data().marker1(), silentProbabilities.begin() + block.getEndIndex(), [] (ValueType& val) { val = storm::utility::zero(); } ); - - // For all states that do have a successor in the block, we set the silent probability to the probability - // stored in the vector of probabilities going to the splitter. - auto it = boost::make_zip_iterator(boost::make_tuple(silentProbabilities.begin() + block.getBeginIndex(), probabilitiesToCurrentSplitter.begin() + block.getBeginIndex())); - auto ite = boost::make_zip_iterator(boost::make_tuple(silentProbabilities.begin() + block.data().marker1(), probabilitiesToCurrentSplitter.begin() + block.data().marker1())); - std::for_each(it, ite, [] (boost::tuple const& tuple) { boost::get<0>(tuple) = boost::get<1>(tuple); } ); + // For all predecessors, we can set the probability to the current probability of moving to the splitter. + for (auto stateIt = this->partition.begin(block), stateIte = this->partition.begin() + block.data().marker1(); stateIt != stateIte; ++stateIt) { + silentProbabilities[*stateIt] = probabilitiesToCurrentSplitter[*stateIt]; + } + // All non-predecessors have a silent probability of zero. + for (auto stateIt = this->partition.begin() + block.data().marker1(), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) { + silentProbabilities[*stateIt] = storm::utility::zero(); + } } template @@ -313,13 +315,11 @@ namespace storm { template void DeterministicModelBisimulationDecomposition::computeConditionalProbabilitiesForNonSilentStates(bisimulation::Block& block) { - auto it = boost::make_zip_iterator(boost::make_tuple(silentProbabilities.begin() + block.getBeginIndex(), probabilitiesToCurrentSplitter.begin() + block.getBeginIndex())); - auto ite = boost::make_zip_iterator(boost::make_tuple(silentProbabilities.begin() + block.data().marker1(), probabilitiesToCurrentSplitter.begin() + block.data().marker1())); - std::for_each(it, ite, [this] (boost::tuple const& tuple) { - if (!this->comparator.isZero(boost::get<0>(tuple))) { - boost::get<1>(tuple) /= storm::utility::one() - boost::get<0>(tuple); + for (auto stateIt = this->partition.begin() + block.getBeginIndex(), stateIte = this->partition.begin() + block.data().marker1(); stateIt != stateIte; ++stateIt) { + if (!this->comparator.isOne(getSilentProbability(*stateIt))) { + probabilitiesToCurrentSplitter[*stateIt] /= storm::utility::one() - getSilentProbability(*stateIt); } - } ); + } } template @@ -334,13 +334,11 @@ namespace storm { // 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 stateLabels(block.getNumberOfStates(), storm::storage::BitVector(nonSilentBlockIndices.size() - 1)); - std::cout << "creating " << block.getNumberOfStates() << " labels " << std::endl; std::vector stateStack; stateStack.reserve(block.getNumberOfStates()); for (uint_fast64_t stateClassIndex = 0; stateClassIndex < nonSilentBlockIndices.size() - 1; ++stateClassIndex) { for (auto stateIt = this->partition.begin() + nonSilentBlockIndices[stateClassIndex], stateIte = this->partition.begin() + nonSilentBlockIndices[stateClassIndex + 1]; stateIt != stateIte; ++stateIt) { - std::cout << "moving backward from state " << *stateIt << std::endl; stateStack.push_back(*stateIt); stateLabels[this->partition.getPosition(*stateIt) - block.getBeginIndex()].set(stateClassIndex); @@ -351,20 +349,13 @@ namespace storm { for (auto const& predecessorEntry : this->backwardTransitions.getRow(currentState)) { storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn(); - std::cout << " found pred " << predecessor << " with value " << predecessorEntry.getValue() << std::endl; if (this->comparator.isZero(predecessorEntry.getValue())) { continue; } - + // Only if the state is in the same block, is a silent state and it has not yet been // labeled with the current label. - std::cout << "pred is at " << this->partition.getPosition(predecessor) << std::endl; - std::cout << "begin of block " << block.getBeginIndex() << std::endl; - std::cout << "accessing index " << (this->partition.getPosition(predecessor) - block.getBeginIndex()) << " of " << stateLabels.size() << std::endl; - std::cout << predecessor << " is in the same block as " << block.getId() << " ? " << (this->partition.getBlock(predecessor) == block) << std::endl; -// std::cout << isSilent(predecessor) << ", " << stateLabels[this->partition.getPosition(predecessor) - block.getBeginIndex()].get(stateClassIndex) << std::endl; if (this->partition.getBlock(predecessor) == block && isSilent(predecessor) && !stateLabels[this->partition.getPosition(predecessor) - block.getBeginIndex()].get(stateClassIndex)) { - std::cout << "assigning label " << stateClassIndex << std::endl; stateStack.push_back(predecessor); stateLabels[this->partition.getPosition(predecessor) - block.getBeginIndex()].set(stateClassIndex); } @@ -378,38 +369,20 @@ namespace storm { template void DeterministicModelBisimulationDecomposition::refinePredecessorBlockOfSplitterWeak(bisimulation::Block& block, std::deque*>& splitterQueue) { + // First, we need to turn the one-step probabilities to go to the splitter to the conditional probabilities + // for all non-silent states. computeConditionalProbabilitiesForNonSilentStates(block); - // First, we need to compute a labeling of the states that expresses which of the non-silent blocks they can reach. - std::vector nonSilentBlockIndices = computeNonSilentBlocks(block); - std::cout << "indices: " << std::endl; - for (auto el : nonSilentBlockIndices) { - std::cout << el << std::endl; - } - - for (int ind = 0; ind < nonSilentBlockIndices.size() - 1; ++ind) { - for (int inner = nonSilentBlockIndices[ind]; inner < nonSilentBlockIndices[ind + 1]; ++inner) { - std::cout << this->partition.getState(inner) << " is in class " << ind << std::endl; - } - } + // Then, we need to compute a labeling of the states that expresses which of the non-silent blocks they can reach. + std::vector nonSilentBlockIndices = computeNonSilentBlocks(block); std::vector weakStateLabels = computeWeakStateLabelingBasedOnNonSilentBlocks(block, nonSilentBlockIndices); - std::cout << "labels: " << std::endl; - for (auto el : weakStateLabels) { - std::cout << el << std::endl; - } - // Then split the block according to this labeling. // CAUTION: that this assumes that the positions of the states in the partition are not update until after // the sorting is over. Otherwise, this interferes with the data used in the sorting process. storm::storage::sparse::state_type originalBlockIndex = block.getBeginIndex(); - auto result = this->partition.splitBlock(block, + auto split = this->partition.splitBlock(block, [&weakStateLabels,&block,originalBlockIndex,this] (storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { - std::cout << "comparing states " << state1 << " and " << state2 << std::endl; - std::cout << (this->partition.getPosition(state1) - originalBlockIndex) << " and " << (this->partition.getPosition(state2) - originalBlockIndex) << std::endl; - std::cout << "size: " << weakStateLabels.size() << std::endl; - std::cout << "begin is " << block.getBeginIndex() << std::endl; - std::cout << "orig begin " << originalBlockIndex << std::endl; return weakStateLabels[this->partition.getPosition(state1) - originalBlockIndex] < weakStateLabels[this->partition.getPosition(state2) - originalBlockIndex]; }, [this, &splitterQueue] (bisimulation::Block& block) { @@ -420,33 +393,41 @@ namespace storm { splitterQueue.emplace_back(&block); }); - // If the block was split, we also update the silent probabilities of the - if (result) { + // If the block was split, we also update the silent probabilities. + if (split) { updateSilentProbabilitiesBasedOnTransitions(block); - // Insert the new block as a splitter. - block.data().setSplitter(); - splitterQueue.emplace_back(&block); - } else { - block.resetMarkers(); + if (!block.data().splitter()) { + // Insert the new block as a splitter. + block.data().setSplitter(); + splitterQueue.emplace_back(&block); + } } } template void DeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitterWeak(bisimulation::Block& splitter, std::list*> const& predecessorBlocks, std::deque*>& splitterQueue) { for (auto block : predecessorBlocks) { - std::cout << "found predecessor block " << block->getId() << std::endl; if (*block != splitter) { refinePredecessorBlockOfSplitterWeak(*block, splitterQueue); } else { // If the block to split is the splitter itself, we must not do any splitting here. - block->resetMarkers(); } + block->resetMarkers(); block->data().setNeedsRefinement(false); } } + template + void DeterministicModelBisimulationDecomposition::insertIntoPredecessorList(bisimulation::Block& predecessorBlock, std::list*>& predecessorBlocks) { + // 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(); + } + } + template void DeterministicModelBisimulationDecomposition::refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) { // The outline of the refinement is as follows. @@ -464,11 +445,6 @@ namespace storm { // // 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. - - std::cout << "current partition is" << std::endl; - this->partition.print(); - std::cout << "refining using splitter " << splitter.getId() << std::endl; - std::list*> predecessorBlocks; storm::storage::sparse::state_type currentPosition = splitter.getBeginIndex(); bool splitterIsPredecessorBlock = false; @@ -500,11 +476,7 @@ namespace storm { 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(); - } + insertIntoPredecessorList(predecessorBlock, predecessorBlocks); } } @@ -516,7 +488,7 @@ namespace storm { // 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); + exploreRemainingStatesOfSplitter(splitter, predecessorBlocks); } // Finally, we split the block based on the precomputed probabilities and the chosen bisimulation type. diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h index 4f0c5dd76..b49390659 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h @@ -88,7 +88,7 @@ namespace storm { void increaseProbabilityToSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block const& predecessorBlock, ValueType const& value); // Explores the remaining predecessors of the splitter. - void exploreRemainingStatesOfSplitter(bisimulation::Block& splitter); + void exploreRemainingStatesOfSplitter(bisimulation::Block& splitter, std::list*>& predecessorBlocks); // Updates the silent probabilities of the states in the block based on the probabilities of going to the splitter. void updateSilentProbabilitiesBasedOnProbabilitiesToSplitter(bisimulation::Block& block); @@ -113,6 +113,9 @@ namespace storm { // Computes a labeling for all states of the block that identifies in which block they need to end up. std::vector computeWeakStateLabelingBasedOnNonSilentBlocks(bisimulation::Block const& block, std::vector const& nonSilentBlockIndices); + // Inserts the block into the list of predecessors if it is not already contained. + void insertIntoPredecessorList(bisimulation::Block& predecessorBlock, std::list*>& predecessorBlocks); + // 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 probabilitiesToCurrentSplitter; diff --git a/src/storage/bisimulation/Partition.cpp b/src/storage/bisimulation/Partition.cpp index ff432fa11..d72fe80cc 100644 --- a/src/storage/bisimulation/Partition.cpp +++ b/src/storage/bisimulation/Partition.cpp @@ -211,8 +211,6 @@ namespace storm { std::pair>>::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 " << block.getId() << " at pos " << position << 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()) { @@ -242,21 +240,13 @@ namespace storm { auto originalBegin = block.getBeginIndex(); auto originalEnd = block.getEndIndex(); - std::cout << "sorted block:" << std::endl; - for (auto stateIt = this->begin(block), stateIte = this->end(block); stateIt != stateIte; ++stateIt) { - std::cout << *stateIt << std::endl; - } - auto it = this->states.cbegin() + block.getBeginIndex(); auto ite = this->states.cbegin() + block.getEndIndex(); - std::cout << "splitting between " << block.getBeginIndex() << " and " << block.getEndIndex() << std::endl; bool wasSplit = false; std::vector::const_iterator upperBound; do { - std::cout << "it (" << *it << ") less than ite-1 (" << *(ite-1) << "? " << less(*it, *(ite - 1)) << std::endl; upperBound = std::upper_bound(it, ite, *it, less); - std::cout << "upper bound is " << std::distance(this->states.cbegin(), upperBound); if (upperBound != ite) { wasSplit = true; auto result = this->splitBlock(block, std::distance(this->states.cbegin(), upperBound)); From 51bd689c96eaab8f2a00fd21edac55f32957a73c Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 3 Nov 2015 17:41:01 +0100 Subject: [PATCH 12/24] fixed a bug in the reward model Former-commit-id: 5302a85d6f183031c3663cc49ee1de344d0750b4 --- src/models/sparse/StandardRewardModel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/models/sparse/StandardRewardModel.cpp b/src/models/sparse/StandardRewardModel.cpp index 5788dae10..b00b6869c 100644 --- a/src/models/sparse/StandardRewardModel.cpp +++ b/src/models/sparse/StandardRewardModel.cpp @@ -111,6 +111,7 @@ namespace storm { if (this->hasTransitionRewards()) { if (this->hasStateActionRewards()) { storm::utility::vector::addVectors(this->getStateActionRewardVector(), transitionMatrix.getPointwiseProductRowSumVector(this->getTransitionRewardMatrix()), this->getStateActionRewardVector()); + this->optionalStateActionRewardVector = boost::none; } else { this->optionalStateActionRewardVector = transitionMatrix.getPointwiseProductRowSumVector(this->getTransitionRewardMatrix()); } @@ -121,8 +122,9 @@ namespace storm { STORM_LOG_THROW(this->getStateRewardVector().size() == this->getStateActionRewardVector().size(), storm::exceptions::InvalidOperationException, "The reduction to state rewards is only possible of both the state and the state-action rewards have the same dimension."); storm::utility::vector::addVectors(this->getStateActionRewardVector(), this->getStateRewardVector(), this->getStateRewardVector()); } else { - this->optionalStateRewardVector = std::move(this->optionalStateRewardVector); + this->optionalStateRewardVector = std::move(this->optionalStateActionRewardVector); } + this->optionalStateActionRewardVector = boost::none; } } From 9b7d4ec57b9ecfee21f1a65fbcea6cde66a4d985 Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 3 Nov 2015 19:45:41 +0100 Subject: [PATCH 13/24] made rational functions use cln again Former-commit-id: a4b14f92712ab702255f77df71e4355e00f257fe --- src/adapters/CarlAdapter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapters/CarlAdapter.h b/src/adapters/CarlAdapter.h index 99e01e575..2ed172cde 100644 --- a/src/adapters/CarlAdapter.h +++ b/src/adapters/CarlAdapter.h @@ -42,7 +42,7 @@ namespace carl { } namespace storm { - typedef mpq_class RationalNumber; + typedef cln::cl_RA RationalNumber; typedef carl::Variable Variable; typedef carl::MultivariatePolynomial RawPolynomial; typedef carl::FactorizedPolynomial Polynomial; From 7833025829402ad4e65822a7f38b4627e23d60c2 Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 3 Nov 2015 20:11:23 +0100 Subject: [PATCH 14/24] reenabled all bisimulation tests Former-commit-id: 24e8629270ddc3e00de1e4ed4cb4c94b23664246 --- ...sticModelBisimulationDecompositionTest.cpp | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp b/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp index 3bb1b97e7..a706475bd 100644 --- a/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp +++ b/test/functional/storage/DeterministicModelBisimulationDecompositionTest.cpp @@ -35,16 +35,16 @@ TEST(DeterministicModelBisimulationDecomposition, Die) { EXPECT_EQ(5ul, result->getNumberOfStates()); EXPECT_EQ(8ul, result->getNumberOfTransitions()); -// options.bounded = false; -// options.type = storm::storage::BisimulationType::Weak; -// -// storm::storage::DeterministicModelBisimulationDecomposition> 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()); + options.bounded = false; + options.type = storm::storage::BisimulationType::Weak; + + storm::storage::DeterministicModelBisimulationDecomposition> 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("one"); auto eventuallyFormula = std::make_shared(labelFormula); @@ -93,17 +93,17 @@ TEST(DeterministicModelBisimulationDecomposition, Crowds) { EXPECT_EQ(65ul, result->getNumberOfStates()); EXPECT_EQ(105ul, result->getNumberOfTransitions()); -// options.bounded = false; -// options.type = storm::storage::BisimulationType::Weak; -// -// storm::storage::DeterministicModelBisimulationDecomposition> 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()); -// + options.bounded = false; + options.type = storm::storage::BisimulationType::Weak; + + storm::storage::DeterministicModelBisimulationDecomposition> 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("observe0Greater1"); auto eventuallyFormula = std::make_shared(labelFormula); From 9bac056354369a3addfb603e8c8ec189e412efde Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 5 Nov 2015 13:32:39 +0100 Subject: [PATCH 15/24] enabled preconditioning for value iteration in gmm++-based MinMax equation solver Former-commit-id: 39c4efdb52e057384d620997d1f25027dcb59bce --- src/solver/GmmxxMinMaxLinearEquationSolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver/GmmxxMinMaxLinearEquationSolver.cpp b/src/solver/GmmxxMinMaxLinearEquationSolver.cpp index 86b8dfa23..e24019e79 100644 --- a/src/solver/GmmxxMinMaxLinearEquationSolver.cpp +++ b/src/solver/GmmxxMinMaxLinearEquationSolver.cpp @@ -124,7 +124,7 @@ namespace storm { storm::storage::SparseMatrix submatrix = this->A.selectRowsFromRowGroups(this->policy, true); submatrix.convertToEquationSystem(); - GmmxxLinearEquationSolver gmmLinearEquationSolver(submatrix, storm::solver::GmmxxLinearEquationSolver::SolutionMethod::Gmres, this->precision, this->maximalNumberOfIterations, storm::solver::GmmxxLinearEquationSolver::Preconditioner::None, this->relative, 50); + GmmxxLinearEquationSolver gmmLinearEquationSolver(submatrix, storm::solver::GmmxxLinearEquationSolver::SolutionMethod::Gmres, this->precision, this->maximalNumberOfIterations, storm::solver::GmmxxLinearEquationSolver::Preconditioner::Ilu, this->relative, 50); storm::utility::vector::selectVectorValues(subB, this->policy, rowGroupIndices, b); // Copy X since we will overwrite it From c2a0bd5ab0338ca0eb79b5c15e2dbfd829f78885 Mon Sep 17 00:00:00 2001 From: dehnert Date: Thu, 5 Nov 2015 17:22:41 +0100 Subject: [PATCH 16/24] initial outline of strong MDP bisimulation Former-commit-id: 06452543eab8d40c509e84eddc6dc90ce6356167 --- .../BisimulationDecomposition.cpp | 15 +- .../bisimulation/BisimulationDecomposition.h | 13 +- ...ministicModelBisimulationDecomposition.cpp | 4 +- ...erministicModelBisimulationDecomposition.h | 5 +- ...ministicModelBisimulationDecomposition.cpp | 230 ++++++++++++++++++ ...erministicModelBisimulationDecomposition.h | 82 +++++++ 6 files changed, 341 insertions(+), 8 deletions(-) create mode 100644 src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp create mode 100644 src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp index 178b5a965..86ac56378 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.cpp +++ b/src/storage/bisimulation/BisimulationDecomposition.cpp @@ -83,8 +83,10 @@ namespace storm { std::shared_ptr newFormula = formula.asSharedPointer(); if (formula.isProbabilityOperatorFormula()) { + optimalityType = formula.asProbabilityOperatorFormula().getOptimalityType(); newFormula = formula.asProbabilityOperatorFormula().getSubformula().asSharedPointer(); } else if (formula.isRewardOperatorFormula()) { + optimalityType = formula.asRewardOperatorFormula().getOptimalityType(); newFormula = formula.asRewardOperatorFormula().getSubformula().asSharedPointer(); } @@ -114,6 +116,8 @@ namespace storm { std::unique_ptr psiStatesCheckResult = checker.check(*rightSubformula); phiStates = phiStatesCheckResult->asExplicitQualitativeCheckResult().getTruthValuesVector(); psiStates = psiStatesCheckResult->asExplicitQualitativeCheckResult().getTruthValuesVector(); + } else { + optimalityType = boost::none; } } @@ -134,7 +138,16 @@ namespace storm { } template - BisimulationDecomposition::BisimulationDecomposition(ModelType const& model, Options const& options) : model(model), backwardTransitions(model.getBackwardTransitions()), options(options), partition(), comparator(), quotient(nullptr) { + BisimulationDecomposition::BisimulationDecomposition(ModelType const& model, Options const& options) : BisimulationDecomposition(model, model.getBackwardTransitions(), options) { + // Intentionally left empty. + } + + template + BisimulationDecomposition::BisimulationDecomposition(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, Options const& options) : model(model), backwardTransitions(backwardTransitions), options(options), partition(), comparator(), quotient(nullptr) { + STORM_LOG_THROW(!options.keepRewards || !model.hasRewardModel() || model.hasUniqueRewardModel(), storm::exceptions::IllegalFunctionCallException, "Bisimulation currently only supports models with at most one reward model."); + STORM_LOG_THROW(!options.keepRewards || !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.type != BisimulationType::Weak || !options.bounded, storm::exceptions::IllegalFunctionCallException, "Weak bisimulation cannot preserve bounded properties."); + // Fix the respected atomic propositions if they were not explicitly given. if (!this->options.respectedAtomicPropositions) { this->options.respectedAtomicPropositions = model.getStateLabeling().getLabels(); diff --git a/src/storage/bisimulation/BisimulationDecomposition.h b/src/storage/bisimulation/BisimulationDecomposition.h index 816dc7436..280737797 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.h +++ b/src/storage/bisimulation/BisimulationDecomposition.h @@ -74,6 +74,7 @@ namespace storm { bool measureDrivenInitialPartition; boost::optional phiStates; boost::optional psiStates; + boost::optional optimalityType; // An optional set of strings that indicate which of the atomic propositions of the model are to be // respected and which may be ignored. If not given, all atomic propositions of the model are respected. @@ -125,8 +126,7 @@ namespace storm { * Decomposes the given model into equivalance classes of a bisimulation. * * @param model The model to decompose. - * @param type The type of the bisimulation to compute. - * @param buildQuotient A flag specifying whether the quotient is to be build. + * @param options The options to use during for the decomposition. */ BisimulationDecomposition(ModelType const& model, Options const& options); @@ -144,6 +144,15 @@ namespace storm { void computeBisimulationDecomposition(); protected: + /*! + * Decomposes the given model into equivalance classes of a bisimulation. + * + * @param model The model to decompose. + * @param backwardTransition The backward transitions of the model. + * @param options The options to use during for the decomposition. + */ + BisimulationDecomposition(ModelType const& model, storm::storage::SparseMatrix const& backwardTransitions, Options const& options); + /*! * 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 diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp index 5805fe15d..e3867f2de 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp @@ -29,9 +29,7 @@ namespace storm { template DeterministicModelBisimulationDecomposition::DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options) : BisimulationDecomposition(model, options), probabilitiesToCurrentSplitter(model.getNumberOfStates(), storm::utility::zero()) { - STORM_LOG_THROW(!options.keepRewards || !model.hasRewardModel() || model.hasUniqueRewardModel(), storm::exceptions::IllegalFunctionCallException, "Bisimulation currently only supports models with at most one reward model."); - STORM_LOG_THROW(!options.keepRewards || !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.type != BisimulationType::Weak || !options.bounded, storm::exceptions::IllegalFunctionCallException, "Weak bisimulation cannot preserve bounded properties."); + // Intentionally left empty. } template diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h index b49390659..b9f2a2a5a 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h @@ -42,7 +42,8 @@ namespace storm { virtual void refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) override; private: - virtual void refinePredecessorBlocksOfSplitterStrong(std::list*> const& predecessorBlocks, std::deque*>& splitterQueue); + // Refines the predecessor blocks wrt. strong bisimulation. + void refinePredecessorBlocksOfSplitterStrong(std::list*> const& predecessorBlocks, std::deque*>& splitterQueue); /*! * Performs the necessary steps to compute a weak bisimulation on a DTMC. @@ -87,7 +88,7 @@ namespace storm { // Increases the probability of moving to the current splitter for the given state. void increaseProbabilityToSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block const& predecessorBlock, ValueType const& value); - // Explores the remaining predecessors of the splitter. + // Explores the remaining states of the splitter. void exploreRemainingStatesOfSplitter(bisimulation::Block& splitter, std::list*>& predecessorBlocks); // Updates the silent probabilities of the states in the block based on the probabilities of going to the splitter. diff --git a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp new file mode 100644 index 000000000..e519e1b2f --- /dev/null +++ b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp @@ -0,0 +1,230 @@ +#include "src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h" + +#include "src/models/sparse/Mdp.h" +#include "src/models/sparse/StandardRewardModel.h" + +#include "src/utility/graph.h" + +#include "src/exceptions/IllegalFunctionCallException.h" + +namespace storm { + namespace storage { + + using namespace bisimulation; + + template + NondeterministicModelBisimulationDecomposition::NondeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options) : BisimulationDecomposition(model, model.getTransitionMatrix().transpose(false), options), choiceToStateMapping(model.getNumberOfChoices()), probabilitiesToCurrentSplitter(model.getNumberOfChoices(), storm::utility::zero()) { + STORM_LOG_THROW(options.type == BisimulationType::Strong, storm::exceptions::IllegalFunctionCallException, "Weak bisimulation is currently not supported for nondeterministic models."); + this->createChoiceToStateMapping(); + } + + template + std::pair NondeterministicModelBisimulationDecomposition::getStatesWithProbability01() { + STORM_LOG_THROW(static_cast(this->options.optimalityType), storm::exceptions::IllegalFunctionCallException, "Can only compute states with probability 0/1 with an optimization direction (min/max)."); + if (this->options.optimalityType.get() == OptimizationDirection::Minimize) { + return storm::utility::graph::performProb01Min(this->model.getTransitionMatrix(), this->model.getTransitionMatrix().getRowGroupIndices(), this->model.getBackwardTransitions(), this->options.phiStates.get(), this->options.psiStates.get()); + } else { + return storm::utility::graph::performProb01Max(this->model.getTransitionMatrix(), this->model.getTransitionMatrix().getRowGroupIndices(), this->model.getBackwardTransitions(), this->options.phiStates.get(), this->options.psiStates.get()); + } + } + + template + void NondeterministicModelBisimulationDecomposition::createChoiceToStateMapping() { + std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); + for (storm::storage::sparse::state_type state = 0; state < this->model.getNumberOfStates(); ++state) { + for (uint_fast64_t choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) { + choiceToStateMapping[choice] = state; + } + } + } + + template + void NondeterministicModelBisimulationDecomposition::buildQuotient() { + STORM_LOG_ASSERT(false, "Not yet implemented"); + } + + template + bool NondeterministicModelBisimulationDecomposition::possiblyNeedsRefinement(bisimulation::Block const& predecessorBlock) const { + return predecessorBlock.getNumberOfStates() > 1 && !predecessorBlock.data().absorbing(); + } + + template + void NondeterministicModelBisimulationDecomposition::clearProbabilitiesToSplitter(storm::storage::sparse::state_type state) { + std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); + for (uint_fast64_t choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) { + probabilitiesToCurrentSplitter[choice] = storm::utility::zero(); + } + } + + template + void NondeterministicModelBisimulationDecomposition::increaseProbabilityToSplitter(storm::storage::sparse::state_type state, uint_fast64_t choice, bisimulation::Block const& predecessorBlock, ValueType const& value) { + storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(state); + + // If the position of the state is to the right of marker1, we have not seen it before. That means we need to + // clear the probability associated with all choices of the given state. + if (predecessorPosition >= predecessorBlock.data().marker1()) { + clearProbabilitiesToSplitter(state); + } + + // Now increase the probability of the given choice of the given state. + probabilitiesToCurrentSplitter[choice] += value; + } + + template + void NondeterministicModelBisimulationDecomposition::moveStateToMarker1(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock) { + this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker1())); + predecessorBlock.data().incrementMarker1(); + } + + template + void NondeterministicModelBisimulationDecomposition::moveStateToMarker2(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock) { + this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker2())); + predecessorBlock.data().incrementMarker2(); + } + + template + void NondeterministicModelBisimulationDecomposition::moveStateInSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter, uint_fast64_t& elementsToSkip) { + storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor); + + // 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 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 + void NondeterministicModelBisimulationDecomposition::insertIntoPredecessorList(bisimulation::Block& predecessorBlock, std::list*>& predecessorBlocks) { + // 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(); + } + } + + template + void NondeterministicModelBisimulationDecomposition::exploreRemainingStatesOfSplitter(bisimulation::Block& splitter, std::list*>& predecessorBlocks) { + 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 choice = predecessorEntry.getColumn(); + storm::storage::sparse::state_type predecessor = choiceToStateMapping[choice]; + Block& 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, choice, predecessorBlock, predecessorEntry.getValue()); + + // 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); + } + + insertIntoPredecessorList(predecessorBlock, predecessorBlocks); + } + } + + // Finally, we can reset the second marker. + splitter.data().setMarker2(splitter.getBeginIndex()); + } + + template + void NondeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitter(std::list*> const& predecessorBlocks, std::deque*>& splitterQueue) { + // TODO + } + + template + void NondeterministicModelBisimulationDecomposition::refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) { + // The outline of the refinement is as follows. + // + // 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. + // + // 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. + std::list*> predecessorBlocks; + storm::storage::sparse::state_type currentPosition = splitter.getBeginIndex(); + 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 choice = predecessorEntry.getColumn(); + storm::storage::sparse::state_type predecessor = choiceToStateMapping[choice]; + storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor); + Block& 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, choice, predecessorBlock, predecessorEntry.getValue()); + + // 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); + } + + insertIntoPredecessorList(predecessorBlock, predecessorBlocks); + } + } + + // If, as a consequence of shifting states, we need to skip some elements, do so now. + splitterIt += elementsToSkip; + currentPosition += elementsToSkip; + } + + // 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, predecessorBlocks); + } + + // Finally, we split the block based on the precomputed probabilities and the chosen bisimulation type. + refinePredecessorBlocksOfSplitter(predecessorBlocks, splitterQueue); + } + + template class NondeterministicModelBisimulationDecomposition>; + + } +} diff --git a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h new file mode 100644 index 000000000..eb315dce6 --- /dev/null +++ b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h @@ -0,0 +1,82 @@ +#ifndef STORM_STORAGE_BISIMULATION_NONDETERMINISTICMODELBISIMULATIONDECOMPOSITION_H_ +#define STORM_STORAGE_BISIMULATION_NONDETERMINISTICMODELBISIMULATIONDECOMPOSITION_H_ + +#include "src/storage/bisimulation/BisimulationDecomposition.h" +#include "src/storage/bisimulation/DeterministicBlockData.h" + +namespace storm { + namespace utility { + template class ConstantsComparator; + } + + namespace storage { + + /*! + * This class represents the decomposition of a nondeterministic model into its bisimulation quotient. + */ + template + class NondeterministicModelBisimulationDecomposition : public BisimulationDecomposition { + public: + typedef bisimulation::DeterministicBlockData BlockDataType; + typedef typename ModelType::ValueType ValueType; + typedef typename ModelType::RewardModelType RewardModelType; + + /*! + * Computes the bisimulation relation for the given model. Which kind of bisimulation is computed, is + * customizable via the given options. + * + * @param model The model to decompose. + * @param options The options that customize the computed bisimulation. + */ + NondeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options = typename BisimulationDecomposition::Options()); + + protected: + virtual std::pair getStatesWithProbability01() override; + + virtual void buildQuotient() override; + + virtual void refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) override; + + private: + // Creates the mapping from the choice indices to the states. + void createChoiceToStateMapping(); + + // Retrieves whether the given predecessor of the splitters possibly needs refinement. + bool possiblyNeedsRefinement(bisimulation::Block const& predecessorBlock) const; + + // Increases the probability of moving to the current splitter for the given choice of the given state. + void increaseProbabilityToSplitter(storm::storage::sparse::state_type state, uint_fast64_t choice, bisimulation::Block const& predecessorBlock, ValueType const& value); + + // Clears the probabilities of all choices of the given state. + void clearProbabilitiesToSplitter(storm::storage::sparse::state_type state); + + // 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& 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& predecessorBlock); + + // 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& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter, uint_fast64_t& elementsToSkip); + + // Inserts the block into the list of predecessors if it is not already contained. + void insertIntoPredecessorList(bisimulation::Block& predecessorBlock, std::list*>& predecessorBlocks); + + // Explores the remaining states of the splitter. + void exploreRemainingStatesOfSplitter(bisimulation::Block& splitter, std::list*>& predecessorBlocks); + + // Refines the predecessor blocks wrt. strong bisimulation. + void refinePredecessorBlocksOfSplitter(std::list*> const& predecessorBlocks, std::deque*>& splitterQueue); + + // A mapping from choice indices to the state state that has this choice. + std::vector choiceToStateMapping; + + // A vector that holds the probabilities for all nondeterministic choices of all states of going into the + // splitter. This is used by the method that refines a block based on probabilities. + std::vector probabilitiesToCurrentSplitter; + + }; + } +} + +#endif /* STORM_STORAGE_BISIMULATION_NONDETERMINISTICMODELBISIMULATIONDECOMPOSITION_H_ */ \ No newline at end of file From 7156a63b0fa818a4427987b0726cbed21f514c3e Mon Sep 17 00:00:00 2001 From: dehnert Date: Fri, 6 Nov 2015 18:58:26 +0100 Subject: [PATCH 17/24] tried different approach for bisim for MDPs Former-commit-id: 92d56a462076debaf95d974c2f65e933d9652361 --- src/builder/DdPrismModelBuilder.cpp | 2 +- src/builder/ExplicitPrismModelBuilder.cpp | 2 +- src/storage/Distribution.cpp | 67 +++- src/storage/Distribution.h | 43 ++- .../BisimulationDecomposition.cpp | 12 + .../bisimulation/BisimulationDecomposition.h | 5 + ...ministicModelBisimulationDecomposition.cpp | 2 +- ...ministicModelBisimulationDecomposition.cpp | 296 +++++++++--------- ...erministicModelBisimulationDecomposition.h | 45 ++- src/utility/ConstantsComparator.cpp | 15 +- src/utility/ConstantsComparator.h | 6 + src/utility/storm.h | 30 +- 12 files changed, 330 insertions(+), 195 deletions(-) diff --git a/src/builder/DdPrismModelBuilder.cpp b/src/builder/DdPrismModelBuilder.cpp index 4f6ab3871..0a20bf38b 100644 --- a/src/builder/DdPrismModelBuilder.cpp +++ b/src/builder/DdPrismModelBuilder.cpp @@ -1020,7 +1020,7 @@ namespace storm { preparedProgram = preparedProgram.substituteConstants(); - STORM_LOG_DEBUG("Building representation of program :" << std::endl << preparedProgram << std::endl); + STORM_LOG_DEBUG("Building representation of program:" << std::endl << preparedProgram << std::endl); // Start by initializing the structure used for storing all information needed during the model generation. // In particular, this creates the meta variables used to encode the model. diff --git a/src/builder/ExplicitPrismModelBuilder.cpp b/src/builder/ExplicitPrismModelBuilder.cpp index 5b62364b4..4f6117c9c 100644 --- a/src/builder/ExplicitPrismModelBuilder.cpp +++ b/src/builder/ExplicitPrismModelBuilder.cpp @@ -295,7 +295,7 @@ namespace storm { // Now that the program is fixed, we we need to substitute all constants with their concrete value. preparedProgram = preparedProgram.substituteConstants(); - STORM_LOG_DEBUG("Building representation of program :" << std::endl << preparedProgram << std::endl); + STORM_LOG_DEBUG("Building representation of program:" << std::endl << preparedProgram << std::endl); // Select the appropriate reward models (after the constants have been substituted). std::vector> selectedRewardModels; diff --git a/src/storage/Distribution.cpp b/src/storage/Distribution.cpp index ce42b6ab4..5dd19e50c 100644 --- a/src/storage/Distribution.cpp +++ b/src/storage/Distribution.cpp @@ -3,8 +3,14 @@ #include #include +#include "src/utility/macros.h" +#include "src/utility/constants.h" +#include "src/utility/ConstantsComparator.h" + #include "src/settings/SettingsManager.h" +#include "src/adapters/CarlAdapter.h" + namespace storm { namespace storage { @@ -14,7 +20,7 @@ namespace storm { } template - bool Distribution::operator==(Distribution const& other) const { + bool Distribution::equals(Distribution const& other, storm::utility::ConstantsComparator const& comparator) const { // We need to check equality by ourselves, because we need to account for epsilon differences. if (this->distribution.size() != other.distribution.size() || this->getHash() != other.getHash()) { return false; @@ -28,7 +34,7 @@ namespace storm { if (first1->first != first2->first) { return false; } - if (std::abs(first1->second - first2->second) > 1e-6) { + if (!comparator.isEqual(first1->second, first2->second)) { return false; } } @@ -38,10 +44,29 @@ namespace storm { template void Distribution::addProbability(storm::storage::sparse::state_type const& state, ValueType const& probability) { - if (this->distribution.find(state) == this->distribution.end()) { + auto it = this->distribution.find(state); + if (it == this->distribution.end()) { this->hash += static_cast(state); + this->distribution.emplace_hint(it, state, probability); + } else { + it->second += probability; + } + } + + template + void Distribution::removeProbability(storm::storage::sparse::state_type const& state, ValueType const& probability, storm::utility::ConstantsComparator const& comparator) { + auto it = this->distribution.find(state); + STORM_LOG_ASSERT(it != this->distribution.end(), "Cannot remove probability, because the state is not in the support of the distribution."); + it->second -= probability; + if (comparator.isZero(it->second)) { + this->distribution.erase(it); } - this->distribution[state] += probability; + } + + template + void Distribution::shiftProbability(storm::storage::sparse::state_type const& fromState, storm::storage::sparse::state_type const& toState, ValueType const& probability, storm::utility::ConstantsComparator const& comparator) { + removeProbability(fromState, probability, comparator); + addProbability(toState, probability); } template @@ -68,7 +93,7 @@ namespace storm { void Distribution::scale(storm::storage::sparse::state_type const& state) { auto probabilityIterator = this->distribution.find(state); if (probabilityIterator != this->distribution.end()) { - ValueType scaleValue = 1 / probabilityIterator->second; + ValueType scaleValue = storm::utility::one() / probabilityIterator->second; this->distribution.erase(probabilityIterator); for (auto& entry : this->distribution) { @@ -82,6 +107,11 @@ namespace storm { return this->hash ^ (this->distribution.size() << 8); } + template + std::size_t Distribution::size() const { + return this->distribution.size(); + } + template std::ostream& operator<<(std::ostream& out, Distribution const& distribution) { out << "{"; @@ -93,7 +123,34 @@ namespace storm { return out; } + template + bool Distribution::less(Distribution const& other, storm::utility::ConstantsComparator const& comparator) const { + if (this->size() != other.size()) { + return this->size() < other.size(); + } + auto firstIt = this->begin(); + auto firstIte = this->end(); + auto secondIt = other.begin(); + for (; firstIt != firstIte; ++firstIt, ++secondIt) { + // If the two blocks already differ, we can decide which distribution is smaller. + if (firstIt->first != secondIt->first) { + return firstIt->first < secondIt->first; + } + + // If the blocks are the same, but the probability differs, we can also decide which distribution is smaller. + if (!comparator.isEqual(firstIt->second, secondIt->second)) { + return comparator.isLess(firstIt->second, secondIt->second); + } + } + return false; + } + template class Distribution; template std::ostream& operator<<(std::ostream& out, Distribution const& distribution); + +#ifdef STORM_HAVE_CARL + template class Distribution; + template std::ostream& operator<<(std::ostream& out, Distribution const& distribution); +#endif } } diff --git a/src/storage/Distribution.h b/src/storage/Distribution.h index 010cf43bc..93330c009 100644 --- a/src/storage/Distribution.h +++ b/src/storage/Distribution.h @@ -8,6 +8,11 @@ #include "src/storage/sparse/StateType.h" namespace storm { + namespace utility { + template + class ConstantsComparator; + } + namespace storage { template @@ -28,7 +33,7 @@ namespace storm { * @param other The distribution with which the current distribution is to be compared. * @return True iff the two distributions are equal. */ - bool operator==(Distribution const& other) const; + bool equals(Distribution const& other, storm::utility::ConstantsComparator const& comparator = storm::utility::ConstantsComparator()) const; /*! * Assigns the given state the given probability under this distribution. @@ -38,13 +43,34 @@ namespace storm { */ void addProbability(storm::storage::sparse::state_type const& state, ValueType const& probability); + /*! + * Removes the given probability mass of going to the given state. + * + * @param state The state for which to remove the probability. + * @param probability The probability to remove. + * @param comparator A comparator that is used to determine if the remaining probability is zero. If so, the + * entry is removed. + */ + void removeProbability(storm::storage::sparse::state_type const& state, ValueType const& probability, storm::utility::ConstantsComparator const& comparator = storm::utility::ConstantsComparator()); + + /*! + * Removes the probability mass from one state and adds it to another. + * + * @param fromState The state from which to take the probability mass. + * @param toState The state from which to which to add the probability mass. + * @param probability The probability mass to shift. + * @param comparator A comparator that is used to determine if the remaining probability is zero. If so, the + * entry is removed. + */ + void shiftProbability(storm::storage::sparse::state_type const& fromState, storm::storage::sparse::state_type const& toState, ValueType const& probability, storm::utility::ConstantsComparator const& comparator = storm::utility::ConstantsComparator()); + /*! * Retrieves an iterator to the elements in this distribution. * * @return The iterator to the elements in this distribution. */ iterator begin(); - + /*! * Retrieves an iterator to the elements in this distribution. * @@ -58,7 +84,7 @@ namespace storm { * @return The iterator past the elements in this distribution. */ iterator end(); - + /*! * Retrieves an iterator past the elements in this distribution. * @@ -82,16 +108,23 @@ namespace storm { */ std::size_t getHash() const; + /*! + * Retrieves the size of the distribution, i.e. the size of the support set. + */ + std::size_t size() const; + + bool less(Distribution const& other, storm::utility::ConstantsComparator const& comparator) const; + private: // A list of states and the probabilities that are assigned to them. container_type distribution; - // A hash value that is maintained to allow for quicker equality comparison between distribution.s + // A hash value that is maintained to allow for quicker equality comparison between distributions. std::size_t hash; }; template - std::ostream& operator<<(std::ostream& out, Distribution const& distribution); + std::ostream& operator<<(std::ostream& out, Distribution const& distribution); } } diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp index 86ac56378..0dc8369fa 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.cpp +++ b/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/Mdp.h" #include "src/models/sparse/StandardRewardModel.h" #include "src/storage/bisimulation/DeterministicBlockData.h" @@ -167,8 +168,12 @@ namespace storm { } else { this->initializeLabelBasedPartition(); } + std::cout << "initial partition is " << std::endl; + this->partition.print(); std::chrono::high_resolution_clock::duration initialPartitionTime = std::chrono::high_resolution_clock::now() - initialPartitionStart; + this->initialize(); + std::chrono::high_resolution_clock::time_point refinementStart = std::chrono::high_resolution_clock::now(); this->performPartitionRefinement(); std::chrono::high_resolution_clock::duration refinementTime = std::chrono::high_resolution_clock::now() - refinementStart; @@ -274,6 +279,11 @@ namespace storm { } } + template + void BisimulationDecomposition::initialize() { + // Intentionally left empty. + } + template void BisimulationDecomposition::extractDecompositionBlocks() { // Now move the states from the internal partition into their final place in the decomposition. We do so in @@ -290,10 +300,12 @@ namespace storm { template class BisimulationDecomposition, bisimulation::DeterministicBlockData>; template class BisimulationDecomposition, bisimulation::DeterministicBlockData>; + template class BisimulationDecomposition, bisimulation::DeterministicBlockData>; #ifdef STORM_HAVE_CARL template class BisimulationDecomposition, bisimulation::DeterministicBlockData>; template class BisimulationDecomposition, bisimulation::DeterministicBlockData>; + template class BisimulationDecomposition, bisimulation::DeterministicBlockData>; #endif } } diff --git a/src/storage/bisimulation/BisimulationDecomposition.h b/src/storage/bisimulation/BisimulationDecomposition.h index 280737797..a7bd575b2 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.h +++ b/src/storage/bisimulation/BisimulationDecomposition.h @@ -185,6 +185,11 @@ namespace storm { */ virtual void initializeMeasureDrivenPartition(); + /*! + * A function that can initialize auxiliary data structures. It is called after initializing the initial partition. + */ + virtual void initialize(); + /*! * Computes the set of states with probability 0/1 for satisfying phi until psi. This is used for the measure * driven initial partition. diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp index e3867f2de..c9672cd47 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp @@ -172,7 +172,7 @@ namespace storm { split |= this->partition.splitBlock(*blockToRefineProbabilistically, [this] (storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { - return getProbabilityToSplitter(state1) < getProbabilityToSplitter(state2); + return this->comparator.isLess(getProbabilityToSplitter(state1), getProbabilityToSplitter(state2)); }, [&splitterQueue] (Block& block) { splitterQueue.emplace_back(&block); block.data().setSplitter(); diff --git a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp index e519e1b2f..1b16cd6b4 100644 --- a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp @@ -5,19 +5,21 @@ #include "src/utility/graph.h" +#include "src/utility/macros.h" #include "src/exceptions/IllegalFunctionCallException.h" +#include "src/adapters/CarlAdapter.h" + namespace storm { namespace storage { - + using namespace bisimulation; template - NondeterministicModelBisimulationDecomposition::NondeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options) : BisimulationDecomposition(model, model.getTransitionMatrix().transpose(false), options), choiceToStateMapping(model.getNumberOfChoices()), probabilitiesToCurrentSplitter(model.getNumberOfChoices(), storm::utility::zero()) { + NondeterministicModelBisimulationDecomposition::NondeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition::Options const& options) : BisimulationDecomposition(model, model.getTransitionMatrix().transpose(false), options), choiceToStateMapping(model.getNumberOfChoices()), quotientDistributions(model.getNumberOfChoices()), orderedQuotientDistributions(model.getNumberOfChoices()) { STORM_LOG_THROW(options.type == BisimulationType::Strong, storm::exceptions::IllegalFunctionCallException, "Weak bisimulation is currently not supported for nondeterministic models."); - this->createChoiceToStateMapping(); } - + template std::pair NondeterministicModelBisimulationDecomposition::getStatesWithProbability01() { STORM_LOG_THROW(static_cast(this->options.optimalityType), storm::exceptions::IllegalFunctionCallException, "Can only compute states with probability 0/1 with an optimization direction (min/max)."); @@ -27,7 +29,13 @@ namespace storm { return storm::utility::graph::performProb01Max(this->model.getTransitionMatrix(), this->model.getTransitionMatrix().getRowGroupIndices(), this->model.getBackwardTransitions(), this->options.phiStates.get(), this->options.psiStates.get()); } } - + + template + void NondeterministicModelBisimulationDecomposition::initialize() { + this->createChoiceToStateMapping(); + this->initializeQuotientDistributions(); + } + template void NondeterministicModelBisimulationDecomposition::createChoiceToStateMapping() { std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); @@ -39,192 +47,172 @@ namespace storm { } template - void NondeterministicModelBisimulationDecomposition::buildQuotient() { - STORM_LOG_ASSERT(false, "Not yet implemented"); - } - - template - bool NondeterministicModelBisimulationDecomposition::possiblyNeedsRefinement(bisimulation::Block const& predecessorBlock) const { - return predecessorBlock.getNumberOfStates() > 1 && !predecessorBlock.data().absorbing(); - } - - template - void NondeterministicModelBisimulationDecomposition::clearProbabilitiesToSplitter(storm::storage::sparse::state_type state) { + void NondeterministicModelBisimulationDecomposition::initializeQuotientDistributions() { std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); - for (uint_fast64_t choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) { - probabilitiesToCurrentSplitter[choice] = storm::utility::zero(); + for (auto choice = 0; choice < nondeterministicChoiceIndices.back(); ++choice) { + for (auto entry : this->model.getTransitionMatrix().getRow(choice)) { + if (!this->comparator.isZero(entry.getValue())) { + this->quotientDistributions[choice].addProbability(this->partition.getBlock(entry.getColumn()).getId(), entry.getValue()); + } + } + orderedQuotientDistributions[choice] = &this->quotientDistributions[choice]; } - } - - template - void NondeterministicModelBisimulationDecomposition::increaseProbabilityToSplitter(storm::storage::sparse::state_type state, uint_fast64_t choice, bisimulation::Block const& predecessorBlock, ValueType const& value) { - storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(state); - // If the position of the state is to the right of marker1, we have not seen it before. That means we need to - // clear the probability associated with all choices of the given state. - if (predecessorPosition >= predecessorBlock.data().marker1()) { - clearProbabilitiesToSplitter(state); + for (auto state = 0; state < this->model.getNumberOfStates(); ++state) { + updateOrderedQuotientDistributions(state); } - - // Now increase the probability of the given choice of the given state. - probabilitiesToCurrentSplitter[choice] += value; - } - - template - void NondeterministicModelBisimulationDecomposition::moveStateToMarker1(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock) { - this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker1())); - predecessorBlock.data().incrementMarker1(); } - template - void NondeterministicModelBisimulationDecomposition::moveStateToMarker2(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock) { - this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker2())); - predecessorBlock.data().incrementMarker2(); + template + void NondeterministicModelBisimulationDecomposition::updateOrderedQuotientDistributions(storm::storage::sparse::state_type state) { + std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); + std::sort(this->orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state], this->orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state + 1], + [this] (storm::storage::Distribution const* dist1, storm::storage::Distribution const* dist2) { + return dist1->less(*dist2, this->comparator); + }); } - template - void NondeterministicModelBisimulationDecomposition::moveStateInSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter, uint_fast64_t& elementsToSkip) { - storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor); - - // 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 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 + void NondeterministicModelBisimulationDecomposition::buildQuotient() { + std::cout << "Found " << this->partition.size() << " blocks" << std::endl; + this->partition.print(); + STORM_LOG_ASSERT(false, "Not yet implemented"); } template - void NondeterministicModelBisimulationDecomposition::insertIntoPredecessorList(bisimulation::Block& predecessorBlock, std::list*>& predecessorBlocks) { - // 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(); - } + bool NondeterministicModelBisimulationDecomposition::possiblyNeedsRefinement(bisimulation::Block const& block) const { + return block.getNumberOfStates() > 1 && !block.data().absorbing(); } - template - void NondeterministicModelBisimulationDecomposition::exploreRemainingStatesOfSplitter(bisimulation::Block& splitter, std::list*>& predecessorBlocks) { - 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 choice = predecessorEntry.getColumn(); - storm::storage::sparse::state_type predecessor = choiceToStateMapping[choice]; - Block& predecessorBlock = this->partition.getBlock(predecessor); - - // If the block does not need to be refined, we skip it. - if (!possiblyNeedsRefinement(predecessorBlock)) { + template + void NondeterministicModelBisimulationDecomposition::updateQuotientDistributionsOfPredecessors(Block const& newBlock, Block const& oldBlock, std::deque*>& splitterQueue) { + uint_fast64_t lastState = 0; + bool lastStateInitialized = false; + + for (auto stateIt = this->partition.begin(newBlock), stateIte = this->partition.end(newBlock); stateIt != stateIte; ++stateIt) { + for (auto predecessorEntry : this->backwardTransitions.getRow(*stateIt)) { + if (this->comparator.isZero(predecessorEntry.getValue())) { continue; } - // We keep track of the probability of the predecessor moving to the splitter. - increaseProbabilityToSplitter(predecessor, choice, predecessorBlock, predecessorEntry.getValue()); + storm::storage::sparse::state_type predecessorChoice = predecessorEntry.getColumn(); + storm::storage::sparse::state_type predecessorState = choiceToStateMapping[predecessorChoice]; + Block& predecessorBlock = this->partition.getBlock(predecessorState); + + // If the predecessor block is not marked as to-refined, we do so now. + if (!predecessorBlock.data().splitter()) { + predecessorBlock.data().setSplitter(); + splitterQueue.push_back(&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); + if (lastStateInitialized) { + // If we have skipped to the choices of the next state, we need to repair the order of the + // distributions for the last state. + if (lastState != predecessorState) { + updateOrderedQuotientDistributions(lastState); + lastState = predecessorState; + } + } else { + lastStateInitialized = true; + lastState = choiceToStateMapping[predecessorChoice]; } - insertIntoPredecessorList(predecessorBlock, predecessorBlocks); + // Now shift the probability from this transition from the old block to the new one. + this->quotientDistributions[predecessorChoice].shiftProbability(oldBlock.getId(), newBlock.getId(), predecessorEntry.getValue()); } } - // Finally, we can reset the second marker. - splitter.data().setMarker2(splitter.getBeginIndex()); + if (lastStateInitialized) { + updateOrderedQuotientDistributions(lastState); + } } template - void NondeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitter(std::list*> const& predecessorBlocks, std::deque*>& splitterQueue) { - // TODO + bool NondeterministicModelBisimulationDecomposition::checkQuotientDistributions() const { + } template - void NondeterministicModelBisimulationDecomposition::refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) { - // The outline of the refinement is as follows. - // - // 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. - // - // 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. - std::list*> predecessorBlocks; - storm::storage::sparse::state_type currentPosition = splitter.getBeginIndex(); - 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 choice = predecessorEntry.getColumn(); - storm::storage::sparse::state_type predecessor = choiceToStateMapping[choice]; - storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor); - Block& 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, choice, predecessorBlock, predecessorEntry.getValue()); - - // 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); - } - - insertIntoPredecessorList(predecessorBlock, predecessorBlocks); - } + bool NondeterministicModelBisimulationDecomposition::splitBlockAccordingToCurrentQuotientDistributions(Block& block, std::deque*>& splitterQueue) { + bool split = this->partition.splitBlock(block, + [this] (storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { + return quotientDistributionsLess(state1, state2); + }, + [this, &block, &splitterQueue] (Block& newBlock) { + updateQuotientDistributionsOfPredecessors(newBlock, block, splitterQueue); + }); + + // The quotient distributions of the predecessors of block do not need to be updated, since the probability + // will go to the block with the same id as before. + +// std::cout << "partition after split: " << std::endl; +// this->partition.print(); + + this->checkQuotientDistributions(); + + return split; + } + + template + bool NondeterministicModelBisimulationDecomposition::quotientDistributionsLess(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { + STORM_LOG_TRACE("Comparing the quotient distributions of state " << state1 << " and " << state2 << "."); + std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); + +// std::cout << "state " << state1 << std::endl; +// for (int c = nondeterministicChoiceIndices[state1]; c < nondeterministicChoiceIndices[state1 + 1]; ++c) { +// std::cout << quotientDistributions[c] << std::endl; +// } +// +// std::cout << "state " << state2 << std::endl; +// for (int c = nondeterministicChoiceIndices[state2]; c < nondeterministicChoiceIndices[state2 + 1]; ++c) { +// std::cout << quotientDistributions[c] << std::endl; +// } + + auto firstIt = orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state1]; + auto firstIte = orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state1 + 1]; + auto secondIt = orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state2]; + auto secondIte = orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state2 + 1]; + + for (; firstIt != firstIte && secondIt != secondIte; ++firstIt, ++secondIt) { + // If the current distributions are in a less-than relationship, we can return a result. + if ((*firstIt)->less(**secondIt, this->comparator)) { + return true; + } else if ((*secondIt)->less(**firstIt, this->comparator)) { + return false; } - // If, as a consequence of shifting states, we need to skip some elements, do so now. - splitterIt += elementsToSkip; - currentPosition += elementsToSkip; + // If the distributions matched, we need to advance both distribution iterators to the next distribution + // that is larger. + while (firstIt != firstIte && std::next(firstIt) != firstIte && !(*firstIt)->less(**std::next(firstIt), this->comparator)) { + ++firstIt; + } + while (secondIt != secondIte && std::next(secondIt) != secondIte && !(*secondIt)->less(**std::next(secondIt), this->comparator)) { + ++secondIt; + } } - // 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, predecessorBlocks); + if (firstIt == firstIte && secondIt != secondIte) { + return true; + } + return false; + } + + template + void NondeterministicModelBisimulationDecomposition::refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) { + if (!possiblyNeedsRefinement(splitter)) { + return; } - // Finally, we split the block based on the precomputed probabilities and the chosen bisimulation type. - refinePredecessorBlocksOfSplitter(predecessorBlocks, splitterQueue); + STORM_LOG_TRACE("Refining block " << splitter.getId()); + + splitBlockAccordingToCurrentQuotientDistributions(splitter, splitterQueue); } template class NondeterministicModelBisimulationDecomposition>; +#ifdef STORM_HAVE_CARL + template class NondeterministicModelBisimulationDecomposition>; +#endif + } } diff --git a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h index eb315dce6..827dc39cc 100644 --- a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h +++ b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h @@ -4,6 +4,8 @@ #include "src/storage/bisimulation/BisimulationDecomposition.h" #include "src/storage/bisimulation/DeterministicBlockData.h" +#include "src/storage/Distribution.h" + namespace storm { namespace utility { template class ConstantsComparator; @@ -37,44 +39,41 @@ namespace storm { virtual void refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) override; + virtual void initialize() override; + private: // Creates the mapping from the choice indices to the states. void createChoiceToStateMapping(); - // Retrieves whether the given predecessor of the splitters possibly needs refinement. - bool possiblyNeedsRefinement(bisimulation::Block const& predecessorBlock) const; - - // Increases the probability of moving to the current splitter for the given choice of the given state. - void increaseProbabilityToSplitter(storm::storage::sparse::state_type state, uint_fast64_t choice, bisimulation::Block const& predecessorBlock, ValueType const& value); - - // Clears the probabilities of all choices of the given state. - void clearProbabilitiesToSplitter(storm::storage::sparse::state_type state); + // Initializes the quotient distributions wrt. to the current partition. + void initializeQuotientDistributions(); - // 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& predecessorBlock); + // Retrieves whether the given block possibly needs refinement. + bool possiblyNeedsRefinement(bisimulation::Block const& block) const; - // 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& predecessorBlock); + // Splits the given block according to the current quotient distributions. + bool splitBlockAccordingToCurrentQuotientDistributions(bisimulation::Block& block, std::deque*>& splitterQueue); - // 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& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter, uint_fast64_t& elementsToSkip); + // Retrieves whether the quotient distributions of state 1 are considered to be less than the ones of state 2. + bool quotientDistributionsLess(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2); - // Inserts the block into the list of predecessors if it is not already contained. - void insertIntoPredecessorList(bisimulation::Block& predecessorBlock, std::list*>& predecessorBlocks); + // Updates the ordered list of quotient distribution for the given state. + void updateOrderedQuotientDistributions(storm::storage::sparse::state_type state); - // Explores the remaining states of the splitter. - void exploreRemainingStatesOfSplitter(bisimulation::Block& splitter, std::list*>& predecessorBlocks); + // Updates the quotient distributions of the predecessors of the new block by taking the probability mass + // away from the old block. + void updateQuotientDistributionsOfPredecessors(bisimulation::Block const& newBlock, bisimulation::Block const& oldBlock, std::deque*>& splitterQueue); - // Refines the predecessor blocks wrt. strong bisimulation. - void refinePredecessorBlocksOfSplitter(std::list*> const& predecessorBlocks, std::deque*>& splitterQueue); + bool checkQuotientDistributions() const; // A mapping from choice indices to the state state that has this choice. std::vector choiceToStateMapping; - // A vector that holds the probabilities for all nondeterministic choices of all states of going into the - // splitter. This is used by the method that refines a block based on probabilities. - std::vector probabilitiesToCurrentSplitter; + // A vector that holds the quotient distributions for all nondeterministic choices of all states. + std::vector> quotientDistributions; + // A vector that stores for each state the ordered list of quotient distributions. + std::vector const*> orderedQuotientDistributions; }; } } diff --git a/src/utility/ConstantsComparator.cpp b/src/utility/ConstantsComparator.cpp index ed3d82fe4..c3d6018cc 100644 --- a/src/utility/ConstantsComparator.cpp +++ b/src/utility/ConstantsComparator.cpp @@ -35,6 +35,11 @@ namespace storm { return false; } + template + bool ConstantsComparator::isLess(ValueType const& value1, ValueType const& value2) const { + return value1 < value2; + } + ConstantsComparator::ConstantsComparator() : precision(static_cast(storm::settings::generalSettings().getPrecision())) { // Intentionally left empty. } @@ -63,6 +68,10 @@ namespace storm { return value == storm::utility::infinity(); } + bool ConstantsComparator::isLess(float const& value1, float const& value2) const { + return std::abs(value1 - value2) < precision; + } + ConstantsComparator::ConstantsComparator() : precision(storm::settings::generalSettings().getPrecision()) { // Intentionally left empty. } @@ -90,7 +99,11 @@ namespace storm { bool ConstantsComparator::isConstant(double const& value) const { return true; } - + + bool ConstantsComparator::isLess(double const& value1, double const& value2) const { + return std::abs(value1 - value2) < precision; + } + // Explicit instantiations. template class ConstantsComparator; template class ConstantsComparator; diff --git a/src/utility/ConstantsComparator.h b/src/utility/ConstantsComparator.h index 7cd03eab3..d30a811d2 100644 --- a/src/utility/ConstantsComparator.h +++ b/src/utility/ConstantsComparator.h @@ -21,6 +21,8 @@ namespace storm { bool isConstant(ValueType const& value) const; bool isInfinity(ValueType const& value) const; + + bool isLess(ValueType const& value1, ValueType const& value2) const; }; // For floats we specialize this class and consider the comparison modulo some predefined precision. @@ -41,6 +43,8 @@ namespace storm { bool isInfinity(float const& value) const; + bool isLess(float const& value1, float const& value2) const; + private: // The precision used for comparisons. float precision; @@ -64,6 +68,8 @@ namespace storm { bool isConstant(double const& value) const; + bool isLess(double const& value1, double const& value2) const; + private: // The precision used for comparisons. double precision; diff --git a/src/utility/storm.h b/src/utility/storm.h index 221545743..d1e32f839 100644 --- a/src/utility/storm.h +++ b/src/utility/storm.h @@ -45,6 +45,7 @@ // Headers for model processing. #include "src/storage/bisimulation/DeterministicModelBisimulationDecomposition.h" +#include "src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h" // Headers for model checking. #include "src/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" @@ -114,7 +115,7 @@ namespace storm { } template - std::shared_ptr performSparseBisimulationMinimization(std::shared_ptr model, std::vector> const& formulas) { + std::shared_ptr performDeterministicSparseBisimulationMinimization(std::shared_ptr model, std::vector> const& formulas) { std::cout << "Performing bisimulation minimization... "; typename storm::storage::DeterministicModelBisimulationDecomposition::Options options; if (!formulas.empty()) { @@ -132,19 +133,40 @@ namespace storm { return model; } + template + std::shared_ptr performNondeterministicSparseBisimulationMinimization(std::shared_ptr model, std::vector> const& formulas) { + std::cout << "Performing bisimulation minimization... "; + typename storm::storage::DeterministicModelBisimulationDecomposition::Options options; + if (!formulas.empty()) { + options = typename storm::storage::NondeterministicModelBisimulationDecomposition::Options(*model, formulas); + } + if (storm::settings::bisimulationSettings().isWeakBisimulationSet()) { + options.type = storm::storage::BisimulationType::Weak; + options.bounded = false; + } + + storm::storage::NondeterministicModelBisimulationDecomposition bisimulationDecomposition(*model, options); + bisimulationDecomposition.computeBisimulationDecomposition(); + model = bisimulationDecomposition.getQuotient(); + std::cout << "done." << std::endl << std::endl; + return model; + } + template, ModelType>::value, bool>::type = 0> std::shared_ptr preprocessModel(std::shared_ptr model, std::vector> const& formulas) { if (storm::settings::generalSettings().isBisimulationSet()) { STORM_LOG_THROW(model->isSparseModel(), storm::exceptions::InvalidSettingsException, "Bisimulation minimization is currently only available for sparse models."); - STORM_LOG_THROW(model->isOfType(storm::models::ModelType::Dtmc) || model->isOfType(storm::models::ModelType::Ctmc), storm::exceptions::InvalidSettingsException, "Bisimulation minimization is currently only available for DTMCs and CTMCs."); + STORM_LOG_THROW(model->isOfType(storm::models::ModelType::Dtmc) || model->isOfType(storm::models::ModelType::Ctmc) || model->isOfType(storm::models::ModelType::Mdp), storm::exceptions::InvalidSettingsException, "Bisimulation minimization is currently only available for DTMCs, CTMCs and MDPs."); model->reduceToStateBasedRewards(); if (model->isOfType(storm::models::ModelType::Dtmc)) { - return performSparseBisimulationMinimization>(model->template as>(), formulas); + return performDeterministicSparseBisimulationMinimization>(model->template as>(), formulas); + } else if (model->isOfType(storm::models::ModelType::Ctmc)) { + return performDeterministicSparseBisimulationMinimization>(model->template as>(), formulas); } else { - return performSparseBisimulationMinimization>(model->template as>(), formulas); + return performNondeterministicSparseBisimulationMinimization>(model->template as>(), formulas); } } From b31d98909bfd45ff18b56eb2478802b61960d35e Mon Sep 17 00:00:00 2001 From: dehnert Date: Sat, 7 Nov 2015 11:40:38 +0100 Subject: [PATCH 18/24] Explicit MDP bisim working but unfortunately slow :( Former-commit-id: 6714bdbd6199ea87c95614f2f756d7de5331b378 --- src/storage/Distribution.cpp | 14 +- src/storage/Distribution.h | 20 +-- .../BisimulationDecomposition.cpp | 10 +- ...ministicModelBisimulationDecomposition.cpp | 4 + ...ministicModelBisimulationDecomposition.cpp | 124 +++++++++++++++--- ...erministicModelBisimulationDecomposition.h | 5 +- src/storage/bisimulation/Partition.cpp | 3 + src/utility/ConstantsComparator.cpp | 2 +- 8 files changed, 137 insertions(+), 45 deletions(-) diff --git a/src/storage/Distribution.cpp b/src/storage/Distribution.cpp index 5dd19e50c..833324bce 100644 --- a/src/storage/Distribution.cpp +++ b/src/storage/Distribution.cpp @@ -15,14 +15,14 @@ namespace storm { namespace storage { template - Distribution::Distribution() : hash(0) { + Distribution::Distribution() { // Intentionally left empty. } template bool Distribution::equals(Distribution const& other, storm::utility::ConstantsComparator const& comparator) const { // We need to check equality by ourselves, because we need to account for epsilon differences. - if (this->distribution.size() != other.distribution.size() || this->getHash() != other.getHash()) { + if (this->distribution.size() != other.distribution.size()) { return false; } @@ -32,9 +32,11 @@ namespace storm { for (; first1 != last1; ++first1, ++first2) { if (first1->first != first2->first) { + std::cout << "false in first" << std::endl; return false; } if (!comparator.isEqual(first1->second, first2->second)) { + std::cout << "false in second " << std::setprecision(15) << first1->second << " vs " << first2->second << std::endl; return false; } } @@ -46,7 +48,6 @@ namespace storm { void Distribution::addProbability(storm::storage::sparse::state_type const& state, ValueType const& probability) { auto it = this->distribution.find(state); if (it == this->distribution.end()) { - this->hash += static_cast(state); this->distribution.emplace_hint(it, state, probability); } else { it->second += probability; @@ -101,12 +102,7 @@ namespace storm { } } } - - template - std::size_t Distribution::getHash() const { - return this->hash ^ (this->distribution.size() << 8); - } - + template std::size_t Distribution::size() const { return this->distribution.size(); diff --git a/src/storage/Distribution.h b/src/storage/Distribution.h index 93330c009..9d3276941 100644 --- a/src/storage/Distribution.h +++ b/src/storage/Distribution.h @@ -5,6 +5,8 @@ #include #include +#include "src/utility/OsDetection.h" + #include "src/storage/sparse/StateType.h" namespace storm { @@ -26,6 +28,14 @@ namespace storm { * Creates an empty distribution. */ Distribution(); + + Distribution(Distribution const& other) = default; + Distribution& operator=(Distribution const& other) = default; + +#ifndef WINDOWS + Distribution(Distribution&& other) = default; + Distribution& operator=(Distribution&& other) = default; +#endif /*! * Checks whether the two distributions specify the same probabilities to go to the same states. @@ -101,13 +111,6 @@ namespace storm { */ void scale(storm::storage::sparse::state_type const& state); - /*! - * Retrieves the hash value of the distribution. - * - * @return The hash value of the distribution. - */ - std::size_t getHash() const; - /*! * Retrieves the size of the distribution, i.e. the size of the support set. */ @@ -118,9 +121,6 @@ namespace storm { private: // A list of states and the probabilities that are assigned to them. container_type distribution; - - // A hash value that is maintained to allow for quicker equality comparison between distributions. - std::size_t hash; }; template diff --git a/src/storage/bisimulation/BisimulationDecomposition.cpp b/src/storage/bisimulation/BisimulationDecomposition.cpp index 0dc8369fa..820cd0596 100644 --- a/src/storage/bisimulation/BisimulationDecomposition.cpp +++ b/src/storage/bisimulation/BisimulationDecomposition.cpp @@ -84,7 +84,9 @@ namespace storm { std::shared_ptr newFormula = formula.asSharedPointer(); if (formula.isProbabilityOperatorFormula()) { - optimalityType = formula.asProbabilityOperatorFormula().getOptimalityType(); + if (formula.asProbabilityOperatorFormula().hasOptimalityType()) { + optimalityType = formula.asProbabilityOperatorFormula().getOptimalityType(); + } newFormula = formula.asProbabilityOperatorFormula().getSubformula().asSharedPointer(); } else if (formula.isRewardOperatorFormula()) { optimalityType = formula.asRewardOperatorFormula().getOptimalityType(); @@ -168,8 +170,6 @@ namespace storm { } else { this->initializeLabelBasedPartition(); } - std::cout << "initial partition is " << std::endl; - this->partition.print(); std::chrono::high_resolution_clock::duration initialPartitionTime = std::chrono::high_resolution_clock::now() - initialPartitionStart; this->initialize(); @@ -218,9 +218,7 @@ namespace storm { uint_fast64_t iterations = 0; while (!splitterQueue.empty()) { ++iterations; - // Optionally: sort the splitter queue according to some criterion (here: prefer small splitters). - std::sort(splitterQueue.begin(), splitterQueue.end(), [] (Block const* b1, Block const* b2) { return b1->getNumberOfStates() < b2->getNumberOfStates(); } ); - + // Get and prepare the next splitter. Block* splitter = splitterQueue.front(); splitterQueue.pop_front(); diff --git a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp index c9672cd47..f81519084 100644 --- a/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/DeterministicModelBisimulationDecomposition.cpp @@ -159,6 +159,8 @@ namespace storm { template void DeterministicModelBisimulationDecomposition::refinePredecessorBlocksOfSplitterStrong(std::list*> const& predecessorBlocks, std::deque*>& splitterQueue) { for (auto block : predecessorBlocks) { + STORM_LOG_TRACE("Refining predecessor " << block->getId() << " of splitter"); + // Depending on the actions we need to take, the block to refine changes, so we need to keep track of it. Block* blockToRefineProbabilistically = block; @@ -428,6 +430,8 @@ namespace storm { template void DeterministicModelBisimulationDecomposition::refinePartitionBasedOnSplitter(bisimulation::Block& splitter, std::deque*>& splitterQueue) { + STORM_LOG_TRACE("Refining partition based on splitter " << splitter.getId()); + // The outline of the refinement is as follows. // // We iterate over all states of the splitter and determine for each predecessor the state the probability diff --git a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp index 1b16cd6b4..89a77a75f 100644 --- a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp @@ -74,9 +74,8 @@ namespace storm { template void NondeterministicModelBisimulationDecomposition::buildQuotient() { - std::cout << "Found " << this->partition.size() << " blocks" << std::endl; - this->partition.print(); STORM_LOG_ASSERT(false, "Not yet implemented"); + // TODO } template @@ -129,17 +128,105 @@ namespace storm { template bool NondeterministicModelBisimulationDecomposition::checkQuotientDistributions() const { - + std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); + for (auto state = 0; state < this->model.getNumberOfStates(); ++state) { + for (auto choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) { + storm::storage::Distribution distribution; + for (auto const& element : this->model.getTransitionMatrix().getRow(choice)) { + distribution.addProbability(this->partition.getBlock(element.getColumn()).getId(), element.getValue()); + } + + if (!distribution.equals(quotientDistributions[choice])) { + std::cout << "the distributions for choice " << choice << " of state " << state << " do not match." << std::endl; + std::cout << "is: " << quotientDistributions[choice] << " but should be " << distribution << std::endl; + exit(-1); + } + + bool less1 = distribution.less(quotientDistributions[choice], this->comparator); + bool less2 = quotientDistributions[choice].less(distribution, this->comparator); + + if (distribution.equals(quotientDistributions[choice]) && (less1 || less2)) { + std::cout << "mismatch of equality and less for " << std::endl; + std::cout << quotientDistributions[choice] << " vs " << distribution << std::endl; + exit(-1); + } + } + + for (auto choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1] - 1; ++choice) { + if (orderedQuotientDistributions[choice + 1]->less(*orderedQuotientDistributions[choice], this->comparator)) { + std::cout << "choice " << (choice+1) << " is less than predecessor" << std::endl; + std::cout << *orderedQuotientDistributions[choice] << " should be less than " << *orderedQuotientDistributions[choice + 1] << std::endl; + exit(-1); + } + } + } + return true; + } + + template + bool NondeterministicModelBisimulationDecomposition::printDistributions(uint_fast64_t state) const { + std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); + for (auto choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) { + std::cout << quotientDistributions[choice] << std::endl; + } + return true; + } + + template + bool NondeterministicModelBisimulationDecomposition::checkBlockStable(bisimulation::Block const& newBlock) const { + std::cout << "checking stability of new block " << newBlock.getId() << " of size " << newBlock.getNumberOfStates() << std::endl; + for (auto stateIt1 = this->partition.begin(newBlock), stateIte1 = this->partition.end(newBlock); stateIt1 != stateIte1; ++stateIt1) { + for (auto stateIt2 = this->partition.begin(newBlock), stateIte2 = this->partition.end(newBlock); stateIt2 != stateIte2; ++stateIt2) { + bool less1 = quotientDistributionsLess(*stateIt1, *stateIt2); + bool less2 = quotientDistributionsLess(*stateIt2, *stateIt1); + if (less1 || less2) { + std::cout << "the partition is not stable for the states " << *stateIt1 << " and " << *stateIt2 << std::endl; + std::cout << "less1 " << less1 << " and less2 " << less2 << std::endl; + + std::cout << "distributions of state " << *stateIt1 << std::endl; + this->printDistributions(*stateIt1); + std::cout << "distributions of state " << *stateIt2 << std::endl; + this->printDistributions(*stateIt2); + exit(-1); + } + } + } + return true; + } + + template + bool NondeterministicModelBisimulationDecomposition::checkDistributionsDifferent(bisimulation::Block const& block, storm::storage::sparse::state_type end) const { + for (auto stateIt1 = this->partition.begin(block), stateIte1 = this->partition.end(block); stateIt1 != stateIte1; ++stateIt1) { + for (auto stateIt2 = this->partition.begin() + block.getEndIndex(), stateIte2 = this->partition.begin() + end; stateIt2 != stateIte2; ++stateIt2) { + if (!quotientDistributionsLess(*stateIt1, *stateIt2)) { + std::cout << "distributions are not less, even though they should be!" << std::endl; + exit(-3); + } else { + std::cout << "less:" << std::endl; + this->printDistributions(*stateIt1); + std::cout << "and" << std::endl; + this->printDistributions(*stateIt2); + } + } + } + return true; } template bool NondeterministicModelBisimulationDecomposition::splitBlockAccordingToCurrentQuotientDistributions(Block& block, std::deque*>& splitterQueue) { + std::list*> newBlocks; bool split = this->partition.splitBlock(block, [this] (storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { - return quotientDistributionsLess(state1, state2); + bool result = quotientDistributionsLess(state1, state2); +// std::cout << state1 << " is " << (!result ? "not" : "") << " smaller than " << state2 << std::endl; + return result; }, - [this, &block, &splitterQueue] (Block& newBlock) { - updateQuotientDistributionsOfPredecessors(newBlock, block, splitterQueue); + [this, &block, &splitterQueue, &newBlocks] (Block& newBlock) { + newBlocks.push_back(&newBlock); + +// this->checkBlockStable(newBlock); +// this->checkDistributionsDifferent(block, block.getEndIndex()); +// this->checkQuotientDistributions(); }); // The quotient distributions of the predecessors of block do not need to be updated, since the probability @@ -148,25 +235,26 @@ namespace storm { // std::cout << "partition after split: " << std::endl; // this->partition.print(); - this->checkQuotientDistributions(); + // defer updating the quotient distributions until *after* all splits, because + // it otherwise influences the subsequent splits! + for (auto el : newBlocks) { + this->updateQuotientDistributionsOfPredecessors(*el, block, splitterQueue); + } + +// this->checkQuotientDistributions(); return split; } template - bool NondeterministicModelBisimulationDecomposition::quotientDistributionsLess(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { + bool NondeterministicModelBisimulationDecomposition::quotientDistributionsLess(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) const { STORM_LOG_TRACE("Comparing the quotient distributions of state " << state1 << " and " << state2 << "."); std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); - -// std::cout << "state " << state1 << std::endl; -// for (int c = nondeterministicChoiceIndices[state1]; c < nondeterministicChoiceIndices[state1 + 1]; ++c) { -// std::cout << quotientDistributions[c] << std::endl; -// } -// -// std::cout << "state " << state2 << std::endl; -// for (int c = nondeterministicChoiceIndices[state2]; c < nondeterministicChoiceIndices[state2 + 1]; ++c) { -// std::cout << quotientDistributions[c] << std::endl; -// } + +// std::cout << "distributions of state " << state1 << std::endl; +// this->printDistributions(state1); +// std::cout << "distributions of state " << state2 << std::endl; +// this->printDistributions(state2); auto firstIt = orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state1]; auto firstIte = orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state1 + 1]; diff --git a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h index 827dc39cc..18a6e8e58 100644 --- a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h +++ b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h @@ -55,7 +55,7 @@ namespace storm { bool splitBlockAccordingToCurrentQuotientDistributions(bisimulation::Block& block, std::deque*>& splitterQueue); // Retrieves whether the quotient distributions of state 1 are considered to be less than the ones of state 2. - bool quotientDistributionsLess(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2); + bool quotientDistributionsLess(storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) const; // Updates the ordered list of quotient distribution for the given state. void updateOrderedQuotientDistributions(storm::storage::sparse::state_type state); @@ -65,6 +65,9 @@ namespace storm { void updateQuotientDistributionsOfPredecessors(bisimulation::Block const& newBlock, bisimulation::Block const& oldBlock, std::deque*>& splitterQueue); bool checkQuotientDistributions() const; + bool checkBlockStable(bisimulation::Block const& newBlock) const; + bool printDistributions(storm::storage::sparse::state_type state) const; + bool checkDistributionsDifferent(bisimulation::Block const& block, storm::storage::sparse::state_type end) const; // A mapping from choice indices to the state state that has this choice. std::vector choiceToStateMapping; diff --git a/src/storage/bisimulation/Partition.cpp b/src/storage/bisimulation/Partition.cpp index d72fe80cc..698eb5cd7 100644 --- a/src/storage/bisimulation/Partition.cpp +++ b/src/storage/bisimulation/Partition.cpp @@ -180,6 +180,7 @@ namespace storm { template void Partition::sortRange(storm::storage::sparse::state_type beginIndex, storm::storage::sparse::state_type endIndex, std::function const& less, bool updatePositions) { std::sort(this->states.begin() + beginIndex, this->states.begin() + endIndex, less); + if (updatePositions) { mapStatesToPositions(this->states.begin() + beginIndex, this->states.begin() + endIndex); } @@ -210,6 +211,7 @@ namespace storm { template std::pair>>::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."); + STORM_LOG_TRACE("Splitting " << block.getId() << " at position " << position << " (begin was " << block.getBeginIndex() << "."); // In case one of the resulting blocks would be empty, we simply return the current block and do not create // a new one. @@ -247,6 +249,7 @@ namespace storm { std::vector::const_iterator upperBound; do { upperBound = std::upper_bound(it, ite, *it, less); + if (upperBound != ite) { wasSplit = true; auto result = this->splitBlock(block, std::distance(this->states.cbegin(), upperBound)); diff --git a/src/utility/ConstantsComparator.cpp b/src/utility/ConstantsComparator.cpp index c3d6018cc..4ad0a2612 100644 --- a/src/utility/ConstantsComparator.cpp +++ b/src/utility/ConstantsComparator.cpp @@ -101,7 +101,7 @@ namespace storm { } bool ConstantsComparator::isLess(double const& value1, double const& value2) const { - return std::abs(value1 - value2) < precision; + return value1 < value2 - precision; } // Explicit instantiations. From 1d49bc6dd0ebfe1f775578cef7d801570906a5ca Mon Sep 17 00:00:00 2001 From: dehnert Date: Mon, 9 Nov 2015 12:02:20 +0100 Subject: [PATCH 19/24] extracting the bisimulation quotient for MDPs; tests for MDP bisimulation Former-commit-id: 5613c653ba6ea35b7933b6a238ef814c517a8e6d --- src/builder/ExplicitPrismModelBuilder.h | 2 - src/storage/Distribution.cpp | 2 - ...ministicModelBisimulationDecomposition.cpp | 133 +++++++++++++++++- ...sticModelBisimulationDecompositionTest.cpp | 58 ++++++++ 4 files changed, 184 insertions(+), 11 deletions(-) create mode 100644 test/functional/storage/NondeterministicModelBisimulationDecompositionTest.cpp diff --git a/src/builder/ExplicitPrismModelBuilder.h b/src/builder/ExplicitPrismModelBuilder.h index 8680c0d7b..dfab62136 100644 --- a/src/builder/ExplicitPrismModelBuilder.h +++ b/src/builder/ExplicitPrismModelBuilder.h @@ -72,8 +72,6 @@ namespace storm { std::string stateInfo(uint_fast64_t state) const override { return valuations[state].toString(); } - - }; // A structure storing information about the used variables of the program. diff --git a/src/storage/Distribution.cpp b/src/storage/Distribution.cpp index 833324bce..50d794d01 100644 --- a/src/storage/Distribution.cpp +++ b/src/storage/Distribution.cpp @@ -32,11 +32,9 @@ namespace storm { for (; first1 != last1; ++first1, ++first2) { if (first1->first != first2->first) { - std::cout << "false in first" << std::endl; return false; } if (!comparator.isEqual(first1->second, first2->second)) { - std::cout << "false in second " << std::setprecision(15) << first1->second << " vs " << first2->second << std::endl; return false; } } diff --git a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp index 89a77a75f..21dd36d89 100644 --- a/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp +++ b/src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp @@ -49,13 +49,29 @@ namespace storm { template void NondeterministicModelBisimulationDecomposition::initializeQuotientDistributions() { std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); - for (auto choice = 0; choice < nondeterministicChoiceIndices.back(); ++choice) { - for (auto entry : this->model.getTransitionMatrix().getRow(choice)) { - if (!this->comparator.isZero(entry.getValue())) { - this->quotientDistributions[choice].addProbability(this->partition.getBlock(entry.getColumn()).getId(), entry.getValue()); + + for (auto const& block : this->partition.getBlocks()) { + if (block->data().absorbing()) { + // If the block is marked as absorbing, we need to create the corresponding distributions. + for (auto stateIt = this->partition.begin(*block), stateIte = this->partition.end(*block); stateIt != stateIte; ++stateIt) { + for (uint_fast64_t choice = nondeterministicChoiceIndices[*stateIt]; choice < nondeterministicChoiceIndices[*stateIt + 1]; ++choice) { + this->quotientDistributions[choice].addProbability(block->getId(), storm::utility::one()); + orderedQuotientDistributions[choice] = &this->quotientDistributions[choice]; + } + } + } else { + // Otherwise, we compute the probabilities from the transition matrix. + for (auto stateIt = this->partition.begin(*block), stateIte = this->partition.end(*block); stateIt != stateIte; ++stateIt) { + for (uint_fast64_t choice = nondeterministicChoiceIndices[*stateIt]; choice < nondeterministicChoiceIndices[*stateIt + 1]; ++choice) { + for (auto entry : this->model.getTransitionMatrix().getRow(choice)) { + if (!this->comparator.isZero(entry.getValue())) { + this->quotientDistributions[choice].addProbability(this->partition.getBlock(entry.getColumn()).getId(), entry.getValue()); + } + } + orderedQuotientDistributions[choice] = &this->quotientDistributions[choice]; + } } } - orderedQuotientDistributions[choice] = &this->quotientDistributions[choice]; } for (auto state = 0; state < this->model.getNumberOfStates(); ++state) { @@ -74,8 +90,106 @@ namespace storm { template void NondeterministicModelBisimulationDecomposition::buildQuotient() { - STORM_LOG_ASSERT(false, "Not yet implemented"); - // TODO + // In order to create the quotient model, we need to construct + // (a) the new transition matrix, + // (b) the new labeling, + // (c) the new reward structures. + + // Prepare a matrix builder for (a). + storm::storage::SparseMatrixBuilder builder(0, this->size(), 0, false, true, this->size()); + + // Prepare the new state labeling for (b). + storm::models::sparse::StateLabeling newLabeling(this->size()); + std::set atomicPropositionsSet = this->options.respectedAtomicPropositions.get(); + atomicPropositionsSet.insert("init"); + std::vector atomicPropositions = std::vector(atomicPropositionsSet.begin(), atomicPropositionsSet.end()); + for (auto const& ap : atomicPropositions) { + newLabeling.addLabel(ap); + } + + // If the model had state rewards, we need to build the state rewards for the quotient as well. + boost::optional> stateRewards; + if (this->options.keepRewards && this->model.hasRewardModel()) { + stateRewards = std::vector(this->blocks.size()); + } + + // Now build (a) and (b) by traversing all blocks. + uint_fast64_t currentRow = 0; + std::vector nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices(); + for (uint_fast64_t blockIndex = 0; blockIndex < this->blocks.size(); ++blockIndex) { + auto const& block = this->blocks[blockIndex]; + + // Open new row group for the new meta state. + builder.newRowGroup(currentRow); + + // Pick one representative state. For strong bisimulation it doesn't matter which state it is, because + // they all behave equally. + storm::storage::sparse::state_type representativeState = *block.begin(); + Block const& oldBlock = this->partition.getBlock(representativeState); + + // If the block is absorbing, we simply add a self-loop. + if (oldBlock.data().absorbing()) { + builder.addNextValue(currentRow, blockIndex, storm::utility::one()); + ++currentRow; + + // If the block has a special representative state, we retrieve it now. + if (oldBlock.data().hasRepresentativeState()) { + representativeState = oldBlock.data().representativeState(); + } + + // 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 (this->model.getStateLabeling().getStateHasLabel(ap, representativeState)) { + newLabeling.addLabelToState(ap, blockIndex); + } + } + } else { + // Add the outgoing choices of the block. + for (uint_fast64_t choice = nondeterministicChoiceIndices[representativeState]; choice < nondeterministicChoiceIndices[representativeState + 1]; ++choice) { + // If the choice is the same as the last one, we do not need to add it. + if (choice > nondeterministicChoiceIndices[representativeState] && quotientDistributions[choice - 1].equals(quotientDistributions[choice], this->comparator)) { + continue; + } + + for (auto entry : quotientDistributions[choice]) { + builder.addNextValue(currentRow, entry.first, entry.second); + } + ++currentRow; + } + + // Otherwise add all atomic propositions to the equivalence class that the representative state + // satisfies. + for (auto const& ap : atomicPropositions) { + if (this->model.getStateLabeling().getStateHasLabel(ap, representativeState)) { + newLabeling.addLabelToState(ap, blockIndex); + } + } + } + + // If the model has state rewards, we simply copy the state reward of the representative state, because + // all states in a block are guaranteed to have the same state reward. + if (this->options.keepRewards && this->model.hasRewardModel()) { + typename std::unordered_map::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 : this->model.getInitialStates()) { + Block const& initialBlock = this->partition.getBlock(initialState); + newLabeling.addLabelToState("init", initialBlock.getId()); + } + + // Construct the reward model mapping. + std::unordered_map rewardModels; + if (this->options.keepRewards && this->model.hasRewardModel()) { + typename std::unordered_map::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(new ModelType(builder.build(), std::move(newLabeling), std::move(rewardModels))); } template @@ -98,6 +212,11 @@ namespace storm { storm::storage::sparse::state_type predecessorState = choiceToStateMapping[predecessorChoice]; Block& predecessorBlock = this->partition.getBlock(predecessorState); + // If the predecessor block is marked as absorbing, we do not need to update anything. + if (predecessorBlock.data().absorbing()) { + continue; + } + // If the predecessor block is not marked as to-refined, we do so now. if (!predecessorBlock.data().splitter()) { predecessorBlock.data().setSplitter(); diff --git a/test/functional/storage/NondeterministicModelBisimulationDecompositionTest.cpp b/test/functional/storage/NondeterministicModelBisimulationDecompositionTest.cpp new file mode 100644 index 000000000..e2207bebb --- /dev/null +++ b/test/functional/storage/NondeterministicModelBisimulationDecompositionTest.cpp @@ -0,0 +1,58 @@ +#include "gtest/gtest.h" +#include "storm-config.h" + +#include "src/parser/PrismParser.h" +#include "src/parser/FormulaParser.h" + +#include "src/builder/ExplicitPrismModelBuilder.h" + +#include "src/storage/bisimulation/NondeterministicModelBisimulationDecomposition.h" +#include "src/models/sparse/Mdp.h" +#include "src/models/sparse/StandardRewardModel.h" + +TEST(NondeterministicModelBisimulationDecomposition, TwoDice) { + storm::prism::Program program = storm::parser::PrismParser::parse(STORM_CPP_TESTS_BASE_PATH "/functional/builder/two_dice.nm"); + + // Build the die model without its reward model. + std::shared_ptr> model = storm::builder::ExplicitPrismModelBuilder().translateProgram(program); + + ASSERT_EQ(model->getType(), storm::models::ModelType::Mdp); + std::shared_ptr> mdp = model->as>(); + + storm::storage::NondeterministicModelBisimulationDecomposition> bisim(*mdp); + ASSERT_NO_THROW(bisim.computeBisimulationDecomposition()); + std::shared_ptr> result; + ASSERT_NO_THROW(result = bisim.getQuotient()); + + EXPECT_EQ(storm::models::ModelType::Mdp, result->getType()); + EXPECT_EQ(77ul, result->getNumberOfStates()); + EXPECT_EQ(183ul, result->getNumberOfTransitions()); + EXPECT_EQ(97ul, result->as>()->getNumberOfChoices()); + + typename storm::storage::NondeterministicModelBisimulationDecomposition>::Options options; + options.respectedAtomicPropositions = std::set({"two"}); + + storm::storage::NondeterministicModelBisimulationDecomposition> bisim2(*mdp, options); + ASSERT_NO_THROW(bisim2.computeBisimulationDecomposition()); + ASSERT_NO_THROW(result = bisim2.getQuotient()); + + EXPECT_EQ(storm::models::ModelType::Mdp, result->getType()); + EXPECT_EQ(11ul, result->getNumberOfStates()); + EXPECT_EQ(26ul, result->getNumberOfTransitions()); + EXPECT_EQ(14ul, result->as>()->getNumberOfChoices()); + + // A parser that we use for conveniently constructing the formulas. + storm::parser::FormulaParser formulaParser; + std::shared_ptr formula = formulaParser.parseSingleFormulaFromString("Pmin=? [F \"two\"]"); + + typename storm::storage::NondeterministicModelBisimulationDecomposition>::Options options2(*mdp, *formula); + + storm::storage::NondeterministicModelBisimulationDecomposition> bisim3(*mdp, options2); + ASSERT_NO_THROW(bisim3.computeBisimulationDecomposition()); + ASSERT_NO_THROW(result = bisim3.getQuotient()); + + EXPECT_EQ(storm::models::ModelType::Mdp, result->getType()); + EXPECT_EQ(11ul, result->getNumberOfStates()); + EXPECT_EQ(26ul, result->getNumberOfTransitions()); + EXPECT_EQ(14ul, result->as>()->getNumberOfChoices()); +} \ No newline at end of file From 0089212b7e9075e8ae389f6bfd9c55faa8234b54 Mon Sep 17 00:00:00 2001 From: TimQu Date: Tue, 10 Nov 2015 13:06:35 +0100 Subject: [PATCH 20/24] adaptation to recent changes in carl Former-commit-id: 90535a0d669bb476fa5fa630956f5889c32ea253 --- src/storage/expressions/ToRationalFunctionVisitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/expressions/ToRationalFunctionVisitor.cpp b/src/storage/expressions/ToRationalFunctionVisitor.cpp index 5740cc66d..d2d86b716 100644 --- a/src/storage/expressions/ToRationalFunctionVisitor.cpp +++ b/src/storage/expressions/ToRationalFunctionVisitor.cpp @@ -65,7 +65,7 @@ namespace storm { if (variablePair != variableToVariableMap.end()) { return convertVariableToPolynomial(variablePair->second); } else { - carl::Variable carlVariable = carl::VariablePool::getInstance().getFreshVariable(expression.getVariableName()); + carl::Variable carlVariable = carl::freshVariable(expression.getVariableName(), carl::VariableType::VT_REAL); variableToVariableMap.emplace(expression.getVariable(), carlVariable); return convertVariableToPolynomial(carlVariable); } From b792438d88d7488e689b77c67e32cf7117bda2fe Mon Sep 17 00:00:00 2001 From: TimQu Date: Tue, 10 Nov 2015 13:09:12 +0100 Subject: [PATCH 21/24] Added missing include Former-commit-id: a073cf157d8828c20965fac8b560a9457b58d5d0 --- src/storage/bisimulation/Partition.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/storage/bisimulation/Partition.h b/src/storage/bisimulation/Partition.h index b78ae62bb..b19830a44 100644 --- a/src/storage/bisimulation/Partition.h +++ b/src/storage/bisimulation/Partition.h @@ -3,6 +3,7 @@ #include #include +#include #include "src/storage/bisimulation/Block.h" From 8d98403748205fe7ea06ffc89b2a49b6598ace27 Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 10 Nov 2015 15:17:58 +0100 Subject: [PATCH 22/24] small fix for use of carl Former-commit-id: 63a0e38cf512377d30fdb474e87ab5a7da76914f --- src/storage/expressions/ToRationalFunctionVisitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/expressions/ToRationalFunctionVisitor.cpp b/src/storage/expressions/ToRationalFunctionVisitor.cpp index d2d86b716..97501aa1b 100644 --- a/src/storage/expressions/ToRationalFunctionVisitor.cpp +++ b/src/storage/expressions/ToRationalFunctionVisitor.cpp @@ -65,7 +65,7 @@ namespace storm { if (variablePair != variableToVariableMap.end()) { return convertVariableToPolynomial(variablePair->second); } else { - carl::Variable carlVariable = carl::freshVariable(expression.getVariableName(), carl::VariableType::VT_REAL); + carl::Variable carlVariable = carl::freshRealVariable(expression.getVariableName()); variableToVariableMap.emplace(expression.getVariable(), carlVariable); return convertVariableToPolynomial(carlVariable); } From b88165f27cdca16892b3fd7c61f6b82eae6432e3 Mon Sep 17 00:00:00 2001 From: dehnert Date: Tue, 10 Nov 2015 15:58:19 +0100 Subject: [PATCH 23/24] fixed the warnings related to our macros Former-commit-id: 526b6ea95666ad6d4a5bcaef68c1d68877f59da8 --- src/settings/OptionBuilder.h | 2 +- src/utility/macros.h | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/settings/OptionBuilder.h b/src/settings/OptionBuilder.h index 622a7cb1c..f70ac7c2a 100644 --- a/src/settings/OptionBuilder.h +++ b/src/settings/OptionBuilder.h @@ -85,7 +85,7 @@ namespace storm { * @return The resulting option. */ std::shared_ptr