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