25 changed files with 769 additions and 49 deletions
-
1resources/3rdparty/CMakeLists.txt
-
2resources/3rdparty/sylvan/src/storm_wrapper.cpp
-
8resources/3rdparty/sylvan/src/sylvan_storm_rational_function.c
-
8resources/3rdparty/sylvan/src/sylvan_storm_rational_number.c
-
6resources/examples/testfiles/dtmc/die.pm
-
15src/storm/models/symbolic/Model.cpp
-
23src/storm/models/symbolic/Model.h
-
10src/storm/storage/dd/Add.cpp
-
21src/storm/storage/dd/Add.h
-
15src/storm/storage/dd/DdMetaVariable.cpp
-
5src/storm/storage/dd/DdMetaVariable.h
-
123src/storm/storage/dd/bisimulation/Partition.cpp
-
73src/storm/storage/dd/bisimulation/Partition.h
-
25src/storm/storage/dd/bisimulation/Signature.cpp
-
24src/storm/storage/dd/bisimulation/Signature.h
-
329src/storm/storage/dd/bisimulation/SignatureRefiner.cpp
-
37src/storm/storage/dd/bisimulation/SignatureRefiner.h
-
7src/storm/storage/dd/cudd/InternalCuddAdd.cpp
-
9src/storm/storage/dd/cudd/InternalCuddAdd.h
-
6src/storm/storage/dd/cudd/InternalCuddDdManager.h
-
5src/storm/storage/dd/sylvan/InternalSylvanAdd.cpp
-
16src/storm/storage/dd/sylvan/InternalSylvanAdd.h
-
7src/storm/storage/dd/sylvan/InternalSylvanDdManager.cpp
-
16src/storm/utility/storm.h
-
27src/test/storage/SymbolicBisimulationDecompositionTest.cpp
@ -0,0 +1,123 @@ |
|||
#include "storm/storage/dd/bisimulation/Partition.h"
|
|||
|
|||
#include "storm/storage/dd/DdManager.h"
|
|||
|
|||
namespace storm { |
|||
namespace dd { |
|||
namespace bisimulation { |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
Partition<DdType, ValueType>::Partition(storm::dd::Add<DdType, ValueType> const& partitionAdd, storm::expressions::Variable const& blockVariable, uint64_t nextFreeBlockIndex) : partitionAdd(partitionAdd), blockVariable(blockVariable), nextFreeBlockIndex(nextFreeBlockIndex) { |
|||
// Intentionally left empty.
|
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
bool Partition<DdType, ValueType>::operator==(Partition<DdType, ValueType> const& other) { |
|||
return this->partitionAdd == other.partitionAdd && this->blockVariable == other.blockVariable && this->nextFreeBlockIndex == other.nextFreeBlockIndex; |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
Partition<DdType, ValueType> Partition<DdType, ValueType>::replacePartitionAdd(storm::dd::Add<DdType, ValueType> const& newPartitionAdd, uint64_t nextFreeBlockIndex) const { |
|||
return Partition<DdType, ValueType>(newPartitionAdd, blockVariable, nextFreeBlockIndex); |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
Partition<DdType, ValueType> Partition<DdType, ValueType>::create(storm::models::symbolic::Model<DdType, ValueType> const& model) { |
|||
return create(model, model.getLabels()); |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
Partition<DdType, ValueType> Partition<DdType, ValueType>::create(storm::models::symbolic::Model<DdType, ValueType> const& model, std::vector<std::string> const& labels) { |
|||
std::vector<storm::expressions::Expression> expressions; |
|||
for (auto const& label : labels) { |
|||
expressions.push_back(model.getExpression(label)); |
|||
} |
|||
return create(model, expressions); |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
Partition<DdType, ValueType> Partition<DdType, ValueType>::create(storm::models::symbolic::Model<DdType, ValueType> const& model, std::vector<storm::expressions::Expression> const& expressions) { |
|||
storm::dd::DdManager<DdType>& manager = model.getManager(); |
|||
|
|||
std::vector<storm::dd::Bdd<DdType>> stateSets; |
|||
for (auto const& expression : expressions) { |
|||
stateSets.emplace_back(model.getStates(expression)); |
|||
} |
|||
|
|||
storm::expressions::Variable blockVariable = createBlockVariable(manager, model.getReachableStates().getNonZeroCount()); |
|||
std::pair<storm::dd::Add<DdType, ValueType>, uint64_t> partitionAddAndBlockCount = createPartitionAdd(manager, model, stateSets, blockVariable); |
|||
return Partition<DdType, ValueType>(partitionAddAndBlockCount.first, blockVariable, partitionAddAndBlockCount.second); |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
uint64_t Partition<DdType, ValueType>::getNumberOfBlocks() const { |
|||
return nextFreeBlockIndex; |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
storm::dd::Add<DdType, ValueType> const& Partition<DdType, ValueType>::getPartitionAdd() const { |
|||
return partitionAdd; |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
storm::expressions::Variable const& Partition<DdType, ValueType>::getBlockVariable() const { |
|||
return blockVariable; |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
uint64_t Partition<DdType, ValueType>::getNextFreeBlockIndex() const { |
|||
return nextFreeBlockIndex; |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType> |
|||
void enumerateBlocksRec(std::vector<storm::dd::Bdd<DdType>> const& stateSets, storm::dd::Bdd<DdType> const& currentStateSet, uint64_t offset, storm::expressions::Variable const& blockVariable, std::function<void (storm::dd::Bdd<DdType> const&)> const& callback) { |
|||
if (currentStateSet.isZero()) { |
|||
return; |
|||
} |
|||
if (offset == stateSets.size()) { |
|||
callback(currentStateSet); |
|||
} else { |
|||
enumerateBlocksRec(stateSets, currentStateSet && stateSets[offset], offset + 1, blockVariable, callback); |
|||
enumerateBlocksRec(stateSets, currentStateSet && !stateSets[offset], offset + 1, blockVariable, callback); |
|||
} |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
std::pair<storm::dd::Add<DdType, ValueType>, uint64_t> Partition<DdType, ValueType>::createPartitionAdd(storm::dd::DdManager<DdType> const& manager, storm::models::symbolic::Model<DdType, ValueType> const& model, std::vector<storm::dd::Bdd<DdType>> const& stateSets, storm::expressions::Variable const& blockVariable) { |
|||
uint64_t blockCount = 0; |
|||
storm::dd::Add<DdType, ValueType> partitionAdd = manager.template getAddZero<ValueType>(); |
|||
|
|||
// Enumerate all realizable blocks.
|
|||
enumerateBlocksRec<DdType>(stateSets, model.getReachableStates(), 0, blockVariable, [&manager, &partitionAdd, &blockVariable, &blockCount](storm::dd::Bdd<DdType> const& stateSet) { |
|||
stateSet.template toAdd<ValueType>().exportToDot("states_" + std::to_string(blockCount) + ".dot"); |
|||
partitionAdd += (stateSet && manager.getEncoding(blockVariable, blockCount)).template toAdd<ValueType>(); |
|||
blockCount++; |
|||
} ); |
|||
|
|||
return std::make_pair(partitionAdd, blockCount); |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
storm::expressions::Variable Partition<DdType, ValueType>::createBlockVariable(storm::dd::DdManager<DdType>& manager, uint64_t numberOfStates) { |
|||
storm::expressions::Variable blockVariable; |
|||
if (manager.hasMetaVariable("blocks")) { |
|||
int64_t counter = 0; |
|||
while (manager.hasMetaVariable("block" + std::to_string(counter))) { |
|||
++counter; |
|||
} |
|||
blockVariable = manager.addMetaVariable("blocks" + std::to_string(counter), 0, numberOfStates, 1).front(); |
|||
} else { |
|||
blockVariable = manager.addMetaVariable("blocks", 0, numberOfStates, 1).front(); |
|||
} |
|||
return blockVariable; |
|||
} |
|||
|
|||
template class Partition<storm::dd::DdType::CUDD, double>; |
|||
|
|||
template class Partition<storm::dd::DdType::Sylvan, double>; |
|||
template class Partition<storm::dd::DdType::Sylvan, storm::RationalNumber>; |
|||
template class Partition<storm::dd::DdType::Sylvan, storm::RationalFunction>; |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
#pragma once |
|||
|
|||
#include "storm/storage/dd/DdType.h" |
|||
#include "storm/storage/dd/Add.h" |
|||
|
|||
#include "storm/models/symbolic/Model.h" |
|||
|
|||
namespace storm { |
|||
namespace dd { |
|||
namespace bisimulation { |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
class Partition { |
|||
public: |
|||
Partition() = default; |
|||
|
|||
/*! |
|||
* Creates a new partition from the given data. |
|||
* |
|||
* @param partitionAdd An ADD that maps encoding over the state/row variables and the block variable to |
|||
* one iff the state is in the block. |
|||
* @param blockVariable The variable to use for the block encoding. Its range must be [0, x] where x is |
|||
* the number of states in the partition. |
|||
* @param nextFreeBlockIndex The next free block index. The existing blocks must be encoded with indices |
|||
* between 0 and this number. |
|||
*/ |
|||
Partition(storm::dd::Add<DdType, ValueType> const& partitionAdd, storm::expressions::Variable const& blockVariable, uint64_t nextFreeBlockIndex); |
|||
|
|||
bool operator==(Partition<DdType, ValueType> const& other); |
|||
|
|||
Partition<DdType, ValueType> replacePartitionAdd(storm::dd::Add<DdType, ValueType> const& newPartitionAdd, uint64_t nextFreeBlockIndex) const; |
|||
|
|||
/*! |
|||
* Creates a partition from the given model that respects all labels. |
|||
*/ |
|||
static Partition create(storm::models::symbolic::Model<DdType, ValueType> const& model); |
|||
|
|||
/*! |
|||
* Creates a partition from the given model that respects the given labels. |
|||
*/ |
|||
static Partition create(storm::models::symbolic::Model<DdType, ValueType> const& model, std::vector<std::string> const& labels); |
|||
|
|||
/*! |
|||
* Creates a partition from the given model that respects the given expressions. |
|||
*/ |
|||
static Partition create(storm::models::symbolic::Model<DdType, ValueType> const& model, std::vector<storm::expressions::Expression> const& expressions); |
|||
|
|||
uint64_t getNumberOfBlocks() const; |
|||
|
|||
storm::dd::Add<DdType, ValueType> const& getPartitionAdd() const; |
|||
|
|||
storm::expressions::Variable const& getBlockVariable() const; |
|||
|
|||
uint64_t getNextFreeBlockIndex() const; |
|||
|
|||
private: |
|||
static std::pair<storm::dd::Add<DdType, ValueType>, uint64_t> createPartitionAdd(storm::dd::DdManager<DdType> const& manager, storm::models::symbolic::Model<DdType, ValueType> const& model, std::vector<storm::dd::Bdd<DdType>> const& stateSets, storm::expressions::Variable const& blockVariable); |
|||
|
|||
static storm::expressions::Variable createBlockVariable(storm::dd::DdManager<DdType>& manager, uint64_t numberOfStates); |
|||
|
|||
// The ADD representing the partition. The ADD is over the row variables of the model and the block variable. |
|||
storm::dd::Add<DdType, ValueType> partitionAdd; |
|||
|
|||
// The meta variable used to encode the block of each state in this partition. |
|||
storm::expressions::Variable blockVariable; |
|||
|
|||
// The next free block index. |
|||
uint64_t nextFreeBlockIndex; |
|||
}; |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,25 @@ |
|||
#include "storm/storage/dd/bisimulation/Signature.h"
|
|||
|
|||
namespace storm { |
|||
namespace dd { |
|||
namespace bisimulation { |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
Signature<DdType, ValueType>::Signature(storm::dd::Add<DdType, ValueType> const& signatureAdd) : signatureAdd(signatureAdd) { |
|||
// Intentionally left empty.
|
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
storm::dd::Add<DdType, ValueType> const& Signature<DdType, ValueType>::getSignatureAdd() const { |
|||
return signatureAdd; |
|||
} |
|||
|
|||
template class Signature<storm::dd::DdType::CUDD, double>; |
|||
|
|||
template class Signature<storm::dd::DdType::Sylvan, double>; |
|||
template class Signature<storm::dd::DdType::Sylvan, storm::RationalNumber>; |
|||
template class Signature<storm::dd::DdType::Sylvan, storm::RationalFunction>; |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
#pragma once |
|||
|
|||
#include "storm/storage/dd/DdType.h" |
|||
|
|||
#include "storm/storage/dd/bisimulation/Partition.h" |
|||
|
|||
namespace storm { |
|||
namespace dd { |
|||
namespace bisimulation { |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
class Signature { |
|||
public: |
|||
Signature(storm::dd::Add<DdType, ValueType> const& signatureAdd); |
|||
|
|||
storm::dd::Add<DdType, ValueType> const& getSignatureAdd() const; |
|||
|
|||
private: |
|||
storm::dd::Add<DdType, ValueType> signatureAdd; |
|||
}; |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,329 @@ |
|||
#include "storm/storage/dd/bisimulation/SignatureRefiner.h"
|
|||
|
|||
#include <unordered_map>
|
|||
|
|||
#include "storm/storage/dd/DdManager.h"
|
|||
|
|||
#include "storm/storage/dd/cudd/InternalCuddDdManager.h"
|
|||
|
|||
#include "storm/utility/macros.h"
|
|||
#include "storm/exceptions/NotImplementedException.h"
|
|||
|
|||
#include "resources/3rdparty/sparsepp/sparsepp.h"
|
|||
|
|||
namespace storm { |
|||
namespace dd { |
|||
namespace bisimulation { |
|||
|
|||
struct CuddPointerPairHash { |
|||
std::size_t operator()(std::pair<DdNode const*, DdNode const*> const& pair) const { |
|||
std::size_t seed = std::hash<DdNode const*>()(pair.first); |
|||
boost::hash_combine(seed, pair.second); |
|||
return seed; |
|||
} |
|||
}; |
|||
|
|||
struct SylvanMTBDDPairHash { |
|||
std::size_t operator()(std::pair<MTBDD, MTBDD> const& pair) const { |
|||
std::size_t seed = std::hash<MTBDD>()(pair.first); |
|||
boost::hash_combine(seed, pair.second); |
|||
return seed; |
|||
} |
|||
}; |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
class InternalSignatureRefiner; |
|||
|
|||
template<typename ValueType> |
|||
class InternalSignatureRefiner<storm::dd::DdType::CUDD, ValueType> { |
|||
public: |
|||
InternalSignatureRefiner(storm::dd::DdManager<storm::dd::DdType::CUDD> const& manager, storm::expressions::Variable const& blockVariable, uint64_t lastStateLevel) : manager(manager), internalDdManager(manager.getInternalDdManager()), blockVariable(blockVariable), lastStateLevel(lastStateLevel), nextFreeBlockIndex(0), numberOfRefinements(0) { |
|||
// Intentionally left empty.
|
|||
} |
|||
|
|||
Partition<storm::dd::DdType::CUDD, ValueType> refine(Partition<storm::dd::DdType::CUDD, ValueType> const& oldPartition, Signature<storm::dd::DdType::CUDD, ValueType> const& signature) { |
|||
storm::dd::Add<storm::dd::DdType::CUDD, ValueType> newPartitionAdd = refine(oldPartition, signature.getSignatureAdd()); |
|||
++numberOfRefinements; |
|||
return oldPartition.replacePartitionAdd(newPartitionAdd, nextFreeBlockIndex); |
|||
} |
|||
|
|||
private: |
|||
storm::dd::Add<storm::dd::DdType::CUDD, ValueType> refine(Partition<storm::dd::DdType::CUDD, ValueType> const& oldPartition, storm::dd::Add<storm::dd::DdType::CUDD, ValueType> const& signatureAdd) { |
|||
// Clear the caches.
|
|||
signatureCache.clear(); |
|||
reuseBlocksCache.clear(); |
|||
nextFreeBlockIndex = oldPartition.getNextFreeBlockIndex(); |
|||
|
|||
// Perform the actual recursive refinement step.
|
|||
DdNodePtr result = refine(oldPartition.getPartitionAdd().getInternalAdd().getCuddDdNode(), signatureAdd.getInternalAdd().getCuddDdNode()); |
|||
|
|||
// Construct resulting ADD from the obtained node and the meta information.
|
|||
storm::dd::InternalAdd<storm::dd::DdType::CUDD, ValueType> internalNewPartitionAdd(&internalDdManager, cudd::ADD(internalDdManager.getCuddManager(), result)); |
|||
storm::dd::Add<storm::dd::DdType::CUDD, ValueType> newPartitionAdd(oldPartition.getPartitionAdd().getDdManager(), internalNewPartitionAdd, oldPartition.getPartitionAdd().getContainedMetaVariables()); |
|||
|
|||
return newPartitionAdd; |
|||
} |
|||
|
|||
DdNodePtr refine(DdNode* partitionNode, DdNode* signatureNode) { |
|||
::DdManager* ddman = internalDdManager.getCuddManager().getManager(); |
|||
|
|||
// Check the cache whether we have seen the same node before.
|
|||
auto sigCacheIt = signatureCache.find(std::make_pair(signatureNode, partitionNode)); |
|||
if (sigCacheIt != signatureCache.end()) { |
|||
// If so, we return the corresponding result.
|
|||
return sigCacheIt->second; |
|||
} |
|||
|
|||
// Determine the levels in the DDs.
|
|||
uint64_t partitionVariable = Cudd_NodeReadIndex(partitionNode); |
|||
uint64_t signatureVariable = Cudd_NodeReadIndex(signatureNode); |
|||
uint64_t partitionLevel = Cudd_ReadPerm(ddman, partitionVariable); |
|||
uint64_t signatureLevel = Cudd_ReadPerm(ddman, signatureVariable); |
|||
uint64_t topLevel = std::min(partitionLevel, signatureLevel); |
|||
uint64_t topVariable = topLevel == partitionLevel ? partitionVariable : signatureVariable; |
|||
|
|||
// Check whether the top variable is still within the state encoding.
|
|||
if (topLevel <= lastStateLevel) { |
|||
// Determine subresults by recursive descent.
|
|||
DdNodePtr thenResult; |
|||
DdNodePtr elseResult; |
|||
if (partitionLevel < signatureLevel) { |
|||
thenResult = refine(Cudd_T(partitionNode), signatureNode); |
|||
Cudd_Ref(thenResult); |
|||
elseResult = refine(Cudd_E(partitionNode), signatureNode); |
|||
Cudd_Ref(elseResult); |
|||
} else if (partitionLevel > signatureLevel) { |
|||
thenResult = refine(partitionNode, Cudd_T(signatureNode)); |
|||
Cudd_Ref(thenResult); |
|||
elseResult = refine(partitionNode, Cudd_E(signatureNode)); |
|||
Cudd_Ref(elseResult); |
|||
} else { |
|||
thenResult = refine(Cudd_T(partitionNode), Cudd_T(signatureNode)); |
|||
Cudd_Ref(thenResult); |
|||
elseResult = refine(Cudd_E(partitionNode), Cudd_E(signatureNode)); |
|||
Cudd_Ref(elseResult); |
|||
} |
|||
|
|||
if (thenResult == elseResult) { |
|||
Cudd_Deref(thenResult); |
|||
Cudd_Deref(elseResult); |
|||
return thenResult; |
|||
} |
|||
|
|||
// Get the node to connect the subresults.
|
|||
DdNode* var = Cudd_addIthVar(ddman, topVariable); |
|||
Cudd_Ref(var); |
|||
DdNode* result = Cudd_addIte(ddman, var, thenResult, elseResult); |
|||
Cudd_Ref(result); |
|||
Cudd_RecursiveDeref(ddman, var); |
|||
Cudd_Deref(thenResult); |
|||
Cudd_Deref(elseResult); |
|||
|
|||
// Store the result in the cache.
|
|||
signatureCache[std::make_pair(signatureNode, partitionNode)] = result; |
|||
|
|||
Cudd_Deref(result); |
|||
return result; |
|||
} else { |
|||
|
|||
// If we are not within the state encoding any more, we hit the signature itself.
|
|||
|
|||
// If we arrived at the constant zero node, then this was an illegal state encoding (we require
|
|||
// all states to be non-deadlock).
|
|||
if (signatureNode == Cudd_ReadZero(ddman)) { |
|||
return signatureNode; |
|||
} |
|||
|
|||
// If this is the first time (in this traversal) that we encounter this signature, we check
|
|||
// whether we can assign the old block number to it.
|
|||
auto reuseCacheIt = reuseBlocksCache.find(partitionNode); |
|||
if (reuseCacheIt == reuseBlocksCache.end()) { |
|||
reuseBlocksCache.emplace(partitionNode, true); |
|||
signatureCache[std::make_pair(signatureNode, partitionNode)] = partitionNode; |
|||
return partitionNode; |
|||
} else { |
|||
DdNode* result; |
|||
{ |
|||
storm::dd::Add<storm::dd::DdType::CUDD, ValueType> blockEncoding = manager.getEncoding(blockVariable, nextFreeBlockIndex).template toAdd<ValueType>(); |
|||
++nextFreeBlockIndex; |
|||
result = blockEncoding.getInternalAdd().getCuddDdNode(); |
|||
Cudd_Ref(result); |
|||
} |
|||
signatureCache[std::make_pair(signatureNode, partitionNode)] = result; |
|||
Cudd_Deref(result); |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
|
|||
storm::dd::DdManager<storm::dd::DdType::CUDD> const& manager; |
|||
storm::dd::InternalDdManager<storm::dd::DdType::CUDD> const& internalDdManager; |
|||
storm::expressions::Variable const& blockVariable; |
|||
|
|||
// The last level that belongs to the state encoding in the DDs.
|
|||
uint64_t lastStateLevel; |
|||
|
|||
// The current number of blocks of the new partition.
|
|||
uint64_t nextFreeBlockIndex; |
|||
|
|||
// The number of completed refinements.
|
|||
uint64_t numberOfRefinements; |
|||
|
|||
// The cache used to identify states with identical signature.
|
|||
spp::sparse_hash_map<std::pair<DdNode const*, DdNode const*>, DdNode*, CuddPointerPairHash> signatureCache; |
|||
|
|||
// The cache used to identify which old block numbers have already been reused.
|
|||
spp::sparse_hash_map<DdNode const*, bool> reuseBlocksCache; |
|||
}; |
|||
|
|||
|
|||
template<typename ValueType> |
|||
class InternalSignatureRefiner<storm::dd::DdType::Sylvan, ValueType> { |
|||
public: |
|||
InternalSignatureRefiner(storm::dd::DdManager<storm::dd::DdType::Sylvan> const& manager, storm::expressions::Variable const& blockVariable, uint64_t lastStateLevel) : manager(manager), internalDdManager(manager.getInternalDdManager()), blockVariable(blockVariable), lastStateLevel(lastStateLevel), nextFreeBlockIndex(0), numberOfRefinements(0) { |
|||
// Intentionally left empty.
|
|||
} |
|||
|
|||
Partition<storm::dd::DdType::Sylvan, ValueType> refine(Partition<storm::dd::DdType::Sylvan, ValueType> const& oldPartition, Signature<storm::dd::DdType::Sylvan, ValueType> const& signature) { |
|||
storm::dd::Add<storm::dd::DdType::Sylvan, ValueType> newPartitionAdd = refine(oldPartition, signature.getSignatureAdd()); |
|||
++numberOfRefinements; |
|||
return oldPartition.replacePartitionAdd(newPartitionAdd, nextFreeBlockIndex); |
|||
} |
|||
|
|||
private: |
|||
storm::dd::Add<storm::dd::DdType::Sylvan, ValueType> refine(Partition<storm::dd::DdType::Sylvan, ValueType> const& oldPartition, storm::dd::Add<storm::dd::DdType::Sylvan, ValueType> const& signatureAdd) { |
|||
// Clear the caches.
|
|||
signatureCache.clear(); |
|||
reuseBlocksCache.clear(); |
|||
nextFreeBlockIndex = oldPartition.getNextFreeBlockIndex(); |
|||
|
|||
// Perform the actual recursive refinement step.
|
|||
MTBDD result = refine(oldPartition.getPartitionAdd().getInternalAdd().getSylvanMtbdd().GetMTBDD(), signatureAdd.getInternalAdd().getSylvanMtbdd().GetMTBDD()); |
|||
|
|||
// Construct resulting ADD from the obtained node and the meta information.
|
|||
storm::dd::InternalAdd<storm::dd::DdType::Sylvan, ValueType> internalNewPartitionAdd(&internalDdManager, sylvan::Mtbdd(result)); |
|||
storm::dd::Add<storm::dd::DdType::Sylvan, ValueType> newPartitionAdd(oldPartition.getPartitionAdd().getDdManager(), internalNewPartitionAdd, oldPartition.getPartitionAdd().getContainedMetaVariables()); |
|||
|
|||
return newPartitionAdd; |
|||
} |
|||
|
|||
MTBDD refine(MTBDD partitionNode, MTBDD signatureNode) { |
|||
LACE_ME; |
|||
|
|||
// Check the cache whether we have seen the same node before.
|
|||
auto sigCacheIt = signatureCache.find(std::make_pair(signatureNode, partitionNode)); |
|||
if (sigCacheIt != signatureCache.end()) { |
|||
// If so, we return the corresponding result.
|
|||
return sigCacheIt->second; |
|||
} |
|||
|
|||
// Determine levels in the DDs.
|
|||
BDDVAR signatureVariable = mtbdd_isleaf(signatureNode) ? 0xffffffff : sylvan_var(signatureNode); |
|||
BDDVAR partitionVariable = sylvan_var(partitionNode); |
|||
BDDVAR topVariable = std::min(signatureVariable, partitionVariable); |
|||
|
|||
// Check whether the top variable is still within the state encoding.
|
|||
if (topVariable <= lastStateLevel) { |
|||
// Determine subresults by recursive descent.
|
|||
MTBDD thenResult; |
|||
MTBDD elseResult; |
|||
if (partitionVariable < signatureVariable) { |
|||
thenResult = refine(sylvan_high(partitionNode), signatureNode); |
|||
elseResult = refine(sylvan_low(partitionNode), signatureNode); |
|||
} else if (partitionVariable > signatureVariable) { |
|||
thenResult = refine(partitionNode, sylvan_high(signatureNode)); |
|||
elseResult = refine(partitionNode, sylvan_low(signatureNode)); |
|||
} else { |
|||
thenResult = refine(sylvan_high(partitionNode), sylvan_high(signatureNode)); |
|||
elseResult = refine(sylvan_low(partitionNode), sylvan_low(signatureNode)); |
|||
} |
|||
|
|||
if (thenResult == elseResult) { |
|||
return thenResult; |
|||
} |
|||
|
|||
// Get the node to connect the subresults.
|
|||
MTBDD result = mtbdd_ite(sylvan_ithvar(topVariable), thenResult, elseResult); |
|||
|
|||
// Store the result in the cache.
|
|||
signatureCache[std::make_pair(signatureNode, partitionNode)] = result; |
|||
|
|||
return result; |
|||
} else { |
|||
|
|||
// If we are not within the state encoding any more, we hit the signature itself.
|
|||
|
|||
// If we arrived at the constant zero node, then this was an illegal state encoding (we require
|
|||
// all states to be non-deadlock).
|
|||
if (mtbdd_iszero(signatureNode)) { |
|||
return signatureNode; |
|||
} |
|||
|
|||
// If this is the first time (in this traversal) that we encounter this signature, we check
|
|||
// whether we can assign the old block number to it.
|
|||
auto reuseCacheIt = reuseBlocksCache.find(partitionNode); |
|||
if (reuseCacheIt == reuseBlocksCache.end()) { |
|||
reuseBlocksCache.emplace(partitionNode, true); |
|||
signatureCache[std::make_pair(signatureNode, partitionNode)] = partitionNode; |
|||
return partitionNode; |
|||
} else { |
|||
MTBDD result; |
|||
{ |
|||
storm::dd::Add<storm::dd::DdType::Sylvan, ValueType> blockEncoding = manager.getEncoding(blockVariable, nextFreeBlockIndex).template toAdd<ValueType>(); |
|||
++nextFreeBlockIndex; |
|||
result = blockEncoding.getInternalAdd().getSylvanMtbdd().GetMTBDD(); |
|||
} |
|||
signatureCache[std::make_pair(signatureNode, partitionNode)] = result; |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
|
|||
storm::dd::DdManager<storm::dd::DdType::Sylvan> const& manager; |
|||
storm::dd::InternalDdManager<storm::dd::DdType::Sylvan> const& internalDdManager; |
|||
storm::expressions::Variable const& blockVariable; |
|||
|
|||
// The last level that belongs to the state encoding in the DDs.
|
|||
uint64_t lastStateLevel; |
|||
|
|||
// The current number of blocks of the new partition.
|
|||
uint64_t nextFreeBlockIndex; |
|||
|
|||
// The number of completed refinements.
|
|||
uint64_t numberOfRefinements; |
|||
|
|||
// The cache used to identify states with identical signature.
|
|||
spp::sparse_hash_map<std::pair<MTBDD, MTBDD>, MTBDD, SylvanMTBDDPairHash> signatureCache; |
|||
|
|||
// The cache used to identify which old block numbers have already been reused.
|
|||
spp::sparse_hash_map<MTBDD, bool> reuseBlocksCache; |
|||
}; |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
SignatureRefiner<DdType, ValueType>::SignatureRefiner(storm::dd::DdManager<DdType> const& manager, storm::expressions::Variable const& blockVariable, std::set<storm::expressions::Variable> const& stateVariables) : manager(manager), stateVariables(stateVariables) { |
|||
uint64_t lastStateLevel = 0; |
|||
for (auto const& stateVariable : stateVariables) { |
|||
lastStateLevel = std::max(lastStateLevel, manager.getMetaVariable(stateVariable).getHighestLevel()); |
|||
} |
|||
|
|||
internalRefiner = std::make_unique<InternalSignatureRefiner<DdType, ValueType>>(manager, blockVariable, lastStateLevel); |
|||
} |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
SignatureRefiner<DdType, ValueType>::~SignatureRefiner() = default; |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
Partition<DdType, ValueType> SignatureRefiner<DdType, ValueType>::refine(Partition<DdType, ValueType> const& oldPartition, Signature<DdType, ValueType> const& signature) { |
|||
return internalRefiner->refine(oldPartition, signature); |
|||
} |
|||
|
|||
template class SignatureRefiner<storm::dd::DdType::CUDD, double>; |
|||
|
|||
template class SignatureRefiner<storm::dd::DdType::Sylvan, double>; |
|||
template class SignatureRefiner<storm::dd::DdType::Sylvan, storm::RationalNumber>; |
|||
template class SignatureRefiner<storm::dd::DdType::Sylvan, storm::RationalFunction>; |
|||
|
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,37 @@ |
|||
#pragma once |
|||
|
|||
#include "storm/storage/dd/DdType.h" |
|||
|
|||
#include "storm/storage/dd/bisimulation/Partition.h" |
|||
#include "storm/storage/dd/bisimulation/Signature.h" |
|||
|
|||
namespace storm { |
|||
namespace dd { |
|||
namespace bisimulation { |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
class InternalSignatureRefiner; |
|||
|
|||
template<storm::dd::DdType DdType, typename ValueType> |
|||
class SignatureRefiner { |
|||
public: |
|||
SignatureRefiner(storm::dd::DdManager<DdType> const& manager, storm::expressions::Variable const& blockVariable, std::set<storm::expressions::Variable> const& stateVariables); |
|||
|
|||
~SignatureRefiner(); |
|||
|
|||
Partition<DdType, ValueType> refine(Partition<DdType, ValueType> const& oldPartition, Signature<DdType, ValueType> const& signature); |
|||
|
|||
private: |
|||
// The manager responsible for the DDs. |
|||
storm::dd::DdManager<DdType> const& manager; |
|||
|
|||
// The variables encodin the states. |
|||
std::set<storm::expressions::Variable> const& stateVariables; |
|||
|
|||
// The internal refiner. |
|||
std::unique_ptr<InternalSignatureRefiner<DdType, ValueType>> internalRefiner; |
|||
}; |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
#include "gtest/gtest.h"
|
|||
#include "storm-config.h"
|
|||
#include "storm/parser/PrismParser.h"
|
|||
#include "storm/storage/SymbolicModelDescription.h"
|
|||
#include "storm/builder/DdPrismModelBuilder.h"
|
|||
#include "storm/models/symbolic/Dtmc.h"
|
|||
#include "storm/storage/dd/BisimulationDecomposition.h"
|
|||
|
|||
TEST(SymbolicBisimulationDecompositionTest_Cudd, Die) { |
|||
storm::storage::SymbolicModelDescription modelDescription = storm::parser::PrismParser::parse(STORM_TEST_RESOURCES_DIR "/dtmc/die.pm"); |
|||
storm::prism::Program program = modelDescription.preprocess().asPrismProgram(); |
|||
|
|||
std::shared_ptr<storm::models::symbolic::Model<storm::dd::DdType::CUDD, double>> model = storm::builder::DdPrismModelBuilder<storm::dd::DdType::CUDD, double>().build(program); |
|||
|
|||
storm::dd::BisimulationDecomposition<storm::dd::DdType::CUDD, double> decomposition(*model, storm::dd::bisimulation::Partition<storm::dd::DdType::CUDD, double>::create(*model, {"one"})); |
|||
decomposition.compute(); |
|||
} |
|||
|
|||
TEST(SymbolicBisimulationDecompositionTest_Cudd, Crowds) { |
|||
storm::storage::SymbolicModelDescription modelDescription = storm::parser::PrismParser::parse(STORM_TEST_RESOURCES_DIR "/dtmc/crowds-5-5.pm"); |
|||
storm::prism::Program program = modelDescription.preprocess().asPrismProgram(); |
|||
|
|||
std::shared_ptr<storm::models::symbolic::Model<storm::dd::DdType::CUDD, double>> model = storm::builder::DdPrismModelBuilder<storm::dd::DdType::CUDD, double>().build(program); |
|||
|
|||
storm::dd::BisimulationDecomposition<storm::dd::DdType::CUDD, double> decomposition(*model, storm::dd::bisimulation::Partition<storm::dd::DdType::CUDD, double>::create(*model, {"observe0Greater1"})); |
|||
decomposition.compute(); |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue