Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
DeterministicModelBisimulationDecomposition.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <unordered_map>
6
14#include "storm/utility/graph.h"
15
16namespace storm {
17namespace storage {
18
19using namespace bisimulation;
20
21template<typename ModelType>
25 probabilitiesToCurrentSplitter(model.getNumberOfStates(), storm::utility::zero<ValueType>()) {
26 // Intentionally left empty.
27}
28
29template<typename ModelType>
30std::pair<storm::storage::BitVector, storm::storage::BitVector> DeterministicModelBisimulationDecomposition<ModelType>::getStatesWithProbability01() {
31 return storm::utility::graph::performProb01(this->backwardTransitions, this->options.phiStates.get(), this->options.psiStates.get());
32}
33
34template<typename ModelType>
36 std::vector<storm::storage::sparse::state_type> stateStack;
37 stateStack.reserve(this->model.getNumberOfStates());
38 storm::storage::BitVector nondivergentStates(this->model.getNumberOfStates());
39
40 uint_fast64_t currentSize = this->partition.size();
41 for (uint_fast64_t blockIndex = 0; blockIndex < currentSize; ++blockIndex) {
42 auto& block = *this->partition.getBlocks()[blockIndex];
43 nondivergentStates.clear();
44
45 for (auto stateIt = this->partition.begin(block), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) {
46 if (nondivergentStates.get(*stateIt)) {
47 continue;
48 }
49
50 // Now traverse the forward transitions of the current state and check whether there is a
51 // transition to some other block.
52 bool isDirectlyNonDivergent = false;
53 for (auto const& successor : this->model.getTransitionMatrix().getRowGroup(*stateIt)) {
54 // If there is such a transition, then we can mark all states in the current block that can
55 // reach the state as non-divergent.
56 if (this->partition.getBlock(successor.getColumn()) != block) {
57 isDirectlyNonDivergent = true;
58 break;
59 }
60 }
61
62 if (isDirectlyNonDivergent) {
63 stateStack.push_back(*stateIt);
64
65 while (!stateStack.empty()) {
66 storm::storage::sparse::state_type currentState = stateStack.back();
67 stateStack.pop_back();
68 nondivergentStates.set(currentState);
69
70 for (auto const& predecessor : this->backwardTransitions.getRow(currentState)) {
71 if (this->partition.getBlock(predecessor.getColumn()) == block && !nondivergentStates.get(predecessor.getColumn())) {
72 stateStack.push_back(predecessor.getColumn());
73 }
74 }
75 }
76 }
77 }
78
79 if (!nondivergentStates.empty() && nondivergentStates.getNumberOfSetBits() != block.getNumberOfStates()) {
80 // After performing the split, the current block will contain the divergent states only.
81 this->partition.splitStates(block, nondivergentStates);
82
83 // Since the remaining states in the block are divergent, we can mark the block as absorbing.
84 // This also guarantees that the self-loop will be added to the state of the quotient
85 // representing this block of states.
86 block.data().setAbsorbing(true);
87 } else if (nondivergentStates.empty()) {
88 // If there are only diverging states in the block, we need to make it absorbing.
89 block.data().setAbsorbing(true);
90 }
91 }
92}
93
94template<typename ModelType>
95void DeterministicModelBisimulationDecomposition<ModelType>::initializeSilentProbabilities() {
96 silentProbabilities.resize(this->model.getNumberOfStates(), storm::utility::zero<ValueType>());
97 for (storm::storage::sparse::state_type state = 0; state < this->model.getNumberOfStates(); ++state) {
98 Block<BlockDataType> const* currentBlockPtr = &this->partition.getBlock(state);
99 for (auto const& successorEntry : this->model.getTransitionMatrix().getRowGroup(state)) {
100 if (&this->partition.getBlock(successorEntry.getColumn()) == currentBlockPtr) {
101 silentProbabilities[state] += successorEntry.getValue();
102 }
103 }
104 }
105}
106
107template<typename ModelType>
108void DeterministicModelBisimulationDecomposition<ModelType>::initializeWeakDtmcBisimulation() {
109 // If we are creating the initial partition for weak bisimulation on DTMCs, we need to (a) split off all
110 // divergent states of each initial block and (b) initialize the vector of silent probabilities.
111 this->splitOffDivergentStates();
112 this->initializeSilentProbabilities();
113}
114
115template<typename ModelType>
116void DeterministicModelBisimulationDecomposition<ModelType>::postProcessInitialPartition() {
117 if (this->options.getType() == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc) {
118 this->initializeWeakDtmcBisimulation();
119 }
120
121 if (this->options.getKeepRewards() && this->model.hasRewardModel() && this->options.getType() == BisimulationType::Weak) {
122 // For a weak bisimulation that is to preserve reward properties, we have to flag all blocks of states
123 // with non-zero reward as reward blocks so they can be refined wrt. strong bisimulation.
124
125 // Here, we assume that the initial partition already respects state (and action) rewards. Therefore, it suffices to
126 // check the first state of each block for a non-zero reward.
127 std::optional<std::vector<ValueType>> const& optionalStateRewardVector = this->model.getUniqueRewardModel().getOptionalStateRewardVector();
128 std::optional<std::vector<ValueType>> const& optionalStateActionRewardVector = this->model.getUniqueRewardModel().getOptionalStateActionRewardVector();
129 for (auto& block : this->partition.getBlocks()) {
130 auto state = *this->partition.begin(*block);
131 block->data().setHasRewards((optionalStateRewardVector && !storm::utility::isZero(optionalStateRewardVector.value()[state])) ||
132 (optionalStateActionRewardVector && !storm::utility::isZero(optionalStateActionRewardVector.value()[state])));
133 }
134 }
135}
136
137template<typename ModelType>
142
143template<typename ModelType>
148
149template<typename ModelType>
152 return probabilitiesToCurrentSplitter[state];
153}
154
155template<typename ModelType>
156bool DeterministicModelBisimulationDecomposition<ModelType>::isSilent(storm::storage::sparse::state_type const& state) const {
157 return this->comparator.isOne(silentProbabilities[state]);
158}
159
160template<typename ModelType>
161bool DeterministicModelBisimulationDecomposition<ModelType>::hasNonZeroSilentProbability(storm::storage::sparse::state_type const& state) const {
162 return !this->comparator.isZero(silentProbabilities[state]);
163}
164
165template<typename ModelType>
166typename DeterministicModelBisimulationDecomposition<ModelType>::ValueType DeterministicModelBisimulationDecomposition<ModelType>::getSilentProbability(
167 storm::storage::sparse::state_type const& state) const {
168 return silentProbabilities[state];
169}
170
171template<typename ModelType>
172void DeterministicModelBisimulationDecomposition<ModelType>::refinePredecessorBlockOfSplitterStrong(
173 bisimulation::Block<BlockDataType>& block, std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
174 STORM_LOG_TRACE("Refining predecessor " << block.getId() << " of splitter");
175
176 // Depending on the actions we need to take, the block to refine changes, so we need to keep track of it.
177 Block<BlockDataType>* blockToRefineProbabilistically = &block;
178
179 bool split = false;
180 // If the new begin index has shifted to a non-trivial position, we need to split the block.
181 if (block.getBeginIndex() != block.data().marker1() && block.getEndIndex() != block.data().marker1()) {
182 split = true;
183 this->partition.splitBlock(block, block.data().marker1());
184 blockToRefineProbabilistically = block.getPreviousBlockPointer();
185
186 // Keep track of whether this is a block with reward states.
187 blockToRefineProbabilistically->data().setHasRewards(block.data().hasRewards());
188 }
189
190 split |= this->partition.splitBlock(
191 *blockToRefineProbabilistically,
193 return this->comparator.isLess(getProbabilityToSplitter(state1), getProbabilityToSplitter(state2));
194 },
195 [&splitterQueue, &block](Block<BlockDataType>& newBlock) {
196 splitterQueue.emplace_back(&newBlock);
197 newBlock.data().setSplitter();
198
199 // Keep track of whether this is a block with reward states.
200 newBlock.data().setHasRewards(block.data().hasRewards());
201 });
202
203 // If the predecessor block was split, we need to insert it into the splitter vector if it is not already
204 // marked as a splitter.
205 if (split && !blockToRefineProbabilistically->data().splitter()) {
206 splitterQueue.emplace_back(blockToRefineProbabilistically);
207 blockToRefineProbabilistically->data().setSplitter();
208 }
209}
210
211template<typename ModelType>
212void DeterministicModelBisimulationDecomposition<ModelType>::refinePredecessorBlocksOfSplitterStrong(
213 std::list<Block<BlockDataType>*> const& predecessorBlocks, std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
214 for (auto block : predecessorBlocks) {
215 refinePredecessorBlockOfSplitterStrong(*block, splitterQueue);
216
217 // If the block was *not* split, we need to reset the markers by notifying the data.
218 block->resetMarkers();
219
220 // Remember that we have refined the block.
221 block->data().setNeedsRefinement(false);
222 }
223}
224
225template<typename ModelType>
226bool DeterministicModelBisimulationDecomposition<ModelType>::possiblyNeedsRefinement(bisimulation::Block<BlockDataType> const& predecessorBlock) const {
227 return predecessorBlock.getNumberOfStates() > 1 && !predecessorBlock.data().absorbing();
228}
229
230template<typename ModelType>
231void DeterministicModelBisimulationDecomposition<ModelType>::increaseProbabilityToSplitter(storm::storage::sparse::state_type predecessor,
232 bisimulation::Block<BlockDataType> const& predecessorBlock,
233 ValueType const& value) {
234 STORM_LOG_TRACE("Increasing probability of " << predecessor << " to splitter by " << value << ".");
235 storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
236
237 // If the position of the state is to the right of marker1, we have not seen it before.
238 if (predecessorPosition >= predecessorBlock.data().marker1()) {
239 // Then, we just set the value.
240 probabilitiesToCurrentSplitter[predecessor] = value;
241 } else {
242 // If the state was seen as a predecessor before, we add the value to the existing value.
243 probabilitiesToCurrentSplitter[predecessor] += value;
244 }
245}
246
247template<typename ModelType>
248void DeterministicModelBisimulationDecomposition<ModelType>::moveStateToMarker1(storm::storage::sparse::state_type predecessor,
249 bisimulation::Block<BlockDataType>& predecessorBlock) {
250 this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker1()));
251 predecessorBlock.data().incrementMarker1();
252}
253
254template<typename ModelType>
255void DeterministicModelBisimulationDecomposition<ModelType>::moveStateToMarker2(storm::storage::sparse::state_type predecessor,
256 bisimulation::Block<BlockDataType>& predecessorBlock) {
257 this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker2()));
258 predecessorBlock.data().incrementMarker2();
259}
260
261template<typename ModelType>
262void DeterministicModelBisimulationDecomposition<ModelType>::moveStateInSplitter(storm::storage::sparse::state_type predecessor,
263 bisimulation::Block<BlockDataType>& predecessorBlock,
264 storm::storage::sparse::state_type currentPositionInSplitter,
265 uint_fast64_t& elementsToSkip) {
266 storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
267
268 // If the predecessors of the given predecessor were already explored, we can move it easily.
269 if (predecessorPosition <= currentPositionInSplitter + elementsToSkip) {
270 this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker1()));
271 predecessorBlock.data().incrementMarker1();
272 } else {
273 // Otherwise, we need to move the predecessor, but we need to make sure that we explore its
274 // predecessors later. We do this by moving it to a range at the beginning of the block that will hold
275 // all predecessors in the splitter whose predecessors have yet to be explored.
276 if (predecessorBlock.data().marker2() == predecessorBlock.data().marker1()) {
277 this->partition.swapStatesAtPositions(predecessorBlock.data().marker2(), predecessorPosition);
278 this->partition.swapStatesAtPositions(predecessorPosition, currentPositionInSplitter + elementsToSkip + 1);
279 } else {
280 this->partition.swapStatesAtPositions(predecessorBlock.data().marker2(), predecessorPosition);
281 this->partition.swapStatesAtPositions(predecessorPosition, predecessorBlock.data().marker1());
282 this->partition.swapStatesAtPositions(predecessorPosition, currentPositionInSplitter + elementsToSkip + 1);
283 }
284
285 // Since we had to move an already explored state to the right of the current position,
286 ++elementsToSkip;
287 predecessorBlock.data().incrementMarker1();
288 predecessorBlock.data().incrementMarker2();
289 }
290}
291
292template<typename ModelType>
293void DeterministicModelBisimulationDecomposition<ModelType>::exploreRemainingStatesOfSplitter(
294 bisimulation::Block<BlockDataType>& splitter, std::list<bisimulation::Block<BlockDataType>*>& predecessorBlocks) {
295 for (auto splitterIt = this->partition.begin(splitter),
296 splitterIte = this->partition.begin(splitter) + (splitter.data().marker2() - splitter.getBeginIndex());
297 splitterIt != splitterIte; ++splitterIt) {
298 storm::storage::sparse::state_type currentState = *splitterIt;
299
300 for (auto const& predecessorEntry : this->backwardTransitions.getRow(currentState)) {
301 storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn();
302 Block<BlockDataType>& predecessorBlock = this->partition.getBlock(predecessor);
303
304 // If the block does not need to be refined, we skip it.
305 if (!possiblyNeedsRefinement(predecessorBlock)) {
306 continue;
307 }
308
309 // If we are computing a weak bisimulation on CTMCs and the predecessor block is the splitter, we
310 // need to ignore it and proceed to the next predecessor.
311 if (this->options.getType() == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Ctmc && predecessorBlock == splitter) {
312 continue;
313 }
314
315 // We keep track of the probability of the predecessor moving to the splitter.
316 increaseProbabilityToSplitter(predecessor, predecessorBlock, predecessorEntry.getValue());
317
318 // Only move the state if it has not been seen as a predecessor before.
319 storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
320 if (predecessorPosition >= predecessorBlock.data().marker1()) {
321 moveStateToMarker1(predecessor, predecessorBlock);
322 }
323
324 // We must not insert the the splitter itself if we are not computing a weak bisimulation on CTMCs.
325 if (this->options.getType() != BisimulationType::Weak || this->model.getType() != storm::models::ModelType::Ctmc || predecessorBlock != splitter) {
326 insertIntoPredecessorList(predecessorBlock, predecessorBlocks);
327 }
328 }
329 }
330
331 // Finally, we can reset the second marker.
332 splitter.data().setMarker2(splitter.getBeginIndex());
333}
334
335template<typename ModelType>
336void DeterministicModelBisimulationDecomposition<ModelType>::updateSilentProbabilitiesBasedOnProbabilitiesToSplitter(
337 bisimulation::Block<BlockDataType>& block) {
338 // For all predecessors, we can set the probability to the current probability of moving to the splitter.
339 for (auto stateIt = this->partition.begin(block), stateIte = this->partition.begin() + block.data().marker1(); stateIt != stateIte; ++stateIt) {
340 silentProbabilities[*stateIt] = probabilitiesToCurrentSplitter[*stateIt];
341 }
342 // All non-predecessors have a silent probability of zero.
343 for (auto stateIt = this->partition.begin() + block.data().marker1(), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) {
344 silentProbabilities[*stateIt] = storm::utility::zero<ValueType>();
345 }
346}
347
348template<typename ModelType>
349void DeterministicModelBisimulationDecomposition<ModelType>::updateSilentProbabilitiesBasedOnTransitions(bisimulation::Block<BlockDataType>& block) {
350 for (auto stateIt = this->partition.begin(block), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) {
351 if (hasNonZeroSilentProbability(*stateIt)) {
352 ValueType newSilentProbability = storm::utility::zero<ValueType>();
353 for (auto const& successorEntry : this->model.getTransitionMatrix().getRow(*stateIt)) {
354 if (this->partition.getBlock(successorEntry.getColumn()) == block) {
355 newSilentProbability += successorEntry.getValue();
356 }
357 }
358 silentProbabilities[*stateIt] = newSilentProbability;
359 }
360 }
361}
362
363template<typename ModelType>
364void DeterministicModelBisimulationDecomposition<ModelType>::computeConditionalProbabilitiesForNonSilentStates(bisimulation::Block<BlockDataType>& block) {
365 for (auto stateIt = this->partition.begin() + block.getBeginIndex(), stateIte = this->partition.begin() + block.data().marker1(); stateIt != stateIte;
366 ++stateIt) {
367 if (!this->comparator.isOne(getSilentProbability(*stateIt))) {
368 probabilitiesToCurrentSplitter[*stateIt] /= storm::utility::one<ValueType>() - getSilentProbability(*stateIt);
369 }
370 }
371}
372
373template<typename ModelType>
374std::vector<uint_fast64_t> DeterministicModelBisimulationDecomposition<ModelType>::computeNonSilentBlocks(bisimulation::Block<BlockDataType>& block) {
376 return probabilitiesToCurrentSplitter[state1] < probabilitiesToCurrentSplitter[state2];
377 };
378 this->partition.sortRange(block.getBeginIndex(), block.data().marker1(), less);
379 return this->partition.computeRangesOfEqualValue(block.getBeginIndex(), block.data().marker1(), less);
380}
381
382template<typename ModelType>
383std::vector<storm::storage::BitVector> DeterministicModelBisimulationDecomposition<ModelType>::computeWeakStateLabelingBasedOnNonSilentBlocks(
384 bisimulation::Block<BlockDataType> const& block, std::vector<uint_fast64_t> const& nonSilentBlockIndices) {
385 // Now that we have the split points of the non-silent states, we perform a backward search from
386 // each non-silent state and label the predecessors with the class of the non-silent state.
387 std::vector<storm::storage::BitVector> stateLabels(block.getNumberOfStates(), storm::storage::BitVector(nonSilentBlockIndices.size() - 1));
388
389 std::vector<storm::storage::sparse::state_type> stateStack;
390 stateStack.reserve(block.getNumberOfStates());
391 for (uint_fast64_t stateClassIndex = 0; stateClassIndex < nonSilentBlockIndices.size() - 1; ++stateClassIndex) {
392 for (auto stateIt = this->partition.begin() + nonSilentBlockIndices[stateClassIndex],
393 stateIte = this->partition.begin() + nonSilentBlockIndices[stateClassIndex + 1];
394 stateIt != stateIte; ++stateIt) {
395 stateStack.push_back(*stateIt);
396 stateLabels[this->partition.getPosition(*stateIt) - block.getBeginIndex()].set(stateClassIndex);
397 while (!stateStack.empty()) {
398 storm::storage::sparse::state_type currentState = stateStack.back();
399 stateStack.pop_back();
400
401 for (auto const& predecessorEntry : this->backwardTransitions.getRow(currentState)) {
402 storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn();
403
404 if (storm::utility::isZero(predecessorEntry.getValue())) {
405 continue;
406 }
407
408 // Only if the state is in the same block, is a silent state and it has not yet been
409 // labeled with the current label.
410 if (this->partition.getBlock(predecessor) == block && isSilent(predecessor) &&
411 !stateLabels[this->partition.getPosition(predecessor) - block.getBeginIndex()].get(stateClassIndex)) {
412 stateStack.push_back(predecessor);
413 stateLabels[this->partition.getPosition(predecessor) - block.getBeginIndex()].set(stateClassIndex);
414 }
415 }
416 }
417 }
418 }
419
420 return stateLabels;
421}
422
423template<typename ModelType>
424void DeterministicModelBisimulationDecomposition<ModelType>::refinePredecessorBlockOfSplitterWeak(
425 bisimulation::Block<BlockDataType>& block, std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
426 // First, we need to turn the one-step probabilities to go to the splitter to the conditional probabilities
427 // for all non-silent states.
428 computeConditionalProbabilitiesForNonSilentStates(block);
429
430 // Then, we need to compute a labeling of the states that expresses which of the non-silent blocks they can reach.
431 std::vector<uint_fast64_t> nonSilentBlockIndices = computeNonSilentBlocks(block);
432 std::vector<storm::storage::BitVector> weakStateLabels = computeWeakStateLabelingBasedOnNonSilentBlocks(block, nonSilentBlockIndices);
433
434 // Then split the block according to this labeling.
435 // CAUTION: that this assumes that the positions of the states in the partition are not update until after
436 // the sorting is over. Otherwise, this interferes with the data used in the sorting process.
437 storm::storage::sparse::state_type originalBlockIndex = block.getBeginIndex();
438 auto split = this->partition.splitBlock(
439 block,
440 [&weakStateLabels, originalBlockIndex, this](storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) {
441 return weakStateLabels[this->partition.getPosition(state1) - originalBlockIndex] <
442 weakStateLabels[this->partition.getPosition(state2) - originalBlockIndex];
443 },
444 [this, &splitterQueue, &block](bisimulation::Block<BlockDataType>& newBlock) {
445 updateSilentProbabilitiesBasedOnTransitions(newBlock);
446
447 // Insert the new block as a splitter.
448 newBlock.data().setSplitter();
449 splitterQueue.emplace_back(&newBlock);
450
451 // Keep track of whether this is a block with reward states.
452 newBlock.data().setHasRewards(block.data().hasRewards());
453 });
454
455 // If the block was split, we also update the silent probabilities.
456 if (split) {
457 updateSilentProbabilitiesBasedOnTransitions(block);
458
459 if (!block.data().splitter()) {
460 // Insert the new block as a splitter.
461 block.data().setSplitter();
462 splitterQueue.emplace_back(&block);
463 }
464 }
465}
466
467template<typename ModelType>
468void DeterministicModelBisimulationDecomposition<ModelType>::refinePredecessorBlocksOfSplitterWeak(
469 bisimulation::Block<BlockDataType>& splitter, std::list<bisimulation::Block<BlockDataType>*> const& predecessorBlocks,
470 std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
471 for (auto block : predecessorBlocks) {
472 if (block->data().hasRewards()) {
473 refinePredecessorBlockOfSplitterStrong(*block, splitterQueue);
474 } else {
475 if (*block != splitter) {
476 refinePredecessorBlockOfSplitterWeak(*block, splitterQueue);
477 } else {
478 // If the block to split is the splitter itself, we must not do any splitting here.
479 }
480 }
481
482 block->resetMarkers();
483 block->data().setNeedsRefinement(false);
484 }
485}
486
487template<typename ModelType>
488void DeterministicModelBisimulationDecomposition<ModelType>::insertIntoPredecessorList(bisimulation::Block<BlockDataType>& predecessorBlock,
489 std::list<bisimulation::Block<BlockDataType>*>& predecessorBlocks) {
490 // Insert the block into the list of blocks to refine (if that has not already happened).
491 if (!predecessorBlock.data().needsRefinement()) {
492 predecessorBlocks.emplace_back(&predecessorBlock);
493 predecessorBlock.data().setNeedsRefinement();
494 }
495}
496
497template<typename ModelType>
499 std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
500 STORM_LOG_TRACE("Refining partition based on splitter " << splitter.getId());
501
502 // The outline of the refinement is as follows.
503 //
504 // We iterate over all states of the splitter and determine for each predecessor the state the probability
505 // entering the splitter. These probabilities are written to a member vector so that after the iteration
506 // process we have the probabilities of all predecessors of the splitter of entering the splitter in one
507 // step. To directly separate the states having a transition into the splitter from the ones who do not,
508 // we move the states to certain locations. That is, on encountering a predecessor of the splitter, it is
509 // moved to the beginning of its block. If the predecessor is in the splitter itself, we have to be a bit
510 // careful about where to move states.
511 //
512 // After this iteration, there may be states of the splitter whose predecessors have not yet been explored,
513 // so this needs to be done now.
514 //
515 // Finally, we use the information obtained in the first part for the actual splitting process in which all
516 // predecessor blocks of the splitter are split based on the probabilities computed earlier.
517 std::list<Block<BlockDataType>*> predecessorBlocks;
518 storm::storage::sparse::state_type currentPosition = splitter.getBeginIndex();
519 bool splitterIsPredecessorBlock = false;
520 for (auto splitterIt = this->partition.begin(splitter), splitterIte = this->partition.end(splitter); splitterIt != splitterIte;
521 ++splitterIt, ++currentPosition) {
522 storm::storage::sparse::state_type currentState = *splitterIt;
523
524 uint_fast64_t elementsToSkip = 0;
525 for (auto const& predecessorEntry : this->backwardTransitions.getRow(currentState)) {
526 storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn();
527 storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
528 Block<BlockDataType>& predecessorBlock = this->partition.getBlock(predecessor);
529
530 // If the block does not need to be refined, we skip it.
531 if (!possiblyNeedsRefinement(predecessorBlock)) {
532 continue;
533 }
534
535 // If we are computing a weak bisimulation on CTMCs and the predecessor block is the splitter, we
536 // need to ignore it and proceed to the next predecessor.
537 if (this->options.getType() == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Ctmc && predecessorBlock == splitter) {
538 continue;
539 }
540
541 // We keep track of the probability of the predecessor moving to the splitter.
542 increaseProbabilityToSplitter(predecessor, predecessorBlock, predecessorEntry.getValue());
543
544 // We only need to move the predecessor if its not already known as a predecessor already.
545 if (predecessorPosition >= predecessorBlock.data().marker1()) {
546 // If the predecessor block is not the splitter, we can move the state easily.
547 if (predecessorBlock != splitter) {
548 moveStateToMarker1(predecessor, predecessorBlock);
549 } else {
550 // If the predecessor is in the splitter, we need to be a bit more careful.
551 splitterIsPredecessorBlock = true;
552 moveStateInSplitter(predecessor, predecessorBlock, currentPosition, elementsToSkip);
553 }
554
555 insertIntoPredecessorList(predecessorBlock, predecessorBlocks);
556 }
557 }
558
559 // If, as a consequence of shifting states, we need to skip some elements, do so now.
560 splitterIt += elementsToSkip;
561 currentPosition += elementsToSkip;
562 }
563
564 // If the splitter was a predecessor block of itself, we potentially need to explore some states that have
565 // not been explored yet.
566 if (splitterIsPredecessorBlock) {
567 exploreRemainingStatesOfSplitter(splitter, predecessorBlocks);
568 }
569
570 // Finally, we split the block based on the precomputed probabilities and the chosen bisimulation type.
571 if (this->options.getType() == BisimulationType::Strong || this->model.getType() == storm::models::ModelType::Ctmc) {
572 // In the case of CTMCs and weak bisimulation, we still call the "splitStrong" method, but we already have
573 // taken care of not adding the splitter to the predecessor blocks, so this is safe.
574 refinePredecessorBlocksOfSplitterStrong(predecessorBlocks, splitterQueue);
575 } else {
576 // If the splitter is a predecessor of we can use the computed probabilities to update the silent
577 // probabilities.
578 if (splitterIsPredecessorBlock) {
579 updateSilentProbabilitiesBasedOnProbabilitiesToSplitter(splitter);
580 }
581
582 refinePredecessorBlocksOfSplitterWeak(splitter, predecessorBlocks, splitterQueue);
583 }
584}
585
586template<typename ModelType>
588 // In order to create the quotient model, we need to construct
589 // (a) the new transition matrix,
590 // (b) the new labeling,
591 // (c) the new reward structures.
592
593 // Prepare a matrix builder for (a).
594 storm::storage::SparseMatrixBuilder<ValueType> builder(this->size(), this->size());
595
596 // Prepare the new state labeling for (b).
597 storm::models::sparse::StateLabeling newLabeling(this->size());
598 std::set<std::string> atomicPropositionsSet = this->options.respectedAtomicPropositions.get();
599 atomicPropositionsSet.insert("init");
600 std::vector<std::string> atomicPropositions = std::vector<std::string>(atomicPropositionsSet.begin(), atomicPropositionsSet.end());
601 for (auto const& ap : atomicPropositions) {
602 newLabeling.addLabel(ap);
603 }
604
605 // If the model had state rewards, we need to build the state rewards for the quotient as well.
606 std::optional<std::vector<ValueType>> stateRewards;
607 if (this->options.getKeepRewards() && this->model.hasRewardModel()) {
608 stateRewards = std::vector<ValueType>(this->blocks.size());
609 }
610
611 // Now build (a) and (b) by traversing all blocks.
612 for (uint_fast64_t blockIndex = 0; blockIndex < this->blocks.size(); ++blockIndex) {
613 auto const& block = this->blocks[blockIndex];
614
615 // Pick one representative state. For strong bisimulation it doesn't matter which state it is, because
616 // they all behave equally.
617 storm::storage::sparse::state_type representativeState = *block.begin();
618
619 // However, for weak bisimulation, we need to make sure the representative state is a non-silent one (if
620 // there is any such state).
621 if (this->options.getType() == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc) {
622 for (auto const& state : block) {
623 if (!isSilent(state)) {
624 representativeState = state;
625 break;
626 }
627 }
628 }
629
630 Block<BlockDataType> const& oldBlock = this->partition.getBlock(representativeState);
631
632 // If the block is absorbing, we simply add a self-loop.
633 if (oldBlock.data().absorbing()) {
634 builder.addNextValue(blockIndex, blockIndex, storm::utility::one<ValueType>());
635
636 // If the block has a special representative state, we retrieve it now.
637 if (oldBlock.data().hasRepresentativeState()) {
638 representativeState = oldBlock.data().representativeState();
639 }
640
641 // Add all of the selected atomic propositions that hold in the representative state to the state
642 // representing the block.
643 for (auto const& ap : atomicPropositions) {
644 if (this->model.getStateLabeling().getStateHasLabel(ap, representativeState)) {
645 newLabeling.addLabelToState(ap, blockIndex);
646 }
647 }
648 } else {
649 // Compute the outgoing transitions of the block.
650 std::map<storm::storage::sparse::state_type, ValueType> blockProbability;
651 for (auto const& entry : this->model.getTransitionMatrix().getRow(representativeState)) {
652 storm::storage::sparse::state_type targetBlock = this->partition.getBlock(entry.getColumn()).getId();
653
654 // If we are computing a weak bisimulation quotient, there is no need to add self-loops.
655 if ((this->options.getType() == BisimulationType::Weak) && targetBlock == blockIndex && !oldBlock.data().hasRewards()) {
656 continue;
657 }
658
659 auto probIterator = blockProbability.find(targetBlock);
660 if (probIterator != blockProbability.end()) {
661 probIterator->second += entry.getValue();
662 } else {
663 blockProbability[targetBlock] = entry.getValue();
664 }
665 }
666
667 // Now add them to the actual matrix.
668 for (auto const& probabilityEntry : blockProbability) {
669 if (this->options.getType() == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc &&
670 !oldBlock.data().hasRewards()) {
671 builder.addNextValue(blockIndex, probabilityEntry.first,
672 probabilityEntry.second / (storm::utility::one<ValueType>() - getSilentProbability(representativeState)));
673 } else {
674 builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second);
675 }
676 }
677
678 // Otherwise add all atomic propositions to the equivalence class that the representative state
679 // satisfies.
680 for (auto const& ap : atomicPropositions) {
681 if (this->model.getStateLabeling().getStateHasLabel(ap, representativeState)) {
682 newLabeling.addLabelToState(ap, blockIndex);
683 }
684 }
685 }
686
687 // If the model has state rewards, we simply copy the state reward of the representative state, because
688 // all states in a block are guaranteed to have the same state reward.
689 if (this->options.getKeepRewards() && this->model.hasRewardModel()) {
690 auto const& rewardModel = this->model.getUniqueRewardModel();
691 if (rewardModel.hasStateRewards()) {
692 stateRewards.value()[blockIndex] = rewardModel.getStateRewardVector()[representativeState];
693 }
694 if (rewardModel.hasStateActionRewards()) {
695 stateRewards.value()[blockIndex] += rewardModel.getStateActionRewardVector()[representativeState];
696 }
697 }
698 }
699
700 // Now check which of the blocks of the partition contain at least one initial state.
701 for (auto initialState : this->model.getInitialStates()) {
702 Block<BlockDataType> const& initialBlock = this->partition.getBlock(initialState);
703 newLabeling.addLabelToState("init", initialBlock.getId());
704 }
705
706 // Construct the reward model mapping.
707 std::unordered_map<std::string, typename ModelType::RewardModelType> rewardModels;
708 if (this->options.getKeepRewards() && this->model.hasRewardModel()) {
709 STORM_LOG_THROW(this->model.hasUniqueRewardModel(), storm::exceptions::IllegalFunctionCallException, "Cannot preserve more than one reward model.");
710 typename std::unordered_map<std::string, typename ModelType::RewardModelType>::const_iterator nameRewardModelPair =
711 this->model.getRewardModels().begin();
712 rewardModels.insert(std::make_pair(nameRewardModelPair->first, typename ModelType::RewardModelType(stateRewards)));
713 }
714
715 // Finally construct the quotient model.
716 this->quotient = std::shared_ptr<ModelType>(new ModelType(builder.build(), std::move(newLabeling), std::move(rewardModels)));
717}
718
721
724
727} // namespace storage
728} // namespace storm
void addLabel(std::string const &label)
Adds a new label to the labelings.
This class manages the labeling of the state space with a number of (atomic) labels.
void addLabelToState(std::string const &label, storm::storage::sparse::state_type state)
Adds a label to a given state.
This class is the superclass of all decompositions of a sparse model into its bisimulation quotient.
virtual void initializeMeasureDrivenPartition()
Creates the measure-driven initial partition for reaching psi states from phi states.
virtual void initializeLabelBasedPartition()
Initializes the initial partition based on all respected labels.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
This class represents the decomposition of a deterministic model into its bisimulation quotient.
virtual void refinePartitionBasedOnSplitter(bisimulation::Block< BlockDataType > &splitter, std::vector< bisimulation::Block< BlockDataType > * > &splitterQueue) override
virtual void initializeLabelBasedPartition() override
Initializes the initial partition based on all respected labels.
virtual void initializeMeasureDrivenPartition() override
Creates the measure-driven initial partition for reaching psi states from phi states.
DeterministicModelBisimulationDecomposition(ModelType const &model, typename BisimulationDecomposition< ModelType, BlockDataType >::Options const &options=typename BisimulationDecomposition< ModelType, BlockDataType >::Options())
Computes the bisimulation relation for the given model.
virtual void buildQuotient() override
Builds the quotient model based on the previously computed equivalence classes (stored in the blocks ...
virtual std::pair< storm::storage::BitVector, storm::storage::BitVector > getStatesWithProbability01() override
Computes the set of states with probability 0/1 for satisfying phi until psi.
A class that can be used to build a sparse matrix by adding value by value.
void addNextValue(index_type row, index_type column, value_type const &value)
Sets the matrix entry at the given row and column to the given value.
SparseMatrix< value_type > build(index_type overriddenRowCount=0, index_type overriddenColumnCount=0, index_type overriddenRowGroupCount=0)
storm::storage::sparse::state_type getBeginIndex() const
Definition Block.cpp:69
std::size_t getId() const
Definition Block.cpp:64
#define STORM_LOG_TRACE(message)
Definition logging.h:12
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
SFTBDDChecker::ValueType ValueType
std::pair< storm::storage::BitVector, storm::storage::BitVector > performProb01(storm::models::sparse::DeterministicModel< T > const &model, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates)
Computes the sets of states that have probability 0 or 1, respectively, of satisfying phi until psi i...
Definition graph.cpp:393
bool isZero(ValueType const &a)
Definition constants.cpp:39