diff --git a/src/storm/storage/dd/BisimulationDecomposition.cpp b/src/storm/storage/dd/BisimulationDecomposition.cpp index 89746b5e9..e7b27efaf 100644 --- a/src/storm/storage/dd/BisimulationDecomposition.cpp +++ b/src/storm/storage/dd/BisimulationDecomposition.cpp @@ -1,9 +1,11 @@ #include "storm/storage/dd/BisimulationDecomposition.h" -#include "storm/storage/dd/bisimulation/SignatureComputer.h" -#include "storm/storage/dd/bisimulation/SignatureRefiner.h" +#include "storm/storage/dd/bisimulation/PartitionRefiner.h" + #include "storm/storage/dd/bisimulation/QuotientExtractor.h" +#include "storm/models/symbolic/Model.h" + #include "storm/utility/macros.h" #include "storm/exceptions/InvalidOperationException.h" #include "storm/exceptions/NotSupportedException.h" @@ -24,80 +26,42 @@ namespace storm { } template - BisimulationDecomposition::BisimulationDecomposition(storm::models::symbolic::Model const& model, Partition const& initialPartition) : model(model), currentPartition(initialPartition) { + BisimulationDecomposition::BisimulationDecomposition(storm::models::symbolic::Model const& model, Partition const& initialPartition) : model(model), refiner(std::make_unique>(model, initialPartition)) { STORM_LOG_THROW(!model.hasRewardModel(), storm::exceptions::NotSupportedException, "Symbolic bisimulation currently does not support preserving rewards."); } + template + BisimulationDecomposition::~BisimulationDecomposition() = default; + template void BisimulationDecomposition::compute(bisimulation::SignatureMode const& mode) { - this->status = Status::InComputation; - auto start = std::chrono::high_resolution_clock::now(); - std::chrono::high_resolution_clock::duration totalSignatureTime(0); - std::chrono::high_resolution_clock::duration totalRefinementTime(0); + STORM_LOG_ASSERT(refiner, "No suitable refiner."); - STORM_LOG_TRACE("Initial partition has " << currentPartition.getNumberOfBlocks() << " blocks."); + STORM_LOG_TRACE("Initial partition has " << refiner->getStatePartition().getNumberOfBlocks() << " blocks."); #ifndef NDEBUG - STORM_LOG_TRACE("Initial partition has " << currentPartition.getNodeCount() << " nodes."); + STORM_LOG_TRACE("Initial partition has " << refiner->getStatePartition().getNodeCount() << " nodes."); #endif - - SignatureRefiner refiner(model.getManager(), currentPartition.getBlockVariable(), model.getRowVariables()); - SignatureComputer signatureComputer(model, mode); - bool done = false; + + auto start = std::chrono::high_resolution_clock::now(); uint64_t iterations = 0; - while (!done) { + bool refined = true; + while (refined) { + refined = refiner->refine(mode); ++iterations; - auto iterationStart = std::chrono::high_resolution_clock::now(); - - std::chrono::milliseconds::rep signatureTime = 0; - std::chrono::milliseconds::rep refinementTime = 0; - - Partition newPartition; - for (uint64_t index = 0, end = signatureComputer.getNumberOfSignatures(); index < end; ++index) { - auto signatureStart = std::chrono::high_resolution_clock::now(); - Signature signature = signatureComputer.compute(currentPartition, index); - auto signatureEnd = std::chrono::high_resolution_clock::now(); - totalSignatureTime += (signatureEnd - signatureStart); - STORM_LOG_DEBUG("Signature " << iterations << "[" << index << "] DD has " << signature.getSignatureAdd().getNodeCount() << " nodes and partition DD has " << currentPartition.getNodeCount() << " nodes."); - - auto refinementStart = std::chrono::high_resolution_clock::now(); - newPartition = refiner.refine(currentPartition, signature); - auto refinementEnd = std::chrono::high_resolution_clock::now(); - totalRefinementTime += (refinementEnd - refinementStart); - - signatureTime += std::chrono::duration_cast(signatureEnd - signatureStart).count(); - refinementTime = std::chrono::duration_cast(refinementEnd - refinementStart).count(); - - // Potentially exit early in case we have refined the partition already. - if (newPartition.getNumberOfBlocks() > currentPartition.getNumberOfBlocks()) { - break; - } - } - - auto iterationTime = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - iterationStart).count(); - - STORM_LOG_DEBUG("Iteration " << iterations << " produced " << newPartition.getNumberOfBlocks() << " blocks and was completed in " << iterationTime << "ms (signature: " << signatureTime << "ms, refinement: " << refinementTime << "ms)."); - - if (currentPartition == newPartition) { - done = true; - } else { - currentPartition = newPartition; - } + STORM_LOG_TRACE("After iteration " << iterations << " partition has " << refiner->getStatePartition().getNumberOfBlocks() << " blocks."); } - - this->status = Status::FixedPoint; auto end = std::chrono::high_resolution_clock::now(); - auto totalSignatureTimeInMilliseconds = std::chrono::duration_cast(totalSignatureTime).count(); - auto totalRefinementTimeInMilliseconds = std::chrono::duration_cast(totalRefinementTime).count(); - STORM_LOG_DEBUG("Partition refinement completed in " << std::chrono::duration_cast(end - start).count() << "ms (" << iterations << " iterations, signature: " << totalSignatureTimeInMilliseconds << "ms, refinement: " << totalRefinementTimeInMilliseconds << "ms)."); + + STORM_LOG_DEBUG("Partition refinement completed in " << std::chrono::duration_cast(end - start).count() << "ms (" << iterations << " iterations)."); } template 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_THROW(this->refiner->getStatus() == 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, refiner->getStatePartition()); 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 3650c58ed..85bd1a48b 100644 --- a/src/storm/storage/dd/BisimulationDecomposition.h +++ b/src/storm/storage/dd/BisimulationDecomposition.h @@ -5,27 +5,39 @@ #include "storm/storage/dd/DdType.h" #include "storm/storage/bisimulation/BisimulationType.h" -#include "storm/storage/dd/bisimulation/Partition.h" #include "storm/storage/dd/bisimulation/SignatureMode.h" -#include "storm/models/symbolic/Model.h" - #include "storm/logic/Formula.h" namespace storm { + namespace models { + template + class Model; + + namespace symbolic { + template + class Model; + } + } + namespace dd { + namespace bisimulation { + template + class Partition; + + template + class PartitionRefiner; + } template class BisimulationDecomposition { public: - enum class Status { - Initialized, InComputation, FixedPoint - }; - BisimulationDecomposition(storm::models::symbolic::Model const& model, storm::storage::BisimulationType const& bisimulationType); BisimulationDecomposition(storm::models::symbolic::Model const& model, std::vector> const& formulas, storm::storage::BisimulationType const& bisimulationType); BisimulationDecomposition(storm::models::symbolic::Model const& model, bisimulation::Partition const& initialPartition); + ~BisimulationDecomposition(); + /*! * Computes the decomposition. */ @@ -37,14 +49,11 @@ namespace storm { std::shared_ptr> getQuotient() const; private: - // The status of the computation. - Status status; - // The model for which to compute the bisimulation decomposition. storm::models::symbolic::Model const& model; - // The current partition in the partition refinement process. Initially set to the initial partition. - bisimulation::Partition currentPartition; + // The refiner to use. + std::unique_ptr> refiner; }; } diff --git a/src/storm/storage/dd/bisimulation/PartitionRefiner.cpp b/src/storm/storage/dd/bisimulation/PartitionRefiner.cpp new file mode 100644 index 000000000..d2061ee07 --- /dev/null +++ b/src/storm/storage/dd/bisimulation/PartitionRefiner.cpp @@ -0,0 +1,78 @@ +#include "storm/storage/dd/bisimulation/PartitionRefiner.h" + +namespace storm { + namespace dd { + namespace bisimulation { + + template + PartitionRefiner::PartitionRefiner(storm::models::symbolic::Model const& model, Partition const& initialStatePartition) : status(Status::Initialized), refinements(0), statePartition(initialStatePartition), signatureComputer(model), signatureRefiner(model.getManager(), statePartition.getBlockVariable(), model.getRowVariables()) { + // Intentionally left empty. + } + + template + bool PartitionRefiner::refine(bisimulation::SignatureMode const& mode) { + this->status = Status::InComputation; + + this->signatureComputer.setSignatureMode(mode); + auto start = std::chrono::high_resolution_clock::now(); + + std::chrono::milliseconds::rep signatureTime = 0; + std::chrono::milliseconds::rep refinementTime = 0; + + bool alreadyRefined = false; + uint64_t index = 0; + Partition newStatePartition; + auto signatureIterator = signatureComputer.compute(statePartition); + while (signatureIterator.hasNext() && !alreadyRefined) { + auto signatureStart = std::chrono::high_resolution_clock::now(); + auto signature = signatureIterator.next(); + auto signatureEnd = std::chrono::high_resolution_clock::now(); + totalSignatureTime += (signatureEnd - signatureStart); + STORM_LOG_DEBUG("Signature " << refinements << "[" << index << "] DD has " << signature.getSignatureAdd().getNodeCount() << " nodes."); + + auto refinementStart = std::chrono::high_resolution_clock::now(); + newStatePartition = signatureRefiner.refine(statePartition, signature); + auto refinementEnd = std::chrono::high_resolution_clock::now(); + totalRefinementTime += (refinementEnd - refinementStart); + + signatureTime += std::chrono::duration_cast(signatureEnd - signatureStart).count(); + refinementTime = std::chrono::duration_cast(refinementEnd - refinementStart).count(); + + // Potentially exit early in case we have refined the partition already. + if (newStatePartition.getNumberOfBlocks() > statePartition.getNumberOfBlocks()) { + alreadyRefined = true; + } + } + + auto totalTimeInRefinement = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + ++refinements; + STORM_LOG_DEBUG("Refinement " << refinements << " produced " << newStatePartition.getNumberOfBlocks() << " blocks and was completed in " << totalTimeInRefinement << "ms (signature: " << signatureTime << "ms, refinement: " << refinementTime << "ms)."); + + if (statePartition == newStatePartition) { + this->status = Status::FixedPoint; + return false; + } else { + this->statePartition = newStatePartition; + return true; + } + } + + template + Partition const& PartitionRefiner::getStatePartition() const { + return statePartition; + } + + template + Status PartitionRefiner::getStatus() const { + return status; + } + + template class PartitionRefiner; + + template class PartitionRefiner; + template class PartitionRefiner; + template class PartitionRefiner; + + } + } +} diff --git a/src/storm/storage/dd/bisimulation/Signature.h b/src/storm/storage/dd/bisimulation/Signature.h index 437839d54..b355b3021 100644 --- a/src/storm/storage/dd/bisimulation/Signature.h +++ b/src/storm/storage/dd/bisimulation/Signature.h @@ -11,6 +11,7 @@ namespace storm { template class Signature { public: + Signature() = default; Signature(storm::dd::Add const& signatureAdd); storm::dd::Add const& getSignatureAdd() const; diff --git a/src/storm/storage/dd/bisimulation/SignatureComputer.cpp b/src/storm/storage/dd/bisimulation/SignatureComputer.cpp index f7606115f..6eca5cf06 100644 --- a/src/storm/storage/dd/bisimulation/SignatureComputer.cpp +++ b/src/storm/storage/dd/bisimulation/SignatureComputer.cpp @@ -2,36 +2,71 @@ #include "storm/storage/dd/DdManager.h" +#include "storm/utility/macros.h" +#include "storm/exceptions/OutOfRangeException.h" + namespace storm { namespace dd { namespace bisimulation { template - SignatureComputer::SignatureComputer(storm::models::symbolic::Model const& model, SignatureMode const& mode) : model(model), transitionMatrix(model.getTransitionMatrix()), mode(mode) { + SignatureIterator::SignatureIterator(SignatureComputer const& signatureComputer, Partition const& partition) : signatureComputer(signatureComputer), partition(partition), position(0) { + // Intentionally left empty. + } + + template + bool SignatureIterator::hasNext() const { + if (signatureComputer.getSignatureMode() == SignatureMode::Eager) { + return position < 1; + } else { + return position < 2; + } + } + + template + Signature SignatureIterator::next() { + auto mode = signatureComputer.getSignatureMode(); + STORM_LOG_THROW((mode == SignatureMode::Eager && position < 1) || (mode == SignatureMode::Lazy && position < 2), storm::exceptions::OutOfRangeException, "Iterator is out of range."); + Signature result; + + if (mode == SignatureMode::Eager || position == 1) { + result = signatureComputer.getFullSignature(partition); + } else if (position == 0) { + result = signatureComputer.getQualitativeSignature(partition); + } + + ++position; + return result; + } + + template + SignatureComputer::SignatureComputer(storm::models::symbolic::Model const& model, SignatureMode const& mode) : model(model), transitionMatrix(model.getTransitionMatrix()) { if (DdType == storm::dd::DdType::Sylvan) { this->transitionMatrix = this->transitionMatrix.notZero().ite(this->transitionMatrix, this->transitionMatrix.getDdManager().template getAddUndefined()); } - if (mode == SignatureMode::Lazy) { + this->setSignatureMode(mode); + } + + template + SignatureIterator SignatureComputer::compute(Partition const& partition) { + return SignatureIterator(*this, partition); + } + + template + void SignatureComputer::setSignatureMode(SignatureMode const& newMode) { + if (newMode == SignatureMode::Lazy && !transitionMatrix01) { if (DdType == storm::dd::DdType::Sylvan) { this->transitionMatrix01 = model.getQualitativeTransitionMatrix().ite(this->transitionMatrix.getDdManager().template getAddOne(), this->transitionMatrix.getDdManager().template getAddUndefined()); } else { this->transitionMatrix01 = model.getQualitativeTransitionMatrix().template toAdd(); } } + this->mode = newMode; } template - uint64_t SignatureComputer::getNumberOfSignatures() const { - return this->mode == SignatureMode::Lazy ? 2 : 1; - } - - template - Signature SignatureComputer::compute(Partition const& partition, uint64_t index) { - if (mode == SignatureMode::Lazy && index == 1) { - return getQualitativeSignature(partition); - } else { - return getFullSignature(partition); - } + SignatureMode const& SignatureComputer::getSignatureMode() const { + return mode; } template @@ -45,15 +80,20 @@ namespace storm { template Signature SignatureComputer::getQualitativeSignature(Partition const& partition) const { + STORM_LOG_ASSERT(this->transitionMatrix01, "Need qualitative transition matrix for this step."); if (partition.storedAsBdd()) { - return Signature(this->transitionMatrix01.multiplyMatrix(partition.asBdd(), model.getColumnVariables())); + return Signature(this->transitionMatrix01.get().multiplyMatrix(partition.asBdd(), model.getColumnVariables())); } else { - return Signature(this->transitionMatrix01.multiplyMatrix(partition.asAdd(), model.getColumnVariables())); + return Signature(this->transitionMatrix01.get().multiplyMatrix(partition.asAdd(), model.getColumnVariables())); } } + + template class SignatureIterator; + template class SignatureIterator; + template class SignatureIterator; + template class SignatureIterator; template class SignatureComputer; - template class SignatureComputer; template class SignatureComputer; template class SignatureComputer; diff --git a/src/storm/storage/dd/bisimulation/SignatureComputer.h b/src/storm/storage/dd/bisimulation/SignatureComputer.h index 9b6177c1e..426d9e329 100644 --- a/src/storm/storage/dd/bisimulation/SignatureComputer.h +++ b/src/storm/storage/dd/bisimulation/SignatureComputer.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "storm/storage/dd/DdType.h" #include "storm/storage/dd/bisimulation/Signature.h" @@ -11,21 +13,48 @@ namespace storm { namespace dd { namespace bisimulation { + + template + class SignatureComputer; + template + class SignatureIterator { + public: + SignatureIterator(SignatureComputer const& signatureComputer, Partition const& partition); + + bool hasNext() const; + + Signature next(); + + private: + // The signature computer to use. + SignatureComputer const& signatureComputer; + + // The current partition. + Partition const& partition; + + // The position in the enumeration. + uint64_t position; + }; + template class SignatureComputer { public: - SignatureComputer(storm::models::symbolic::Model const& model, SignatureMode const& mode = SignatureMode::Eager); + friend class SignatureIterator; - Signature compute(Partition const& partition, uint64_t index = 0); + SignatureComputer(storm::models::symbolic::Model const& model, SignatureMode const& mode = SignatureMode::Eager); - uint64_t getNumberOfSignatures() const; + void setSignatureMode(SignatureMode const& newMode); + + SignatureIterator compute(Partition const& partition); private: Signature getFullSignature(Partition const& partition) const; Signature getQualitativeSignature(Partition const& partition) const; + SignatureMode const& getSignatureMode() const; + storm::models::symbolic::Model const& model; // The transition matrix to use for the signature computation. @@ -35,7 +64,7 @@ namespace storm { SignatureMode mode; // Only used when using lazy signatures is enabled. - storm::dd::Add transitionMatrix01; + boost::optional> transitionMatrix01; }; } diff --git a/src/storm/storage/dd/bisimulation/Status.h b/src/storm/storage/dd/bisimulation/Status.h new file mode 100644 index 000000000..532f61d92 --- /dev/null +++ b/src/storm/storage/dd/bisimulation/Status.h @@ -0,0 +1,13 @@ +#pragma once + +namespace storm { + namespace dd { + namespace bisimulation { + + enum class Status { + Initialized, InComputation, FixedPoint + }; + + } + } +}