diff --git a/src/storm/cli/cli.cpp b/src/storm/cli/cli.cpp index bdba45a30..7618fd9e3 100644 --- a/src/storm/cli/cli.cpp +++ b/src/storm/cli/cli.cpp @@ -277,8 +277,6 @@ namespace storm { model = model.preprocess(constantDefinitions); - - if (ioSettings.isNoBuildModelSet()) { return; } diff --git a/src/storm/models/sparse/Model.cpp b/src/storm/models/sparse/Model.cpp index d75065299..81ecad8a4 100644 --- a/src/storm/models/sparse/Model.cpp +++ b/src/storm/models/sparse/Model.cpp @@ -20,7 +20,7 @@ namespace storm { storm::models::sparse::StateLabeling const& stateLabeling, std::unordered_map const& rewardModels, boost::optional> const& optionalChoiceLabeling) - : ModelBase(modelType), transitionMatrix(transitionMatrix), stateLabeling(stateLabeling), + : storm::models::Model(modelType), transitionMatrix(transitionMatrix), stateLabeling(stateLabeling), rewardModels(rewardModels), choiceLabeling(optionalChoiceLabeling) { for (auto const& rewardModel : this->getRewardModels()) { STORM_LOG_THROW(!rewardModel.second.hasTransitionRewards() || rewardModel.second.getTransitionRewardMatrix().isSubmatrixOf(this->getTransitionMatrix()), storm::exceptions::IllegalArgumentException, "The transition reward matrix is not a submatrix of the transition matrix, i.e. there are rewards for transitions that do not exist."); @@ -33,7 +33,7 @@ namespace storm { storm::models::sparse::StateLabeling&& stateLabeling, std::unordered_map&& rewardModels, boost::optional>&& optionalChoiceLabeling) - : ModelBase(modelType), transitionMatrix(std::move(transitionMatrix)), stateLabeling(std::move(stateLabeling)), + : storm::models::Model(modelType), transitionMatrix(std::move(transitionMatrix)), stateLabeling(std::move(stateLabeling)), rewardModels(std::move(rewardModels)), choiceLabeling(std::move(optionalChoiceLabeling)) { for (auto const& rewardModel : this->getRewardModels()) { STORM_LOG_THROW(!rewardModel.second.hasTransitionRewards() || rewardModel.second.getTransitionRewardMatrix().isSubmatrixOf(this->getTransitionMatrix()), storm::exceptions::IllegalArgumentException, "The transition reward matrix is not a submatrix of the transition matrix, i.e. there are rewards for transitions that do not exist."); diff --git a/src/storm/models/sparse/Model.h b/src/storm/models/sparse/Model.h index a545ae3ce..57382a1c9 100644 --- a/src/storm/models/sparse/Model.h +++ b/src/storm/models/sparse/Model.h @@ -6,7 +6,7 @@ #include #include -#include "storm/models/ModelBase.h" +#include "storm/models/Model.h" #include "storm/models/sparse/StateLabeling.h" #include "storm/storage/sparse/StateType.h" #include "storm/storage/SparseMatrix.h" @@ -35,7 +35,7 @@ namespace storm { * Base class for all sparse models. */ template> - class Model : public storm::models::ModelBase { + class Model : public storm::models::Model { template friend class storm::utility::ModelInstantiator; diff --git a/src/storm/models/symbolic/Model.cpp b/src/storm/models/symbolic/Model.cpp index 1ec8ef7d9..2a6804618 100644 --- a/src/storm/models/symbolic/Model.cpp +++ b/src/storm/models/symbolic/Model.cpp @@ -35,7 +35,7 @@ namespace storm { std::vector> const& rowColumnMetaVariablePairs, std::map labelToExpressionMap, std::unordered_map const& rewardModels) - : ModelBase(modelType), manager(manager), reachableStates(reachableStates), transitionMatrix(transitionMatrix), rowVariables(rowVariables), rowExpressionAdapter(rowExpressionAdapter), columnVariables(columnVariables), rowColumnMetaVariablePairs(rowColumnMetaVariablePairs), labelToExpressionMap(labelToExpressionMap), rewardModels(rewardModels) { + : storm::models::Model(modelType), manager(manager), reachableStates(reachableStates), transitionMatrix(transitionMatrix), rowVariables(rowVariables), rowExpressionAdapter(rowExpressionAdapter), columnVariables(columnVariables), rowColumnMetaVariablePairs(rowColumnMetaVariablePairs), labelToExpressionMap(labelToExpressionMap), rewardModels(rewardModels) { this->labelToBddMap.emplace("init", initialStates); this->labelToBddMap.emplace("deadlock", deadlockStates); } @@ -52,7 +52,7 @@ namespace storm { std::vector> const& rowColumnMetaVariablePairs, std::map> labelToBddMap, std::unordered_map const& rewardModels) - : ModelBase(modelType), manager(manager), reachableStates(reachableStates), transitionMatrix(transitionMatrix), rowVariables(rowVariables), rowExpressionAdapter(nullptr), columnVariables(columnVariables), rowColumnMetaVariablePairs(rowColumnMetaVariablePairs), labelToBddMap(labelToBddMap), rewardModels(rewardModels) { + : storm::models::Model(modelType), manager(manager), reachableStates(reachableStates), transitionMatrix(transitionMatrix), rowVariables(rowVariables), rowExpressionAdapter(nullptr), columnVariables(columnVariables), rowColumnMetaVariablePairs(rowColumnMetaVariablePairs), labelToBddMap(labelToBddMap), rewardModels(rewardModels) { STORM_LOG_THROW(this->labelToBddMap.find("init") == this->labelToBddMap.end(), storm::exceptions::WrongFormatException, "Illegal custom label 'init'."); STORM_LOG_THROW(this->labelToBddMap.find("deadlock") == this->labelToBddMap.end(), storm::exceptions::WrongFormatException, "Illegal custom label 'deadlock'."); this->labelToBddMap.emplace("init", initialStates); diff --git a/src/storm/models/symbolic/Model.h b/src/storm/models/symbolic/Model.h index cae2e3c85..f12a7ad26 100644 --- a/src/storm/models/symbolic/Model.h +++ b/src/storm/models/symbolic/Model.h @@ -10,7 +10,7 @@ #include "storm/storage/expressions/Variable.h" #include "storm/storage/dd/DdType.h" #include "storm/storage/dd/Bdd.h" -#include "storm/models/ModelBase.h" +#include "storm/models/Model.h" #include "storm/utility/OsDetection.h" #include "storm-config.h" @@ -45,7 +45,7 @@ namespace storm { * Base class for all symbolic models. */ template - class Model : public storm::models::ModelBase { + class Model : public storm::models::Model { public: typedef CValueType ValueType; diff --git a/src/storm/settings/modules/BisimulationSettings.cpp b/src/storm/settings/modules/BisimulationSettings.cpp index fdef27093..2c1db8a39 100644 --- a/src/storm/settings/modules/BisimulationSettings.cpp +++ b/src/storm/settings/modules/BisimulationSettings.cpp @@ -22,7 +22,7 @@ namespace storm { this->addOption(storm::settings::OptionBuilder(moduleName, typeOptionName, true, "Sets the kind of bisimulation quotienting used.").addArgument(storm::settings::ArgumentBuilder::createStringArgument("name", "The name of the type to use.").addValidatorString(ArgumentValidatorFactory::createMultipleChoiceValidator(types)).setDefaultValueString("strong").build()).build()); std::vector quotTypes = { "sparse", "dd" }; - this->addOption(storm::settings::OptionBuilder(moduleName, typeOptionName, true, "Sets the format in which the quotient is extracted (only applies to DD-based bisimulation).").addArgument(storm::settings::ArgumentBuilder::createStringArgument("format", "The format of the quotient.").addValidatorString(ArgumentValidatorFactory::createMultipleChoiceValidator(quotTypes)).setDefaultValueString("dd").build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, quotientFormatOptionName, true, "Sets the format in which the quotient is extracted (only applies to DD-based bisimulation).").addArgument(storm::settings::ArgumentBuilder::createStringArgument("format", "The format of the quotient.").addValidatorString(ArgumentValidatorFactory::createMultipleChoiceValidator(quotTypes)).setDefaultValueString("dd").build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, representativeOptionName, false, "Sets whether to use representatives in the quotient rather than block numbers.").build()); } @@ -42,7 +42,7 @@ namespace storm { } BisimulationSettings::QuotientFormat BisimulationSettings::getQuotientFormat() const { - std::string quotientFormatAsString = this->getOption(typeOptionName).getArgumentByName("format").getValueAsString(); + std::string quotientFormatAsString = this->getOption(quotientFormatOptionName).getArgumentByName("format").getValueAsString(); if (quotientFormatAsString == "sparse") { return BisimulationSettings::QuotientFormat::Sparse; } diff --git a/src/storm/storage/SparseMatrix.cpp b/src/storm/storage/SparseMatrix.cpp index d71fa7ba4..11878f0d2 100644 --- a/src/storm/storage/SparseMatrix.cpp +++ b/src/storm/storage/SparseMatrix.cpp @@ -125,41 +125,47 @@ namespace storm { // the insertion. bool fixCurrentRow = row == lastRow && column < lastColumn; - // If we switched to another row, we have to adjust the missing entries in the row indices vector. - if (row != lastRow) { - // Otherwise, we need to push the correct values to the vectors, which might trigger reallocations. - for (index_type i = lastRow + 1; i <= row; ++i) { - rowIndications.push_back(currentEntryCount); + // If the element is in the same row and column as the previous entry, we add them up. + if (row == lastRow && column == lastColumn) { + columnsAndValues.back().setColumn(column); + columnsAndValues.back().setValue(value); + } else { + // If we switched to another row, we have to adjust the missing entries in the row indices vector. + if (row != lastRow) { + // Otherwise, we need to push the correct values to the vectors, which might trigger reallocations. + for (index_type i = lastRow + 1; i <= row; ++i) { + rowIndications.push_back(currentEntryCount); + } + + lastRow = row; } - lastRow = row; - } - - lastColumn = column; - - // Finally, set the element and increase the current size. - columnsAndValues.emplace_back(column, value); - highestColumn = std::max(highestColumn, column); - ++currentEntryCount; - - // If we need to fix the row, do so now. - if (fixCurrentRow) { - // First, we sort according to columns. - std::sort(columnsAndValues.begin() + rowIndications.back(), columnsAndValues.end(), [] (storm::storage::MatrixEntry const& a, storm::storage::MatrixEntry const& b) { - return a.getColumn() < b.getColumn(); - }); + lastColumn = column; - // Then, we eliminate possible duplicate entries. - auto it = std::unique(columnsAndValues.begin() + rowIndications.back(), columnsAndValues.end(), [] (storm::storage::MatrixEntry const& a, storm::storage::MatrixEntry const& b) { - return a.getColumn() == b.getColumn(); - }); + // Finally, set the element and increase the current size. + columnsAndValues.emplace_back(column, value); + highestColumn = std::max(highestColumn, column); + ++currentEntryCount; - // Finally, remove the superfluous elements. - std::size_t elementsToRemove = std::distance(it, columnsAndValues.end()); - if (elementsToRemove > 0) { - STORM_LOG_WARN("Unordered insertion into matrix builder caused duplicate entries."); - currentEntryCount -= elementsToRemove; - columnsAndValues.resize(columnsAndValues.size() - elementsToRemove); + // If we need to fix the row, do so now. + if (fixCurrentRow) { + // First, we sort according to columns. + std::sort(columnsAndValues.begin() + rowIndications.back(), columnsAndValues.end(), [] (storm::storage::MatrixEntry const& a, storm::storage::MatrixEntry const& b) { + return a.getColumn() < b.getColumn(); + }); + + // Then, we eliminate possible duplicate entries. + auto it = std::unique(columnsAndValues.begin() + rowIndications.back(), columnsAndValues.end(), [] (storm::storage::MatrixEntry const& a, storm::storage::MatrixEntry const& b) { + return a.getColumn() == b.getColumn(); + }); + + // Finally, remove the superfluous elements. + std::size_t elementsToRemove = std::distance(it, columnsAndValues.end()); + if (elementsToRemove > 0) { + STORM_LOG_WARN("Unordered insertion into matrix builder caused duplicate entries."); + currentEntryCount -= elementsToRemove; + columnsAndValues.resize(columnsAndValues.size() - elementsToRemove); + } } } diff --git a/src/storm/storage/dd/BisimulationDecomposition.cpp b/src/storm/storage/dd/BisimulationDecomposition.cpp index a1698404a..81ad026af 100644 --- a/src/storm/storage/dd/BisimulationDecomposition.cpp +++ b/src/storm/storage/dd/BisimulationDecomposition.cpp @@ -82,12 +82,12 @@ namespace storm { } template - std::shared_ptr> BisimulationDecomposition::getQuotient() const { + std::shared_ptr> BisimulationDecomposition::getQuotient() const { STORM_LOG_THROW(this->status == Status::FixedPoint, storm::exceptions::InvalidOperationException, "Cannot extract quotient, because bisimulation decomposition was not completed."); STORM_LOG_TRACE("Starting quotient extraction."); QuotientExtractor extractor; - std::shared_ptr> quotient = extractor.extract(model, currentPartition); + std::shared_ptr> quotient = extractor.extract(model, currentPartition); STORM_LOG_TRACE("Quotient extraction done."); return quotient; diff --git a/src/storm/storage/dd/BisimulationDecomposition.h b/src/storm/storage/dd/BisimulationDecomposition.h index e0ec00d27..d13d6a114 100644 --- a/src/storm/storage/dd/BisimulationDecomposition.h +++ b/src/storm/storage/dd/BisimulationDecomposition.h @@ -33,7 +33,7 @@ namespace storm { /*! * Retrieves the quotient model after the bisimulation decomposition was computed. */ - std::shared_ptr> getQuotient() const; + std::shared_ptr> getQuotient() const; private: // The status of the computation. diff --git a/src/storm/storage/dd/DdMetaVariable.cpp b/src/storm/storage/dd/DdMetaVariable.cpp index 440612d7c..fda284391 100644 --- a/src/storm/storage/dd/DdMetaVariable.cpp +++ b/src/storm/storage/dd/DdMetaVariable.cpp @@ -49,6 +49,28 @@ namespace storm { return this->cube; } + template + std::vector DdMetaVariable::getIndices() const { + std::vector> indicesAndLevels = this->getIndicesAndLevels(); + std::sort(indicesAndLevels.begin(), indicesAndLevels.end(), [] (std::pair const& a, std::pair const& b) { return a.second < b.second; }); + + std::vector indices; + for (auto const& e : indicesAndLevels) { + indices.emplace_back(e.first); + } + + return indices; + } + + template + std::vector> DdMetaVariable::getIndicesAndLevels() const { + std::vector> indicesAndLevels; + for (auto const& v : ddVariables) { + indicesAndLevels.emplace_back(v.getIndex(), v.getLevel()); + } + return indicesAndLevels; + } + template uint64_t DdMetaVariable::getHighestLevel() const { uint64_t result = 0; diff --git a/src/storm/storage/dd/DdMetaVariable.h b/src/storm/storage/dd/DdMetaVariable.h index 9b9b736ed..c18f9fa44 100644 --- a/src/storm/storage/dd/DdMetaVariable.h +++ b/src/storm/storage/dd/DdMetaVariable.h @@ -78,6 +78,16 @@ namespace storm { */ uint64_t getHighestLevel() const; + /*! + * Retrieves the indices of the DD variables associated with this meta variable sorted by level. + */ + std::vector getIndices() const; + + /*! + * Retrieves the indices and levels of the DD variables associated with this meta variable. + */ + std::vector> getIndicesAndLevels() const; + private: /*! * Creates an integer meta variable with the given name and range bounds. diff --git a/src/storm/storage/dd/bisimulation/QuotientExtractor.cpp b/src/storm/storage/dd/bisimulation/QuotientExtractor.cpp index bef5d3fc3..a59eed3bd 100644 --- a/src/storm/storage/dd/bisimulation/QuotientExtractor.cpp +++ b/src/storm/storage/dd/bisimulation/QuotientExtractor.cpp @@ -6,6 +6,10 @@ #include "storm/models/symbolic/Ctmc.h" #include "storm/models/symbolic/StandardRewardModel.h" +#include "storm/models/sparse/Dtmc.h" +#include "storm/models/sparse/Ctmc.h" +#include "storm/models/sparse/StandardRewardModel.h" + #include "storm/storage/dd/bisimulation/PreservationInformation.h" #include "storm/settings/SettingsManager.h" @@ -13,10 +17,452 @@ #include "storm/utility/macros.h" #include "storm/exceptions/NotSupportedException.h" +#include "storm/storage/SparseMatrix.h" +#include "storm/storage/BitVector.h" + +#include + namespace storm { namespace dd { namespace bisimulation { + + template + class InternalSparseQuotientExtractor; + + template + class InternalSparseQuotientExtractorBase { + public: + InternalSparseQuotientExtractorBase(storm::dd::DdManager const& manager, std::set const& stateVariables) : manager(manager) { + for (auto const& variable : stateVariables) { + auto const& ddMetaVariable = manager.getMetaVariable(variable); + std::vector> indicesAndLevels = ddMetaVariable.getIndicesAndLevels(); + stateVariablesIndicesAndLevels.insert(stateVariablesIndicesAndLevels.end(), indicesAndLevels.begin(), indicesAndLevels.end()); + } + + // Sort the indices by their levels. + std::sort(stateVariablesIndicesAndLevels.begin(), stateVariablesIndicesAndLevels.end(), [] (std::pair const& a, std::pair const& b) { return a.second < b.second; } ); + } + + protected: + storm::storage::SparseMatrix createMatrixFromEntries(Partition const& partition) { + for (auto& row : entries) { + std::sort(row.begin(), row.end(), [] (storm::storage::MatrixEntry const& a, storm::storage::MatrixEntry const& b) { return a.getColumn() < b.getColumn(); } ); + } + + storm::storage::SparseMatrixBuilder builder(partition.getNumberOfBlocks(), partition.getNumberOfBlocks()); + uint64_t rowCounter = 0; + for (auto& row : entries) { + for (auto const& entry : row) { + builder.addNextValue(rowCounter, entry.getColumn(), entry.getValue()); + } + + // Free storage for row. + row.clear(); + row.shrink_to_fit(); + + ++rowCounter; + } + + return builder.build(); + } + + storm::dd::DdManager const& manager; + std::vector> stateVariablesIndicesAndLevels; + std::vector>> entries; + }; + template + class InternalSparseQuotientExtractor : public InternalSparseQuotientExtractorBase { + public: + InternalSparseQuotientExtractor(storm::dd::DdManager const& manager, std::set const& stateVariables) : InternalSparseQuotientExtractorBase(manager, stateVariables), ddman(this->manager.getInternalDdManager().getCuddManager().getManager()) { + // Intentionally left empty. + } + + storm::storage::SparseMatrix extractTransitionMatrix(storm::dd::Add const& transitionMatrix, Partition const& partition) { + STORM_LOG_ASSERT(partition.storedAsAdd(), "Expected partition stored as ADD."); + + // Create the number of rows necessary for the matrix. + this->entries.resize(partition.getNumberOfBlocks()); + + storm::storage::BitVector encoding(this->stateVariablesIndicesAndLevels.size()); + extractTransitionMatrixRec(transitionMatrix.getInternalAdd().getCuddDdNode(), partition.asAdd().getInternalAdd().getCuddDdNode(), partition.asAdd().getInternalAdd().getCuddDdNode(), 0, encoding); + + return this->createMatrixFromEntries(partition); + } + + storm::storage::BitVector extractStates(storm::dd::Bdd const& states, Partition const& partition) { + STORM_LOG_ASSERT(partition.storedAsAdd(), "Expected partition stored as ADD."); + + storm::storage::BitVector result(partition.getNumberOfBlocks()); + extractStatesRec(states.getInternalBdd().getCuddDdNode(), partition.asAdd().getInternalAdd().getCuddDdNode(), 0, result); + + return result; + } + + private: + uint64_t decodeBlockIndex(DdNode* blockEncoding) { + std::unique_ptr& blockCacheEntry = blockDecodeCache[blockEncoding]; + if (blockCacheEntry) { + return *blockCacheEntry; + } + + uint64_t result = 0; + uint64_t offset = 0; + while (blockEncoding != Cudd_ReadOne(ddman)) { + if (Cudd_T(blockEncoding) != Cudd_ReadZero(ddman)) { + blockEncoding = Cudd_T(blockEncoding); + result |= 1ull << offset; + } else { + blockEncoding = Cudd_E(blockEncoding); + } + ++offset; + } + + blockCacheEntry.reset(new uint64_t(result)); + + return result; + } + + void extractStatesRec(DdNode* statesNode, DdNode* partitionNode, uint64_t offset, storm::storage::BitVector& result) { + if (statesNode == Cudd_ReadLogicZero(ddman)) { + return; + } + + // Determine the levels in the DDs. + uint64_t statesVariable = Cudd_NodeReadIndex(statesNode); + uint64_t partitionVariable = Cudd_NodeReadIndex(partitionNode) - 1; + + // See how many variables we skipped. + while (offset < this->stateVariablesIndicesAndLevels.size() && statesVariable != this->stateVariablesIndicesAndLevels[offset].first && partitionVariable != this->stateVariablesIndicesAndLevels[offset].first) { + ++offset; + } + + if (offset == this->stateVariablesIndicesAndLevels.size()) { + result.set(decodeBlockIndex(partitionNode)); + return; + } + + uint64_t topVariable; + if (statesVariable == this->stateVariablesIndicesAndLevels[offset].first) { + topVariable = statesVariable; + } else { + topVariable = partitionVariable; + } + + DdNode* tStates = statesNode; + DdNode* eStates = statesNode; + bool negate = false; + if (topVariable == statesVariable) { + tStates = Cudd_T(statesNode); + eStates = Cudd_E(statesNode); + negate = Cudd_IsComplement(statesNode); + } + + DdNode* tPartition = partitionNode; + DdNode* ePartition = partitionNode; + if (topVariable == partitionVariable) { + tPartition = Cudd_T(partitionNode); + ePartition = Cudd_E(partitionNode); + } + + extractStatesRec(negate ? Cudd_Not(tStates) : tStates, tPartition, offset, result); + extractStatesRec(negate ? Cudd_Not(eStates) : eStates, ePartition, offset, result); + } + + void extractTransitionMatrixRec(DdNode* transitionMatrixNode, DdNode* sourcePartitionNode, DdNode* targetPartitionNode, uint64_t currentIndex, storm::storage::BitVector& sourceState) { + // For the empty DD, we do not need to add any entries. Note that the partition nodes cannot be zero + // as all states of the model have to be contained. + if (transitionMatrixNode == Cudd_ReadZero(ddman)) { + return; + } + + // If we have moved through all source variables, we must have arrived at a constant. + if (currentIndex == sourceState.size()) { + // Decode the source block. + uint64_t sourceBlockIndex = decodeBlockIndex(sourcePartitionNode); + + std::unique_ptr& sourceRepresentative = uniqueSourceRepresentative[sourceBlockIndex]; + if (sourceRepresentative && *sourceRepresentative != sourceState) { + // In this case, we have picked a different representative and must not record any entries now. + return; + } + + // Otherwise, we record the new representative. + sourceRepresentative.reset(new storm::storage::BitVector(sourceState)); + + // Decode the target block. + uint64_t targetBlockIndex = decodeBlockIndex(targetPartitionNode); + + this->entries[sourceBlockIndex].emplace_back(targetBlockIndex, Cudd_V(transitionMatrixNode)); + } else { + // Determine the levels in the DDs. + uint64_t transitionMatrixVariable = Cudd_NodeReadIndex(transitionMatrixNode); + uint64_t sourcePartitionVariable = Cudd_NodeReadIndex(sourcePartitionNode) - 1; + uint64_t targetPartitionVariable = Cudd_NodeReadIndex(targetPartitionNode) - 1; + + // Move through transition matrix. + DdNode* tt = transitionMatrixNode; + DdNode* te = transitionMatrixNode; + DdNode* et = transitionMatrixNode; + DdNode* ee = transitionMatrixNode; + if (transitionMatrixVariable == this->stateVariablesIndicesAndLevels[currentIndex].first) { + DdNode* t = Cudd_T(transitionMatrixNode); + DdNode* e = Cudd_E(transitionMatrixNode); + + uint64_t tVariable = Cudd_NodeReadIndex(t); + if (tVariable == this->stateVariablesIndicesAndLevels[currentIndex].first + 1) { + tt = Cudd_T(t); + te = Cudd_E(t); + } else { + tt = te = t; + } + + uint64_t eVariable = Cudd_NodeReadIndex(e); + if (eVariable == this->stateVariablesIndicesAndLevels[currentIndex].first + 1) { + et = Cudd_T(e); + ee = Cudd_E(e); + } else { + et = ee = e; + } + } else { + if (transitionMatrixVariable == this->stateVariablesIndicesAndLevels[currentIndex].first + 1) { + tt = Cudd_T(transitionMatrixNode); + te = Cudd_E(transitionMatrixNode); + } else { + tt = te = transitionMatrixNode; + } + } + + // Move through partition (for source state). + DdNode* sourceT; + DdNode* sourceE; + if (sourcePartitionVariable == this->stateVariablesIndicesAndLevels[currentIndex].first) { + sourceT = Cudd_T(sourcePartitionNode); + sourceE = Cudd_E(sourcePartitionNode); + } else { + sourceT = sourceE = sourcePartitionNode; + } + + // Move through partition (for target state). + DdNode* targetT; + DdNode* targetE; + if (targetPartitionVariable == this->stateVariablesIndicesAndLevels[currentIndex].first) { + targetT = Cudd_T(targetPartitionNode); + targetE = Cudd_E(targetPartitionNode); + } else { + targetT = targetE = targetPartitionNode; + } + + sourceState.set(currentIndex, true); + extractTransitionMatrixRec(tt, sourceT, targetT, currentIndex + 1, sourceState); + extractTransitionMatrixRec(te, sourceT, targetE, currentIndex + 1, sourceState); + + sourceState.set(currentIndex, false); + extractTransitionMatrixRec(et, sourceE, targetT, currentIndex + 1, sourceState); + extractTransitionMatrixRec(ee, sourceE, targetE, currentIndex + 1, sourceState); + } + } + + ::DdManager* ddman; + + spp::sparse_hash_map> uniqueSourceRepresentative; + spp::sparse_hash_map> blockDecodeCache; + }; + + template + class InternalSparseQuotientExtractor : public InternalSparseQuotientExtractorBase { + public: + InternalSparseQuotientExtractor(storm::dd::DdManager const& manager, std::set const& stateVariables) : InternalSparseQuotientExtractorBase(manager, stateVariables) { + // Intentionally left empty. + } + + storm::storage::SparseMatrix extractTransitionMatrix(storm::dd::Add const& transitionMatrix, Partition const& partition) { + STORM_LOG_ASSERT(partition.storedAsBdd(), "Expected partition stored as BDD."); + + // Create the number of rows necessary for the matrix. + this->entries.resize(partition.getNumberOfBlocks()); + + storm::storage::BitVector encoding(this->stateVariablesIndicesAndLevels.size()); + extractTransitionMatrixRec(transitionMatrix.getInternalAdd().getSylvanMtbdd().GetMTBDD(), partition.asBdd().getInternalBdd().getSylvanBdd().GetBDD(), partition.asBdd().getInternalBdd().getSylvanBdd().GetBDD(), 0, encoding); + + return this->createMatrixFromEntries(partition); + } + + storm::storage::BitVector extractStates(storm::dd::Bdd const& states, Partition const& partition) { + STORM_LOG_ASSERT(partition.storedAsBdd(), "Expected partition stored as BDD."); + + storm::storage::BitVector result(partition.getNumberOfBlocks()); + extractStatesRec(states.getInternalBdd().getSylvanBdd().GetBDD(), partition.asBdd().getInternalBdd().getSylvanBdd().GetBDD(), 0, result); + + return result; + } + + private: + uint64_t decodeBlockIndex(BDD blockEncoding) { + std::unique_ptr& blockCacheEntry = blockDecodeCache[blockEncoding]; + if (blockCacheEntry) { + return *blockCacheEntry; + } + + uint64_t result = 0; + uint64_t offset = 0; + while (blockEncoding != sylvan_true) { + if (sylvan_high(blockEncoding) != sylvan_false) { + blockEncoding = sylvan_high(blockEncoding); + result |= 1ull << offset; + } else { + blockEncoding = sylvan_low(blockEncoding); + } + ++offset; + } + + blockCacheEntry.reset(new uint64_t(result)); + + return result; + } + + void extractStatesRec(BDD statesNode, BDD partitionNode, uint64_t offset, storm::storage::BitVector& result) { + if (statesNode == sylvan_false) { + return; + } + + // Determine the levels in the DDs. + uint64_t statesVariable = sylvan_isconst(statesNode) ? 0xffffffff : sylvan_var(statesNode); + uint64_t partitionVariable = sylvan_var(partitionNode) - 1; + + // See how many variables we skipped. + while (offset < this->stateVariablesIndicesAndLevels.size() && statesVariable != this->stateVariablesIndicesAndLevels[offset].first && partitionVariable != this->stateVariablesIndicesAndLevels[offset].first) { + ++offset; + } + + if (offset == this->stateVariablesIndicesAndLevels.size()) { + result.set(decodeBlockIndex(partitionNode)); + return; + } + + uint64_t topVariable; + if (statesVariable == this->stateVariablesIndicesAndLevels[offset].first) { + topVariable = statesVariable; + } else { + topVariable = partitionVariable; + } + + BDD tStates = statesNode; + BDD eStates = statesNode; + if (topVariable == statesVariable) { + tStates = sylvan_high(statesNode); + eStates = sylvan_low(statesNode); + } + + BDD tPartition = partitionNode; + BDD ePartition = partitionNode; + if (topVariable == partitionVariable) { + tPartition = sylvan_high(partitionNode); + ePartition = sylvan_low(partitionNode); + } + + extractStatesRec(tStates, tPartition, offset, result); + extractStatesRec(eStates, ePartition, offset, result); + } + + void extractTransitionMatrixRec(MTBDD transitionMatrixNode, BDD sourcePartitionNode, BDD targetPartitionNode, uint64_t currentIndex, storm::storage::BitVector& sourceState) { + // For the empty DD, we do not need to add any entries. Note that the partition nodes cannot be zero + // as all states of the model have to be contained. + if (mtbdd_iszero(transitionMatrixNode)) { + return; + } + + // If we have moved through all source variables, we must have arrived at a constant. + if (currentIndex == sourceState.size()) { + // Decode the source block. + uint64_t sourceBlockIndex = decodeBlockIndex(sourcePartitionNode); + + std::unique_ptr& sourceRepresentative = uniqueSourceRepresentative[sourceBlockIndex]; + if (sourceRepresentative && *sourceRepresentative != sourceState) { + // In this case, we have picked a different representative and must not record any entries now. + return; + } + + // Otherwise, we record the new representative. + sourceRepresentative.reset(new storm::storage::BitVector(sourceState)); + + // Decode the target block. + uint64_t targetBlockIndex = decodeBlockIndex(targetPartitionNode); + + this->entries[sourceBlockIndex].emplace_back(targetBlockIndex, storm::dd::InternalAdd::getValue(transitionMatrixNode)); + } else { + // Determine the levels in the DDs. + uint64_t transitionMatrixVariable = sylvan_isconst(transitionMatrixNode) ? 0xffffffff : sylvan_var(transitionMatrixNode); + uint64_t sourcePartitionVariable = sylvan_var(sourcePartitionNode) - 1; + uint64_t targetPartitionVariable = sylvan_var(targetPartitionNode) - 1; + + // Move through transition matrix. + MTBDD tt = transitionMatrixNode; + MTBDD te = transitionMatrixNode; + MTBDD et = transitionMatrixNode; + MTBDD ee = transitionMatrixNode; + if (transitionMatrixVariable == this->stateVariablesIndicesAndLevels[currentIndex].first) { + MTBDD t = sylvan_high(transitionMatrixNode); + MTBDD e = sylvan_low(transitionMatrixNode); + + uint64_t tVariable = sylvan_isconst(t) ? 0xffffffff : sylvan_var(t); + if (tVariable == this->stateVariablesIndicesAndLevels[currentIndex].first + 1) { + tt = sylvan_high(t); + te = sylvan_low(t); + } else { + tt = te = t; + } + + uint64_t eVariable = sylvan_isconst(e) ? 0xffffffff : sylvan_var(e); + if (eVariable == this->stateVariablesIndicesAndLevels[currentIndex].first + 1) { + et = sylvan_high(e); + ee = sylvan_low(e); + } else { + et = ee = e; + } + } else { + if (transitionMatrixVariable == this->stateVariablesIndicesAndLevels[currentIndex].first + 1) { + tt = sylvan_high(transitionMatrixNode); + te = sylvan_low(transitionMatrixNode); + } else { + tt = te = transitionMatrixNode; + } + } + + // Move through partition (for source state). + MTBDD sourceT; + MTBDD sourceE; + if (sourcePartitionVariable == this->stateVariablesIndicesAndLevels[currentIndex].first) { + sourceT = sylvan_high(sourcePartitionNode); + sourceE = sylvan_low(sourcePartitionNode); + } else { + sourceT = sourceE = sourcePartitionNode; + } + + // Move through partition (for target state). + MTBDD targetT; + MTBDD targetE; + if (targetPartitionVariable == this->stateVariablesIndicesAndLevels[currentIndex].first) { + targetT = sylvan_high(targetPartitionNode); + targetE = sylvan_low(targetPartitionNode); + } else { + targetT = targetE = targetPartitionNode; + } + + sourceState.set(currentIndex, true); + extractTransitionMatrixRec(tt, sourceT, targetT, currentIndex + 1, sourceState); + extractTransitionMatrixRec(te, sourceT, targetE, currentIndex + 1, sourceState); + + sourceState.set(currentIndex, false); + extractTransitionMatrixRec(et, sourceE, targetT, currentIndex + 1, sourceState); + extractTransitionMatrixRec(ee, sourceE, targetE, currentIndex + 1, sourceState); + } + } + + spp::sparse_hash_map> uniqueSourceRepresentative; + spp::sparse_hash_map> blockDecodeCache; + }; + template QuotientExtractor::QuotientExtractor() : useRepresentatives(false) { auto const& settings = storm::settings::getModule(); @@ -25,9 +471,9 @@ namespace storm { } template - std::shared_ptr> QuotientExtractor::extract(storm::models::symbolic::Model const& model, Partition const& partition) { + std::shared_ptr> QuotientExtractor::extract(storm::models::symbolic::Model const& model, Partition const& partition) { auto start = std::chrono::high_resolution_clock::now(); - std::shared_ptr> result; + std::shared_ptr> result; if (quotientFormat == storm::settings::modules::BisimulationSettings::QuotientFormat::Sparse) { result = extractSparseQuotient(model, partition); } else { @@ -35,12 +481,51 @@ namespace storm { } auto end = std::chrono::high_resolution_clock::now(); STORM_LOG_TRACE("Quotient extraction completed in " << std::chrono::duration_cast(end - start).count() << "ms."); + + STORM_LOG_THROW(result, storm::exceptions::NotSupportedException, "Quotient could not be extracted."); + return result; } template - std::shared_ptr> QuotientExtractor::extractSparseQuotient(storm::models::symbolic::Model const& model, Partition const& partition) { - return nullptr; + std::shared_ptr> QuotientExtractor::extractSparseQuotient(storm::models::symbolic::Model const& model, Partition const& partition) { + InternalSparseQuotientExtractor sparseExtractor(model.getManager(), model.getRowVariables()); + storm::storage::SparseMatrix quotientTransitionMatrix = sparseExtractor.extractTransitionMatrix(model.getTransitionMatrix(), partition); + + std::cout << "Matrix has " << quotientTransitionMatrix.getEntryCount() << " entries" << std::endl; + + storm::models::sparse::StateLabeling quotientStateLabeling(partition.getNumberOfBlocks()); + quotientStateLabeling.addLabel("init", sparseExtractor.extractStates(model.getInitialStates(), partition)); + std::cout << "init: " << quotientStateLabeling.getStates("init").getNumberOfSetBits() << std::endl; + quotientStateLabeling.addLabel("deadlock", sparseExtractor.extractStates(model.getDeadlockStates(), partition)); + std::cout << "deadlock: " << quotientStateLabeling.getStates("init").getNumberOfSetBits() << std::endl; + + for (auto const& label : partition.getPreservationInformation().getLabels()) { + quotientStateLabeling.addLabel(label, sparseExtractor.extractStates(model.getStates(label), partition)); + std::cout << label << ": " << quotientStateLabeling.getStates(label).getNumberOfSetBits() << std::endl; + } + for (auto const& expression : partition.getPreservationInformation().getExpressions()) { + std::stringstream stream; + stream << expression; + std::string expressionAsString = stream.str(); + + if (quotientStateLabeling.containsLabel(expressionAsString)) { + STORM_LOG_WARN("Duplicate label '" << expressionAsString << "', dropping second label definition."); + } else { + quotientStateLabeling.addLabel(stream.str(), sparseExtractor.extractStates(model.getStates(expression), partition)); + std::cout << stream.str() << ": " << quotientStateLabeling.getStates(stream.str()).getNumberOfSetBits() << std::endl; + } + } + + + std::shared_ptr> result; + if (model.getType() == storm::models::ModelType::Dtmc) { + result = std::make_shared>(std::move(quotientTransitionMatrix), std::move(quotientStateLabeling)); + } else if (model.getType() == storm::models::ModelType::Ctmc) { + result = std::make_shared>(std::move(quotientTransitionMatrix), std::move(quotientStateLabeling)); + } + + return result; } template @@ -65,10 +550,14 @@ namespace storm { } storm::dd::Add partitionAsAdd = partitionAsBdd.template toAdd(); + auto start = std::chrono::high_resolution_clock::now(); storm::dd::Add quotientTransitionMatrix = model.getTransitionMatrix().multiplyMatrix(partitionAsAdd.renameVariables(blockVariableSet, blockPrimeVariableSet), model.getColumnVariables()); quotientTransitionMatrix = quotientTransitionMatrix.multiplyMatrix(partitionAsAdd.renameVariables(model.getColumnVariables(), model.getRowVariables()), model.getRowVariables()); + auto end = std::chrono::high_resolution_clock::now(); + STORM_LOG_TRACE("Quotient transition matrix extracted in " << std::chrono::duration_cast(end - start).count() << "ms."); storm::dd::Bdd quotientTransitionMatrixBdd = quotientTransitionMatrix.notZero(); + start = std::chrono::high_resolution_clock::now(); storm::dd::Bdd partitionAsBddOverRowVariables = partitionAsBdd.renameVariables(model.getColumnVariables(), model.getRowVariables()); storm::dd::Bdd reachableStates = partitionAsBdd.existsAbstract(model.getColumnVariables()); storm::dd::Bdd initialStates = (model.getInitialStates() && partitionAsBddOverRowVariables).existsAbstract(model.getRowVariables()); @@ -90,7 +579,9 @@ namespace storm { preservedLabelBdds.emplace(stream.str(), (model.getStates(expression) && partitionAsBddOverRowVariables).existsAbstract(model.getRowVariables())); } } - + end = std::chrono::high_resolution_clock::now(); + STORM_LOG_TRACE("Quotient labels extracted in " << std::chrono::duration_cast(end - start).count() << "ms."); + if (modelType == storm::models::ModelType::Dtmc) { return std::shared_ptr>(new storm::models::symbolic::Dtmc(model.getManager().asSharedPointer(), reachableStates, initialStates, deadlockStates, quotientTransitionMatrix, blockVariableSet, blockPrimeVariableSet, blockMetaVariablePairs, preservedLabelBdds, {})); } else { diff --git a/src/storm/storage/dd/bisimulation/QuotientExtractor.h b/src/storm/storage/dd/bisimulation/QuotientExtractor.h index eb6cbef4b..14b518016 100644 --- a/src/storm/storage/dd/bisimulation/QuotientExtractor.h +++ b/src/storm/storage/dd/bisimulation/QuotientExtractor.h @@ -5,6 +5,7 @@ #include "storm/storage/dd/DdType.h" #include "storm/models/symbolic/Model.h" +#include "storm/models/sparse/Model.h" #include "storm/storage/dd/bisimulation/Partition.h" @@ -19,10 +20,10 @@ namespace storm { public: QuotientExtractor(); - std::shared_ptr> extract(storm::models::symbolic::Model const& model, Partition const& partition); + std::shared_ptr> extract(storm::models::symbolic::Model const& model, Partition const& partition); private: - std::shared_ptr> extractSparseQuotient(storm::models::symbolic::Model const& model, Partition const& partition); + std::shared_ptr> extractSparseQuotient(storm::models::symbolic::Model const& model, Partition const& partition); std::shared_ptr> extractDdQuotient(storm::models::symbolic::Model const& model, Partition const& partition); std::shared_ptr> extractQuotientUsingBlockVariables(storm::models::symbolic::Model const& model, Partition const& partition); diff --git a/src/storm/storage/dd/cudd/InternalCuddAdd.h b/src/storm/storage/dd/cudd/InternalCuddAdd.h index 8e7c1a34f..b37499898 100644 --- a/src/storm/storage/dd/cudd/InternalCuddAdd.h +++ b/src/storm/storage/dd/cudd/InternalCuddAdd.h @@ -595,7 +595,6 @@ namespace storm { InternalDdManager const& getInternalDdManager() const; - private: /*! * Retrieves the CUDD ADD object associated with this ADD. * @@ -610,6 +609,7 @@ namespace storm { */ DdNode* getCuddDdNode() const; + private: /*! * Performs a recursive step to perform the given function between the given DD-based vector and the given * explicit vector. diff --git a/src/storm/storage/dd/cudd/InternalCuddBdd.h b/src/storm/storage/dd/cudd/InternalCuddBdd.h index 3ca4f47d7..005404e8e 100644 --- a/src/storm/storage/dd/cudd/InternalCuddBdd.h +++ b/src/storm/storage/dd/cudd/InternalCuddBdd.h @@ -387,7 +387,6 @@ namespace storm { friend struct std::hash>; - private: /*! * Retrieves the CUDD BDD object associated with this DD. * @@ -402,6 +401,7 @@ namespace storm { */ DdNode* getCuddDdNode() const; + private: /*! * Builds a BDD representing the values that make the given filter function evaluate to true. * diff --git a/src/storm/storage/dd/sylvan/InternalSylvanAdd.h b/src/storm/storage/dd/sylvan/InternalSylvanAdd.h index 86f76ed3f..8ecc880e5 100644 --- a/src/storm/storage/dd/sylvan/InternalSylvanAdd.h +++ b/src/storm/storage/dd/sylvan/InternalSylvanAdd.h @@ -605,6 +605,13 @@ namespace storm { */ sylvan::Mtbdd getSylvanMtbdd() const; + /*! + * Retrieves the value of the given node (that must be a leaf). + * + * @return The value of the leaf. + */ + static ValueType getValue(MTBDD const& node); + private: /*! * Recursively builds the ODD from an ADD. @@ -733,13 +740,6 @@ namespace storm { static MTBDD getLeaf(storm::RationalFunction const& value); #endif - /*! - * Retrieves the value of the given node (that must be a leaf). - * - * @return The value of the leaf. - */ - static ValueType getValue(MTBDD const& node); - // The manager responsible for this MTBDD. InternalDdManager const* ddManager; diff --git a/src/storm/transformer/SymbolicToSparseTransformer.cpp b/src/storm/transformer/SymbolicToSparseTransformer.cpp index ec026968f..448bbcd39 100644 --- a/src/storm/transformer/SymbolicToSparseTransformer.cpp +++ b/src/storm/transformer/SymbolicToSparseTransformer.cpp @@ -29,6 +29,7 @@ namespace storm { template std::shared_ptr> SymbolicDtmcToSparseDtmcTransformer::translate(storm::models::symbolic::Dtmc const& symbolicDtmc) { + this->odd = symbolicDtmc.getReachableStates().createOdd(); storm::storage::SparseMatrix transitionMatrix = symbolicDtmc.getTransitionMatrix().toMatrix(this->odd, this->odd); std::unordered_map> rewardModels; diff --git a/src/storm/utility/storm.h b/src/storm/utility/storm.h index a913917be..614759395 100644 --- a/src/storm/utility/storm.h +++ b/src/storm/utility/storm.h @@ -194,7 +194,7 @@ namespace storm { if (storm::settings::getModule().isBisimulationSet()) { storm::dd::BisimulationDecomposition decomposition(*result, formulas); decomposition.compute(); - result = decomposition.getQuotient(); + result = std::dynamic_pointer_cast>(decomposition.getQuotient()); } return result;