Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
Signatures.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <limits>
5#include <set>
6
16
17namespace storm::bisimulation {
18
19template<typename ValueType, SignatureMode Mode>
20Signatures<ValueType, Mode>::ChoiceSignatureCache::ChoiceSignatureCache(uint64_t const numStates) : values(numStates, storm::utility::zero<ValueType>()) {}
21
22template<typename ValueType, SignatureMode Mode>
23void Signatures<ValueType, Mode>::ChoiceSignatureCache::addValue(Partition::Block const& b, ValueType const& value) {
24 auto& current = values[b.front()];
25 if (storm::utility::isZero(current)) {
26 support.push_back(b);
27 }
28 current += value;
29}
30
31template<typename ValueType, SignatureMode Mode>
32auto Signatures<ValueType, Mode>::ChoiceSignatureCache::extract(BlockDistributionView const dest) -> BlockDistributionView {
33 std::sort(support.begin(), support.end(), Partition::BlockCompare());
34 STORM_LOG_ASSERT(support.size() <= dest.size(), "Destination span is too small to hold all entries.");
35 auto result = dest.first(support.size());
36
37 auto writeIt = result.begin();
38 for (auto const& b : support) {
39 auto& value = values[b.front()];
40 *writeIt = std::make_pair(b, std::move(value));
41 ++writeIt;
43 }
44 if (result.size() != dest.size()) {
45 // Mark the end of the written entries with an empty block.
46 // This is used to easily get the choice signature later without rebuilding it. See getChoiceSignature.
47 dest[result.size()] = std::pair<Partition::Block, ValueType>{};
48 }
49 support.clear();
50 return result;
52
53template<typename ValueType, SignatureMode Mode>
54std::strong_ordering Signatures<ValueType, Mode>::ChoiceSignature::compareStructure(ChoiceSignature const& other) const {
55 if (auto const cmp = choiceClass <=> other.choiceClass; cmp != 0) {
56 return cmp;
57 }
58 if (auto const cmp = distr.size() <=> other.distr.size(); cmp != 0) {
59 return cmp;
60 }
61 auto it2 = other.distr.begin();
62 for (auto const& entry : distr) {
63 if (auto const cmp = entry.first.size() <=> it2->first.size(); cmp != 0) {
64 return cmp;
65 }
66 if (auto const cmp = entry.first.data() <=> it2->first.data(); cmp != 0) {
67 return cmp;
68 }
69 ++it2;
70 }
71 return std::strong_ordering::equal;
72}
73
74template<typename ValueType, SignatureMode Mode>
75auto Signatures<ValueType, Mode>::ChoiceSignature::compare(ChoiceSignature const& other) const -> ComparisonResult {
76 if (auto const cmp = compareStructure(other); cmp != 0) {
77 return cmp;
78 }
79 STORM_LOG_ASSERT(distr.size() == other.distr.size(), "The distributions of two signatures with the same structure must have the same size.");
80 // Only the distribution values can still differ. ValueType (e.g. RationalFunction, Interval) may lack <=>, so we fall back to </!= here.
81 if constexpr (Mode == SignatureMode::Exact) {
82 // Strong order: full lexicographic comparison.
83 auto it2 = other.distr.begin();
84 for (auto const& entry : distr) {
85 if (entry.second != it2->second) {
86 return entry.second < it2->second ? ComparisonResult::less : ComparisonResult::greater;
87 }
88 ++it2;
89 }
90 } else {
91 // Weak order: compare only the first entry
92 if (distr.empty()) {
93 return ComparisonResult::equivalent;
94 }
95 auto const& value = distr.front().second;
96 auto const& otherValue = other.distr.front().second;
97 if (value != otherValue) {
98 return value < otherValue ? ComparisonResult::less : ComparisonResult::greater;
99 }
100 }
101 return ComparisonResult::equivalent;
102}
103
104template<typename ValueType, SignatureMode Mode>
105bool Signatures<ValueType, Mode>::ChoiceSignature::approximatelyEqual(ChoiceSignature const& other, ValueType const& tolerance) const
106 requires(Mode == SignatureMode::Approximative)
107{
108 if (compareStructure(other) != 0) {
109 return false;
110 }
111 auto otherIt = other.distr.begin();
112 for (auto const& [block, value] : distr) {
113 if (storm::utility::abs<ValueType>(value - otherIt->second) > tolerance) {
114 return false;
115 }
116 ++otherIt;
117 }
118 return true;
119}
120
121template<typename ValueType, SignatureMode Mode>
122auto Signatures<ValueType, Mode>::StateSignature::find(ChoiceSignature const& signature, ValueType const tolerance) const
123 -> std::pair<ChoiceSignatureIterator, bool> {
124 if (choices.empty()) {
125 return std::make_pair(choices.end(), false);
126 }
127 auto it = std::lower_bound(choices.begin(), choices.end(), signature, [](ChoiceSignature const& choice1, ChoiceSignature const& choice2) {
128 return choice1.compare(choice2) == ChoiceSignature::ComparisonResult::less;
129 });
130 if constexpr (Mode == SignatureMode::Exact) {
131 return std::make_pair(it, it != choices.end() && it->compare(signature) == std::strong_ordering::equal);
132 } else {
133 if (signature.distr.empty()) {
134 // For empty choice signatures, it suffices to compare the structure.
135 return std::make_pair(it, it != choices.end() && it->compareStructure(signature) == std::strong_ordering::equal);
136 }
137 return findWithHint(it, signature, tolerance);
138 }
139}
140
141template<typename ValueType, SignatureMode Mode>
142auto Signatures<ValueType, Mode>::StateSignature::findWithHint(ChoiceSignatureIterator hint, ChoiceSignature const& signature, ValueType const tolerance) const
143 -> std::pair<ChoiceSignatureIterator, bool>
144 requires(Mode == SignatureMode::Approximative)
145{
146 // The input signature defines a (potentially empty) window inside the choices vector. That window is given by the entries that are compareStructure-equal
147 // to signature and whose first entry is within tolerance of the first entry of signature.
148 // As the choices are sorted by compare(), they can be devided into three consecutive, potentially empty segments:
149 // the entries before the window, the window itself, and the entries after the window.
150
151 STORM_LOG_ASSERT(hint >= choices.begin() && hint <= choices.end(), "Hint iterator must be within the range of choices.");
152 STORM_LOG_ASSERT(!signature.distr.empty(), "Distributions must not be empty."); // Don't deal with this special case here.
153
154 auto const near = [&tolerance](ValueType const& value1, ValueType const& value2) { return storm::utility::abs<ValueType>(value1 - value2) <= tolerance; };
155 enum class Location { BeforeWindow, InWindow, AfterWindow };
156 // Locate in which segment of the choices vector the given choice is.
157 auto const locate = [this, &signature, &near](ChoiceSignatureIterator choiceIt) {
158 if (choiceIt == choices.end()) {
159 return Location::AfterWindow;
160 }
161 auto const& choice = *choiceIt;
162 if (auto const cmp = choice.compareStructure(signature); cmp != 0) {
163 return cmp < 0 ? Location::BeforeWindow : Location::AfterWindow;
164 }
165 auto const& value = choice.distr.front().second;
166 auto const& signatureValue = signature.distr.front().second;
167 if (near(value, signatureValue)) {
168 return Location::InWindow;
169 }
170 return value < signatureValue ? Location::BeforeWindow : Location::AfterWindow;
171 };
172 // Returns true iff the choice is considered equivalent to the signature. Assumes that the given choice is in the window.
173 auto const found = [&signature, &near](ChoiceSignature const& choice) {
174 // The first entry is already assumed to be near, so we check the others.
175 auto it1 = choice.distr.begin() + 1;
176 auto it2 = signature.distr.begin() + 1;
177 for (; it1 != choice.distr.end() && it2 != signature.distr.end(); ++it1, ++it2) {
178 if (!near(it1->second, it2->second)) {
179 return false;
180 }
181 }
182 return true;
183 };
184
185 // Assumes an iterator that points inside the window and scans all window contents to the right (it excluded)
186 auto const scanToRightBoundary = [&](ChoiceSignatureIterator it) {
187 ++it;
188 while (locate(it) == Location::InWindow) {
189 if (found(*it)) {
190 return std::make_pair(it, true);
191 }
192 ++it;
193 }
194 return std::make_pair(it, false);
195 };
196 // Assumes an iterator that points inside the window and scans all window contents to the left (it excluded)
197 auto const scanToLeftBoundary = [&](ChoiceSignatureIterator it) {
198 while (it != choices.begin()) {
199 --it;
200 if (locate(it) != Location::InWindow) {
201 break;
202 }
203 if (found(*it)) {
204 return std::make_pair(it, true);
205 }
206 }
207 return std::make_pair(it, false);
208 };
209
210 // Callers usually pass a hint that lies in the window or is close to it. There are three cases for the hint:
211 // a) inside, b) before, or c) after the window.
212 auto const hintLocation = locate(hint);
213 if (hintLocation == Location::InWindow) { // case a), so we might need to scan in both directions
214 if (found(*hint)) {
215 return std::make_pair(hint, true);
216 }
217 if (auto resRight = scanToRightBoundary(hint); resRight.second) {
218 return resRight;
219 }
220 if (auto resLeft = scanToLeftBoundary(hint); resLeft.second) {
221 return resLeft;
222 }
223 } else {
224 auto start = hint;
225 auto startLocation = hintLocation;
226 if (hintLocation == Location::BeforeWindow) { // case b), find right boundary of window of the window
227 while (startLocation == Location::BeforeWindow) {
228 ++start;
229 startLocation = locate(start);
230 }
231 } else { // case c), find left boundary of the window
232 while (startLocation == Location::AfterWindow && start != choices.begin()) {
233 --start;
234 startLocation = locate(start);
235 }
236 }
237 if (startLocation == Location::InWindow) {
238 if (found(*start)) {
239 return std::make_pair(start, true);
240 }
241 if (auto result = hintLocation == Location::BeforeWindow ? scanToRightBoundary(start) : scanToLeftBoundary(start); result.second) {
242 return result;
243 }
244 }
245 }
246 return std::make_pair(hint, false);
247}
248
249template<typename ValueType, SignatureMode Mode>
250void Signatures<ValueType, Mode>::StateSignature::insert(ChoiceSignature const& signature, ValueType const tolerance) {
251 auto [it, found] = find(signature, tolerance);
252 if (!found) {
253 choices.insert(it, signature);
254 }
255}
256
257template<typename ValueType, SignatureMode Mode>
258Signatures<ValueType, Mode>::Signatures(storm::models::sparse::Model<ValueType> const& model, std::optional<std::vector<uint64_t>> const& choiceClasses,
259 storm::bisimulation::Partition const& partition)
260 requires(Mode == SignatureMode::Exact)
261 : choiceSignatureCache(partition.getNumberOfElements()),
262 model(model),
263 partition(partition),
264 choiceClasses(choiceClasses),
265 choiceDistributionStorage(model.getTransitionMatrix().getEntryCount()),
266 halfTolerance(storm::utility::zero<ValueType>()),
267 stateSignatureCache(model.getNumberOfStates()) {}
268
269template<typename ValueType, SignatureMode Mode>
270Signatures<ValueType, Mode>::Signatures(storm::models::sparse::Model<ValueType> const& model, std::optional<std::vector<uint64_t>> const& choiceClasses,
271 storm::bisimulation::Partition const& partition, ValueType const& tolerance)
272 requires(Mode == SignatureMode::Approximative)
273 : choiceSignatureCache(partition.getNumberOfElements()),
274 model(model),
275 partition(partition),
276 choiceClasses(choiceClasses),
277 choiceDistributionStorage(model.getTransitionMatrix().getEntryCount()),
278 halfTolerance(tolerance / storm::utility::convertNumber<ValueType, uint64_t>(2)),
279 stateSignatureCache(model.getNumberOfStates()) {}
280
281template<typename ValueType, SignatureMode Mode>
282auto Signatures<ValueType, Mode>::buildChoiceSignature(uint64_t const choiceIndex) -> ChoiceSignature {
283 auto const& matrix = model.getTransitionMatrix();
284 // Use the choiceSignatureCache to compute the distribution over successor blocks
285 for (auto const& entry : matrix.getRow(choiceIndex)) {
286 if (!storm::utility::isZero(entry.getValue())) {
287 choiceSignatureCache.addValue(partition.getBlockOfElement(entry.getColumn()), entry.getValue());
288 }
289 }
290 // Identify the choice distribution storage.
291 auto const row = matrix.getRow(choiceIndex);
292 uint64_t const offset = std::distance(matrix.begin(), row.begin());
293 BlockDistributionView distrStorage(choiceDistributionStorage.data() + offset, row.getNumberOfEntries());
294
295 // Extract the resulting distribution from the cache.
296 return ChoiceSignature{.choiceClass = choiceClasses ? (*choiceClasses)[choiceIndex] : 0, .distr = choiceSignatureCache.extract(distrStorage)};
297}
298
299template<typename ValueType, SignatureMode Mode>
300auto Signatures<ValueType, Mode>::getChoiceSignature(uint64_t const choiceIndex) const -> ChoiceSignature {
301 // Identify the choice distribution storage
302 auto const& matrix = model.getTransitionMatrix();
303 auto const row = matrix.getRow(choiceIndex);
304 uint64_t const offset = std::distance(matrix.begin(), row.begin());
305 BlockDistributionView distrStorage(choiceDistributionStorage.data() + offset, row.getNumberOfEntries());
306
307 // Determine the right size by finding the first empty block (if any)
308 uint64_t size = 0;
309 for (auto const& [block, _] : distrStorage) {
310 if (block.empty()) {
311 break;
312 }
313 ++size;
314 }
315
316 return ChoiceSignature{.choiceClass = choiceClasses ? (*choiceClasses)[choiceIndex] : 0, .distr = distrStorage.first(size)};
317}
318
319template<typename ValueType, SignatureMode Mode>
321 auto& sig = stateSignatureCache[stateIndex];
322 sig.choices.clear();
323 for (uint64_t const choiceIndex : model.getTransitionMatrix().getRowGroupIndices(stateIndex)) {
324 sig.insert(buildChoiceSignature(choiceIndex), halfTolerance);
325 }
326}
327
328template<typename ValueType, SignatureMode Mode>
330 requires(Mode == SignatureMode::Exact)
331{
332 return SplitOrder(stateSignatureCache);
333}
334
335template<typename ValueType, SignatureMode Mode>
337 requires(Mode == SignatureMode::Approximative)
338{
339 return SplitOrder(stateSignatureCache);
340}
341
342template<typename ValueType, SignatureMode Mode>
343bool Signatures<ValueType, Mode>::SplitOrder::operator()(uint64_t const state1, uint64_t const state2) const {
344 auto const& sig1 = signatures[state1];
345 auto const& sig2 = signatures[state2];
346 if (auto const cmp = sig1.choices.size() <=> sig2.choices.size(); cmp != 0) {
347 return cmp < 0;
348 }
349 auto it2 = sig2.choices.begin();
350 for (auto const& choice : sig1.choices) {
351 if constexpr (Mode == SignatureMode::Exact) {
352 // In exact mode, do a full comparison of the choice signatures
353 if (auto const cmp = choice.compare(*it2); cmp != 0) {
354 return cmp < 0;
355 }
356 } else {
357 // In approximative mode, only sort based on the structure of the choice signatures
358 if (auto const cmp = choice.compareStructure(*it2); cmp != 0) {
359 return cmp < 0;
360 }
361 }
362 ++it2;
363 }
364 return false;
365}
366
367template<typename ValueType, SignatureMode Mode>
369 requires(Mode == SignatureMode::Approximative)
370{
371 return SplitCondition(stateSignatureCache, halfTolerance);
372}
373
374template<typename ValueType, SignatureMode Mode>
375bool Signatures<ValueType, Mode>::SplitCondition::operator()(uint64_t const state1, uint64_t const state2) const
376 requires(Mode == SignatureMode::Approximative)
377{
378 auto const& sig1 = signatures[state1];
379 auto const& sig2 = signatures[state2];
380 // We can assume that sig1.choices and sig2.choices have the same pointwise structure (compareStructure-equivalent) as this comparison is only called after
381 // splitting with respect to getStructuralSplitOrder.
382 STORM_LOG_ASSERT(sig1.choices.size() == sig2.choices.size(), "SplitCondition should only be called for signatures with the same number of choices.");
383 auto it1 = sig1.choices.begin();
384 auto it2 = sig2.choices.begin();
385 for (; it1 != sig1.choices.end() && it2 != sig2.choices.end(); ++it1, ++it2) {
386 STORM_LOG_ASSERT(it1->distr.size() == it2->distr.size(), "SplitCondition should only be called for signatures with pointwise same choice structure.");
387 if (it1->distr.empty()) {
388 continue;
389 }
390 // Find a choice matching choice2 in sig1. As both signatures are sorted the same way, it1 is usually close to the window of choice2.
391 auto const [choiceInSig1It, foundIn1] = sig1.findWithHint(it1, *it2, tolerance);
392 if (!foundIn1) {
393 return true;
394 }
395 // If it1 was not the matching choice, we have to check if it1 also has a matching choice in sig2
396 if (it1 != choiceInSig1It && !sig2.findWithHint(it2, *it1, tolerance).second) {
397 return true;
398 }
399 }
400 // Reaching this point means that we consider the signatures to be equal, so no split.
401 return false;
402}
403
404template<typename ValueType, SignatureMode Mode>
405void Signatures<ValueType, Mode>::addQuotientChoiceMapping(uint64_t const state, StateSignature const& representativeSignature,
406 std::vector<uint64_t> const& choiceSignatureToQuotientChoiceIndex,
407 std::vector<uint64_t>& toQuotientChoice) const {
408 constexpr uint64_t Invalid = std::numeric_limits<uint64_t>::max();
409 auto const& stateSignature = stateSignatureCache[state];
410 auto const choiceIndices = model.getTransitionMatrix().getRowGroupIndices(state);
411 STORM_LOG_ASSERT(stateSignature.choices.size() == representativeSignature.choices.size(), "States in a block have different number of choice signatures.");
412
413 // Every choice of the state is represented by one of the state's own choice signatures. That representation is surjective, since each of those signatures
414 // was established from a choice of this state. It thus suffices to find a bijective matching between the choice signatures of the state and those of the
415 // representative: the composition of the two is surjective on the choice signatures of the representative as well. Such a surjective mapping is needed,
416 // as otherwise a scheduler for the quotient model could not be translated back to a choice for this state. Both steps are within halfTolerance, i.e. the
417 // values of a choice and those of the quotient choice representing it differ by at most the tolerance this instance was created with.
418 std::vector<uint64_t> choiceSignatureMatching;
419 if constexpr (Mode == SignatureMode::Exact) {
420 // The signatures of two states of the same block are equal entry-wise, so we can match them in order.
421 choiceSignatureMatching = storm::utility::vector::buildVectorForRange<uint64_t>(0, stateSignature.choices.size());
422 STORM_LOG_ASSERT(std::all_of(choiceSignatureMatching.begin(), choiceSignatureMatching.end(),
423 [&stateSignature, &representativeSignature](uint64_t const i) {
424 return stateSignature.choices[i].compare(representativeSignature.choices[i]) == std::strong_ordering::equal;
425 }),
426 "In exact mode, choice signatures are expected to be equal for states in the same block.");
427 } else {
428 // In approximate mode, this is in fact a matching problem on a bipartite graph.
429 auto optionalChoiceSignatureMatching = storm::utility::findPerfectMatching(
430 stateSignature.choices.size(), [&stateSignature, &representativeSignature, this](uint64_t const stateChoice, uint64_t const representativeChoice) {
431 return stateSignature.choices[stateChoice].approximatelyEqual(representativeSignature.choices[representativeChoice], halfTolerance);
432 });
433 // Such a matching does not have to exist: approximate equality is not transitive, so two choice signatures of the state can have the same, single
434 // approximately equal partner in the representative's signature, which is not enough of a reason to split the block during refinement.
435 STORM_LOG_THROW(optionalChoiceSignatureMatching.has_value(), storm::exceptions::UnexpectedException,
436 "Unable to match the choices of state " << state << " with the choices of the state that represents it in the quotient. "
437 << "Try again with a smaller tolerance.");
438 choiceSignatureMatching = std::move(*optionalChoiceSignatureMatching);
439 }
440
441 for (uint64_t const choiceIndex : choiceIndices) {
442 auto [it, found] = stateSignature.find(getChoiceSignature(choiceIndex), halfTolerance);
443 STORM_LOG_ASSERT(found, "Expected to find the signature of a choice in the signature of its own state");
444 uint64_t const indexInStateSignature = std::distance(stateSignature.choices.begin(), it);
445 uint64_t const indexInRepresentativeSignature = choiceSignatureMatching[indexInStateSignature];
446 STORM_LOG_ASSERT(choiceSignatureToQuotientChoiceIndex[indexInRepresentativeSignature] != Invalid,
447 "Expected to have already seen this representative choice");
448 toQuotientChoice[choiceIndex] = choiceSignatureToQuotientChoiceIndex[indexInRepresentativeSignature];
449 }
450
451 // Finally, assert that we indeed have a surjective mapping
452 STORM_LOG_ASSERT(([&]() {
453 std::set<uint64_t> seenQuotientChoices;
454 for (uint64_t const choiceIndex : choiceIndices) {
455 seenQuotientChoices.insert(toQuotientChoice[choiceIndex]);
456 }
457 return seenQuotientChoices.size() == representativeSignature.choices.size();
458 }()),
459 "The mapping from state choices to quotient choices is not surjective.");
460}
461
462template<typename ValueType, SignatureMode Mode>
463void Signatures<ValueType, Mode>::extendQuotientData(QuotientData<ValueType>& quotientData, bool const createQuotientChoiceMapping) const {
464 auto& signatureData = quotientData.signatureData.emplace();
465 if (createQuotientChoiceMapping) {
466 quotientData.toQuotientChoice.emplace(model.getNumberOfChoices(), 0);
467 }
468 signatureData.quotientChoiceGroupIndices.reserve(quotientData.toRepresentativeState.size() + 1);
469
470 // Scratch space for mappings, reused for every state.
471 std::vector<uint64_t> choiceSignatureToQuotientChoiceIndex; // Maps choiceSignature indices to quotient model choice indices
472 constexpr uint64_t Invalid = std::numeric_limits<uint64_t>::max();
473
474 for (uint64_t quotientState = 0; quotientState < quotientData.toRepresentativeState.size(); ++quotientState) {
475 auto const representativeState = quotientData.toRepresentativeState[quotientState];
476 uint64_t const firstQuotientChoiceIndex = signatureData.toRepresentativeChoice.size();
477 signatureData.quotientChoiceGroupIndices.push_back(firstQuotientChoiceIndex);
478 // Relies on performSignatureBasedRefinement's postcondition that all cached signatures (including singleton blocks) are up to date.
479 auto const& representativeSignature = stateSignatureCache[representativeState];
480
481 // At the quotientState, the choices should appear in the same order as the represented choices at the representativeState. This prevents unexpected
482 // effects downstream (e.g. different quality of the initial policy in policy iteration). For Markov automata with hybrid states, this is even necessary
483 // to ensure that the Markovian choice remains the first one.
484 // The choiceSignatureToQuotientChoiceIndex mapping below permutes the choices as they appear in representativeSignature into the right order.
485 choiceSignatureToQuotientChoiceIndex.assign(representativeSignature.choices.size(), Invalid);
486 for (uint64_t const choiceIndex : model.getTransitionMatrix().getRowGroupIndices(representativeState)) {
487 auto [it, found] = representativeSignature.find(getChoiceSignature(choiceIndex), halfTolerance);
488 STORM_LOG_ASSERT(found, "Expected to find the signature of representative state");
489 uint64_t const choiceSignatureIndex = std::distance(representativeSignature.choices.begin(), it);
490 if (choiceSignatureToQuotientChoiceIndex[choiceSignatureIndex] == Invalid) {
491 // We see this representative choice for the first time. Establish some mappings
492 uint64_t const quotientChoiceIndex = signatureData.toRepresentativeChoice.size();
493 choiceSignatureToQuotientChoiceIndex[choiceSignatureIndex] = quotientChoiceIndex;
494 signatureData.toRepresentativeChoice.push_back(choiceIndex);
495 if (createQuotientChoiceMapping) {
496 (*quotientData.toQuotientChoice)[choiceIndex] = quotientChoiceIndex;
497 }
498 // Fill distribution from signature
499 auto& distr = signatureData.quotientChoiceDistributions.emplace_back();
500 for (auto const& [successorBlock, value] : it->distr) {
501 distr.emplace(quotientData.toQuotientState[successorBlock.front()], value);
502 }
503 } else {
504 // We have already seen this representative choice before.
505 if (createQuotientChoiceMapping) {
506 (*quotientData.toQuotientChoice)[choiceIndex] = choiceSignatureToQuotientChoiceIndex[choiceSignatureIndex];
507 }
508 }
509 }
510 if (createQuotientChoiceMapping) {
511 // Handle choice mappings for the other states of the block. This is comparatively expensive, since (unlike the representative's own choices
512 // above) it visits every choice of every non-representative state, so it is skipped entirely if the caller does not need toQuotientChoice.
513 for (uint64_t const state : partition.getBlockOfElement(representativeState)) {
514 if (state != representativeState) {
515 addQuotientChoiceMapping(state, representativeSignature, choiceSignatureToQuotientChoiceIndex, *quotientData.toQuotientChoice);
516 }
517 }
518 }
519 }
520 signatureData.quotientChoiceGroupIndices.push_back(signatureData.toRepresentativeChoice.size());
521 signatureData.quotientChoiceGroupIndices.shrink_to_fit();
522 signatureData.toRepresentativeChoice.shrink_to_fit();
523 signatureData.quotientChoiceDistributions.shrink_to_fit();
524}
525
531
532} // namespace storm::bisimulation
Represents a partition of a set of consecutive indices.
Definition Partition.h:20
Represents a strict weak order over state indices based on their signature.
Definition Signatures.h:48
bool operator()(uint64_t const state1, uint64_t const state2) const
Computes and caches the state signatures and provides state-based order / condition for splitting in ...
Definition Signatures.h:24
void extendQuotientData(QuotientData< ValueType > &quotientData, bool const createQuotientChoiceMapping) const
Fills in the choice mappings of the given (already state-mapped) quotient data: for every quotient st...
void updateStateSignature(uint64_t const stateIndex)
Updates the state signature of the given state index.
Signatures(storm::models::sparse::Model< ValueType > const &model, std::optional< std::vector< uint64_t > > const &choiceClasses, storm::bisimulation::Partition const &partition)
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
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:39
SFTBDDChecker::ValueType ValueType
std::vector< T > buildVectorForRange(T min, T max)
Constructs a vector [min, min+1, ...., max-1].
Definition vector.h:130
std::optional< std::vector< uint64_t > > findPerfectMatching(uint64_t const n, EdgePredicate &&hasEdge)
Definition matching.h:18
bool isZero(ValueType const &a)
Definition constants.cpp:44
ValueType abs(ValueType const &number)
ValueType zero()
Definition constants.cpp:26
The index mappings between an input model and its bisimulation quotient, derived from a partition of ...
std::vector< uint64_t > toRepresentativeState
std::vector< uint64_t > toQuotientState
std::optional< SignatureData > signatureData
std::optional< std::vector< uint64_t > > toQuotientChoice
Represents a condition for splitting two states based on their signature.
Definition Signatures.h:75
bool operator()(uint64_t const state1, uint64_t const state2) const