Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
SignatureBasedRefinement.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstdint>
5
14
15namespace storm::bisimulation {
16
17namespace detail {
18
19template<typename ValueType, SignatureMode SignatureMode>
38
39template<typename ValueType, SignatureMode SignatureMode>
41 bool const enforcePredecessorExploration) {
42 // Split the pivot block B into B=B_1 cup B_2 cup ... cup B_n using signature refinement
43 // First update the state signatures
44 for (uint64_t const state : pivotBlock) {
45 context.signatures.updateStateSignature(state);
46 }
47 // Then perform the signature-based split.
48 bool pivotHasBeenSplit{false};
50 pivotHasBeenSplit = context.partition.splitBlockByOrder(pivotBlock, context.signatures.getEquivalenceSplitOrder());
51 } else {
52 // In approximative mode, there provably is no transitive order on signatures that captures "approximately equal signatures".
53 // We therefore split in two steps: first, we split by a coarse order based on structural properties of the state signature.
54 // Then, we do a more expensive split based on clustering on each sub-block.
55 pivotHasBeenSplit = context.partition.splitBlockByOrder(pivotBlock, context.signatures.getStructuralSplitOrder());
56 auto const splitCondition = context.signatures.getApproximateSplitCondition();
57 context.partition.forEachSubBlock(pivotBlock, [&context, &splitCondition, &pivotHasBeenSplit](auto const& subBlock) {
58 pivotHasBeenSplit |= context.partition.splitBlockByClustering(subBlock, splitCondition);
59 });
60 }
61
62 if (!pivotHasBeenSplit && !enforcePredecessorExploration) {
63 // When the current pivot block is stable, there is no need to look into its predecessors. We can continue with the next pivot.
64 return;
65 }
66
67 // When the pivot block is not stable, it means it has been split and the predecessor blocks have not been checked since that split.
68 // Therefore, all the predecessor blocks need to checked again.
69 // While we could just add all those predecessors to the queue, we instead try to split them first based on simple, graph-based criteria so that (expensive)
70 // signature refinement is hopefully only applied to smaller blocks. Specifically, we split predecessor blocks based on which set of sub-blocks of the pivot
71 // they can reach.
72
73 // Gather predecessors of the pivot and their reachable sub-blocks
74 auto& predecessorToPivotBlocks = context.cache.predecessorToPivotBlocks;
75 auto& predecessorBlocks = context.cache.predecessorBlocks;
76 uint64_t subBlockIndex = 0;
77 context.partition.forEachSubBlock(pivotBlock, [&context, &predecessorToPivotBlocks, &predecessorBlocks, &subBlockIndex](auto const& subBlock) {
78 for (uint64_t const state : subBlock) {
79 for (auto const& predecessorEntry : context.backwardTransitions.getRow(state)) {
80 auto const predecessorState = predecessorEntry.getColumn();
81 auto const predecessorBlock = context.partition.getBlockOfElement(predecessorState);
82 if (predecessorBlock.size() > 1) {
83 // No need to investigate singleton predecessor blocks as they cannot be split any further.
84 predecessorToPivotBlocks.addValue(predecessorState, subBlockIndex);
85 predecessorBlocks.insert(predecessorBlock);
86 }
87 }
88 }
89 ++subBlockIndex;
90 });
91 // Apply splitting of the pivot predecessors (similar to splitter-based refinement)
92 while (!predecessorBlocks.empty()) {
93 auto const predecessorBlock = predecessorBlocks.pop();
94 // Split the predecessor block according to which sub-blocks of the pivot-block can be reached.
95 auto const& toPivotBlocks = predecessorToPivotBlocks.getValues();
96
97 // Split the block by whether a state is a predecessor of the pivot block or not
98 // We do this by either iterating over the pivotPredecessorStates or the predecessorBlock, depending on what is shorter.
99 auto [noPredecessors, predecessors] =
100 predecessorToPivotBlocks.getNonDefaultStates().size() < predecessorBlock.size()
101 ? context.partition.splitBlockByRange(predecessorBlock, predecessorToPivotBlocks.getNonDefaultStates())
102 : context.partition.splitBlockByPredicate(predecessorBlock, [&toPivotBlocks](auto const& state) { return toPivotBlocks[state] != 0ull; });
103
104 // At least one state should be a predecessor of the pivot block (otherwise we wouldn't have found that block above)
105 STORM_LOG_ASSERT(!predecessors.empty(), "The predecessor block should contain at least one predecessor state.");
106
107 // Now apply the splitting based on which sub-blocks of the pivot block can be reached.
108 // If we did not actually split the pivot block, this operation would have no effect.
109 if (pivotHasBeenSplit) {
111 predecessors, [&toPivotBlocks](uint64_t const state1, uint64_t const state2) { return toPivotBlocks[state1] < toPivotBlocks[state2]; });
112 } else {
114 std::all_of(predecessors.begin(), predecessors.end(), [&toPivotBlocks](uint64_t const& state) { return toPivotBlocks[state] == 1ull; }),
115 "Expected all predecessor states to reach the pivot block.");
116 }
117
118 if (context.partition.isProperSuperBlock(predecessorBlock)) {
119 // Erase the super block from the queue and the enforced unstable blocks (it might or might not be in there)
120 context.queue.erase(predecessorBlock);
121 // Add all sub-blocks to the queue. As we made a split, we must explore the predecessors of predecessorBlock.
122 context.partition.forEachSubBlock(predecessorBlock, [&context](auto const& block) { context.queue[block] = true; });
123 } else {
124 // The simple, graph-based splitting was not effective. We must add the entire predecessorBlock to the queue. We do not have to
125 // enforce that predecessors are explored.
126 context.queue.try_emplace(predecessorBlock, false);
127 }
128 }
129 // Reset the touched reachable-subblocks
130 predecessorToPivotBlocks.clear();
131}
132
133} // namespace detail
134
135template<typename ValueType, SignatureMode SignatureMode>
138 static_assert(!storm::IsIntervalType<ValueType>, "Interval types are not yet supported for signature-based refinement.");
139 detail::SignatureRefinementContext<ValueType, SignatureMode> context(model, partition, signatures);
140 // Initially, add all current blocks to the queue. No need to enforce exploring predecessors.
141 partition.forEachBlock([&context](auto const& block) { context.queue.emplace(block, false); });
142
143 while (!context.queue.empty()) {
144 // take the smallest block from the queue
145 auto const [pivotBlock, enforcePredecessorExploration] = *context.queue.begin();
146 context.queue.erase(context.queue.begin());
147 STORM_LOG_ASSERT(!partition.isProperSuperBlock(pivotBlock), "Broken invariant: the queue should not contain blocks that have been split.");
148 // Split the pivotBlock based on its signature and split the predecessor blocks based on a simple, graph-based criterion
149 detail::refinePartitionBasedOnSignature(context, pivotBlock, enforcePredecessorExploration);
150 }
151
152 // Singleton blocks are never re-examined once formed (because they cannot be split any further). Their cached signature can become stale if one of their
153 // successor blocks is split afterwards. Refresh them here so that, as documented, all signatures are up to date once this function returns.
154 partition.forEachBlock([&signatures](auto const& block) {
155 if (block.size() == 1) {
156 signatures.updateStateSignature(block.front());
157 }
158 });
159}
160
161// double
168
169// storm::RationalNumber
176
177// storm::RationalFunction
181
182} // namespace storm::bisimulation
Set of blocks that are no proper superblock.
Definition Partition.h:62
Represents a partition of a set of consecutive indices.
Definition Partition.h:20
bool splitBlockByClustering(Block const &block, Condition const &condition)
Splits the given block by anchor-based clustering: repeatedly picks the first not-yet-grouped element...
Definition Partition.h:288
std::map< Block, T, BlockCompare > OrderedBlockMap
A map of blocks, ordered by their size (smallest first).
Definition Partition.h:54
void forEachBlock(Func const &f) const
Applies the given function on each block in the partition.
Definition Partition.h:184
void forEachSubBlock(Block const &superBlock, Func const &f) const
Applies the given function on each block in the given super block.
Definition Partition.h:196
bool splitBlockByOrder(Block const &block, SplittingOrder const &less)
Splits the given block according to the given order.
Definition Partition.h:215
std::span< ElementIndex const > Block
A contiguous range of elements that form a (possibly proper super-) block of the partition.
Definition Partition.h:27
Block getBlockOfElement(ElementIndex element) const
Definition Partition.cpp:74
std::pair< Block, Block > splitBlockByRange(Block const &block, SplitRange &&r)
Splits the given block according to the given range of elements.
Definition Partition.h:362
bool isProperSuperBlock(Block const &block) const
std::pair< Block, Block > splitBlockByPredicate(Block const &block, SplittingPredicate const &f)
Splits the given block according to the given predicate.
Definition Partition.h:329
Computes and caches the state signatures and provides state-based order / condition for splitting in ...
Definition Signatures.h:24
void updateStateSignature(uint64_t const stateIndex)
Updates the state signature of the given state index.
SplitOrder getEquivalenceSplitOrder() const
Instantiates a SplitOrder whose ties exactly coincide with true signature equality (operator==).
SplitCondition getApproximateSplitCondition() const
Instantiates a SplitCondition.
SplitOrder getStructuralSplitOrder() const
Instantiates a SplitOrder that only compares with respect to structural equality of the state signatu...
Base class for all sparse models.
Definition Model.h:34
A class that holds a possibly non-square matrix in the compressed row storage format.
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
void refinePartitionBasedOnSignature(SignatureRefinementContext< ValueType, SignatureMode > &context, storm::bisimulation::Partition::Block const pivotBlock, bool const enforcePredecessorExploration)
SparseAccumulator< uint64_t > WrappingSetAccumulator
Accumulates a set of indices for each state, where only the index modulo 64 is stored,...
void performSignatureBasedRefinement(storm::models::sparse::Model< ValueType > const &model, storm::bisimulation::Partition &partition, Signatures< ValueType, SignatureMode > &signatures)
template void performSignatureBasedRefinement< storm::RationalNumber, SignatureMode::Approximative >(storm::models::sparse::Model< storm::RationalNumber > const &model, storm::bisimulation::Partition &partition, Signatures< storm::RationalNumber, SignatureMode::Approximative > &signatures)
template void performSignatureBasedRefinement< double, SignatureMode::Exact >(storm::models::sparse::Model< double > const &model, storm::bisimulation::Partition &partition, Signatures< double, SignatureMode::Exact > &signatures)
template void performSignatureBasedRefinement< double, SignatureMode::Approximative >(storm::models::sparse::Model< double > const &model, storm::bisimulation::Partition &partition, Signatures< double, SignatureMode::Approximative > &signatures)
template void performSignatureBasedRefinement< storm::RationalFunction, SignatureMode::Exact >(storm::models::sparse::Model< storm::RationalFunction > const &model, storm::bisimulation::Partition &partition, Signatures< storm::RationalFunction, SignatureMode::Exact > &signatures)
template void performSignatureBasedRefinement< storm::RationalNumber, SignatureMode::Exact >(storm::models::sparse::Model< storm::RationalNumber > const &model, storm::bisimulation::Partition &partition, Signatures< storm::RationalNumber, SignatureMode::Exact > &signatures)
constexpr bool IsIntervalType
Helper to check if a type is an interval.
storm::models::sparse::Model< ValueType > const & model
storm::bisimulation::Partition::OrderedBlockMap< bool > queue
storm::bisimulation::Signatures< ValueType, SignatureMode > & signatures
SignatureRefinementContext(storm::models::sparse::Model< ValueType > const &model, storm::bisimulation::Partition &partition, Signatures< ValueType, SignatureMode > &signatures)
storm::storage::SparseMatrix< ValueType > const backwardTransitions
struct storm::bisimulation::detail::SignatureRefinementContext::Cache cache