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