Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
SparseDtmcEliminationModelChecker.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <random>
6
23#include "storm/utility/graph.h"
27
28namespace storm {
29namespace modelchecker {
30
32
33template<typename SparseDtmcModelType>
38
39template<typename SparseDtmcModelType>
49
50template<typename SparseDtmcModelType>
53 storm::logic::StateFormula const& stateFormula = checkTask.getFormula();
54 std::unique_ptr<CheckResult> subResultPointer = this->check(stateFormula);
55 storm::storage::BitVector const& psiStates = subResultPointer->asExplicitQualitativeCheckResult().getTruthValuesVector();
56
57 storm::storage::SparseMatrix<ValueType> const& transitionMatrix = this->getModel().getTransitionMatrix();
58 uint_fast64_t numberOfStates = transitionMatrix.getRowCount();
59 if (psiStates.empty()) {
60 return std::unique_ptr<CheckResult>(
61 new ExplicitQuantitativeCheckResult<ValueType>(std::vector<ValueType>(numberOfStates, storm::utility::zero<ValueType>())));
62 }
63 if (psiStates.full()) {
64 return std::unique_ptr<CheckResult>(
65 new ExplicitQuantitativeCheckResult<ValueType>(std::vector<ValueType>(numberOfStates, storm::utility::one<ValueType>())));
66 }
67
68 storm::storage::BitVector const& initialStates = this->getModel().getInitialStates();
69 STORM_LOG_THROW(initialStates.getNumberOfSetBits() == 1, storm::exceptions::IllegalArgumentException,
70 "Input model is required to have exactly one initial state.");
71 STORM_LOG_THROW(checkTask.isOnlyInitialStatesRelevantSet(), storm::exceptions::IllegalArgumentException,
72 "Cannot compute long-run probabilities for all states.");
73
74 storm::storage::SparseMatrix<ValueType> backwardTransitions = this->getModel().getBackwardTransitions();
75 storm::storage::BitVector maybeStates =
76 storm::utility::graph::performProbGreater0(backwardTransitions, storm::storage::BitVector(transitionMatrix.getRowCount(), true), psiStates);
77
78 std::vector<ValueType> result(transitionMatrix.getRowCount(), storm::utility::zero<ValueType>());
79
80 // Determine whether we need to perform some further computation.
81 bool furtherComputationNeeded = true;
82 if (checkTask.isOnlyInitialStatesRelevantSet() && initialStates.isDisjointFrom(maybeStates)) {
83 STORM_LOG_DEBUG("The long-run probability for all initial states was found in a preprocessing step.");
84 furtherComputationNeeded = false;
85 }
86 if (maybeStates.empty()) {
87 STORM_LOG_DEBUG("The long-run probability for all states was found in a preprocessing step.");
88 furtherComputationNeeded = false;
89 }
90
91 if (furtherComputationNeeded) {
92 if (checkTask.isOnlyInitialStatesRelevantSet()) {
93 // Determine the set of states that is reachable from the initial state without jumping over a target state.
95 transitionMatrix, initialStates, storm::storage::BitVector(numberOfStates, true), storm::storage::BitVector(numberOfStates, false));
96
97 // Subtract from the maybe states the set of states that is not reachable (on a path from the initial to a target state).
98 maybeStates &= reachableStates;
99 }
100
101 std::vector<ValueType> stateValues(maybeStates.size(), storm::utility::zero<ValueType>());
102 storm::utility::vector::setVectorValues(stateValues, psiStates, storm::utility::one<ValueType>());
103 result =
104 computeLongRunValues(transitionMatrix, backwardTransitions, initialStates, maybeStates, checkTask.isOnlyInitialStatesRelevantSet(), stateValues);
105 }
106
107 // Construct check result based on whether we have computed values for all states or just the initial states.
108 std::unique_ptr<CheckResult> checkResult(new ExplicitQuantitativeCheckResult<ValueType>(result));
109 if (checkTask.isOnlyInitialStatesRelevantSet()) {
110 // If we computed the results for the initial states only, we need to filter the result to only
111 // communicate these results.
112 checkResult->filter(ExplicitQualitativeCheckResult(initialStates));
113 }
114 return checkResult;
115}
116
117template<typename SparseDtmcModelType>
120 // Do some sanity checks to establish some required properties.
121 RewardModelType const& rewardModel = this->getModel().getRewardModel(checkTask.isRewardModelSet() ? checkTask.getRewardModel() : "");
122 STORM_LOG_THROW(!rewardModel.empty(), storm::exceptions::IllegalArgumentException, "Input model does not have a reward model.");
123
124 storm::storage::BitVector const& initialStates = this->getModel().getInitialStates();
125 STORM_LOG_THROW(initialStates.getNumberOfSetBits() == 1, storm::exceptions::IllegalArgumentException,
126 "Input model is required to have exactly one initial state.");
127 STORM_LOG_THROW(checkTask.isOnlyInitialStatesRelevantSet(), storm::exceptions::IllegalArgumentException,
128 "Cannot compute long-run probabilities for all states.");
129
130 storm::storage::SparseMatrix<ValueType> const& transitionMatrix = this->getModel().getTransitionMatrix();
131 uint_fast64_t numberOfStates = transitionMatrix.getRowCount();
132
133 // Get the state-reward values from the reward model.
134 std::vector<ValueType> stateRewardValues = rewardModel.getTotalRewardVector(this->getModel().getTransitionMatrix());
135
136 storm::storage::BitVector maybeStates(stateRewardValues.size());
137 uint_fast64_t index = 0;
138 for (auto const& value : stateRewardValues) {
139 if (value != storm::utility::zero<ValueType>()) {
140 maybeStates.set(index, true);
141 }
142 ++index;
143 }
144
145 storm::storage::SparseMatrix<ValueType> backwardTransitions = this->getModel().getBackwardTransitions();
146
147 storm::storage::BitVector allStates(numberOfStates, true);
148 maybeStates = storm::utility::graph::performProbGreater0(backwardTransitions, allStates, maybeStates);
149
150 std::vector<ValueType> result(numberOfStates, storm::utility::zero<ValueType>());
151
152 // Determine whether we need to perform some further computation.
153 bool furtherComputationNeeded = true;
154 if (checkTask.isOnlyInitialStatesRelevantSet() && initialStates.isDisjointFrom(maybeStates)) {
155 furtherComputationNeeded = false;
156 }
157
158 if (furtherComputationNeeded) {
159 if (checkTask.isOnlyInitialStatesRelevantSet()) {
160 // Determine the set of states that is reachable from the initial state without jumping over a target state.
162 transitionMatrix, initialStates, storm::storage::BitVector(numberOfStates, true), storm::storage::BitVector(numberOfStates, false));
163
164 // Subtract from the maybe states the set of states that is not reachable (on a path from the initial to a target state).
165 maybeStates &= reachableStates;
166 }
167
168 result = computeLongRunValues(transitionMatrix, backwardTransitions, initialStates, maybeStates, checkTask.isOnlyInitialStatesRelevantSet(),
169 stateRewardValues);
170 }
171
172 // Construct check result based on whether we have computed values for all states or just the initial states.
173 std::unique_ptr<CheckResult> checkResult(new ExplicitQuantitativeCheckResult<ValueType>(result));
174 if (checkTask.isOnlyInitialStatesRelevantSet()) {
175 // If we computed the results for the initial states only, we need to filter the result to only
176 // communicate these results.
177 checkResult->filter(ExplicitQualitativeCheckResult(initialStates));
178 }
179 return checkResult;
180}
181
182template<typename SparseDtmcModelType>
183std::vector<typename SparseDtmcEliminationModelChecker<SparseDtmcModelType>::ValueType>
185 storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
186 storm::storage::BitVector const& initialStates,
187 storm::storage::BitVector const& maybeStates,
188 bool computeResultsForInitialStatesOnly, std::vector<ValueType>& stateValues) {
189 std::chrono::high_resolution_clock::time_point totalTimeStart = std::chrono::high_resolution_clock::now();
190
191 // Start by decomposing the DTMC into its BSCCs.
192 std::chrono::high_resolution_clock::time_point sccDecompositionStart = std::chrono::high_resolution_clock::now();
194 transitionMatrix, storm::storage::StronglyConnectedComponentDecompositionOptions().onlyBottomSccs());
195 auto sccDecompositionEnd = std::chrono::high_resolution_clock::now();
196
197 std::chrono::high_resolution_clock::time_point conversionStart = std::chrono::high_resolution_clock::now();
198
199 // Then, we convert the reduced matrix to a more flexible format to be able to perform state elimination more easily.
200 storm::storage::FlexibleSparseMatrix<ValueType> flexibleMatrix(transitionMatrix);
201 flexibleMatrix.filterEntries(maybeStates, maybeStates);
202 storm::storage::FlexibleSparseMatrix<ValueType> flexibleBackwardTransitions(backwardTransitions);
203 flexibleBackwardTransitions.filterEntries(maybeStates, maybeStates);
204 auto conversionEnd = std::chrono::high_resolution_clock::now();
205
206 std::chrono::high_resolution_clock::time_point modelCheckingStart = std::chrono::high_resolution_clock::now();
207
209 storm::settings::getModule<storm::settings::modules::EliminationSettings>().getEliminationOrder();
210 boost::optional<std::vector<uint_fast64_t>> distanceBasedPriorities;
212 distanceBasedPriorities = getDistanceBasedPriorities(transitionMatrix, backwardTransitions, initialStates, stateValues,
214 }
215
216 uint_fast64_t numberOfStates = transitionMatrix.getRowCount();
217 storm::storage::BitVector regularStatesInBsccs(numberOfStates);
218 storm::storage::BitVector relevantBsccs(bsccDecomposition.size());
219 storm::storage::BitVector bsccRepresentativesAsBitVector(numberOfStates);
220 std::vector<storm::storage::sparse::state_type> bsccRepresentatives;
221 uint_fast64_t currentIndex = 0;
222 for (auto const& bscc : bsccDecomposition) {
223 // Since all states in an SCC can reach all other states, we only need to check whether an arbitrary
224 // state is a maybe state.
225 if (maybeStates.get(*bscc.cbegin())) {
226 relevantBsccs.set(currentIndex);
227 bsccRepresentatives.push_back(*bscc.cbegin());
228 bsccRepresentativesAsBitVector.set(*bscc.cbegin(), true);
229 for (auto const& state : bscc) {
230 regularStatesInBsccs.set(state, true);
231 }
232 }
233 ++currentIndex;
234 }
235 regularStatesInBsccs &= ~bsccRepresentativesAsBitVector;
236
237 // Compute the average time to stay in each state for all states in BSCCs.
238 std::vector<ValueType> averageTimeInStates(stateValues.size(), storm::utility::one<ValueType>());
239
240 // First, we eliminate all states in BSCCs (except for the representative states).
241 std::shared_ptr<StatePriorityQueue> priorityQueue =
242 createStatePriorityQueue(distanceBasedPriorities, flexibleMatrix, flexibleBackwardTransitions, stateValues, regularStatesInBsccs);
243 storm::solver::stateelimination::MultiValueStateEliminator<ValueType> stateEliminator(flexibleMatrix, flexibleBackwardTransitions, priorityQueue,
244 stateValues, averageTimeInStates);
245
246 while (priorityQueue->hasNext()) {
247 storm::storage::sparse::state_type state = priorityQueue->pop();
248 stateEliminator.eliminateState(state, true);
249#ifdef STORM_DEV
250 STORM_LOG_ASSERT(checkConsistent(flexibleMatrix, flexibleBackwardTransitions), "The forward and backward transition matrices became inconsistent.");
251#endif
252 }
253
254 // Now, we set the values of all states in BSCCs to that of the representative value (and clear the
255 // transitions of the representative states while doing so).
256 auto representativeIt = bsccRepresentatives.begin();
257 for (auto sccIndex : relevantBsccs) {
258 // We only need to set the values for all states of the BSCC if we are not computing the values for the
259 // initial states only.
260 ValueType bsccValue = stateValues[*representativeIt] / averageTimeInStates[*representativeIt];
261 auto const& bscc = bsccDecomposition[sccIndex];
262 if (!computeResultsForInitialStatesOnly) {
263 for (auto const& state : bscc) {
264 stateValues[state] = bsccValue;
265 }
266 } else {
267 for (auto const& state : bscc) {
268 stateValues[state] = storm::utility::zero<ValueType>();
269 }
270 stateValues[*representativeIt] = bsccValue;
271 }
272
273 FlexibleRowType& representativeForwardRow = flexibleMatrix.getRow(*representativeIt);
274 representativeForwardRow.clear();
275 representativeForwardRow.shrink_to_fit();
276
277 FlexibleRowType& representativeBackwardRow = flexibleBackwardTransitions.getRow(*representativeIt);
278 auto it = representativeBackwardRow.begin(), ite = representativeBackwardRow.end();
279 for (; it != ite; ++it) {
280 if (it->getColumn() == *representativeIt) {
281 break;
282 }
283 }
284 representativeBackwardRow.erase(it);
285
286 ++representativeIt;
287 }
288
289 // If there are states remaining that are not in BSCCs, we need to eliminate them now.
290 storm::storage::BitVector remainingStates = maybeStates & ~regularStatesInBsccs;
291
292 // Set the value initial value of all states not in a BSCC to zero, because a) any previous value would
293 // incorrectly influence the result and b) the value have been erroneously changed for the predecessors of
294 // BSCCs by the previous state elimination.
295 for (auto state : remainingStates) {
296 if (!bsccRepresentativesAsBitVector.get(state)) {
297 stateValues[state] = storm::utility::zero<ValueType>();
298 }
299 }
300
301 // We only need to eliminate the remaining states if there was some BSCC that has a non-zero value, i.e.
302 // that consists of maybe states.
303 if (!relevantBsccs.empty()) {
304 performOrdinaryStateElimination(flexibleMatrix, flexibleBackwardTransitions, remainingStates, initialStates, computeResultsForInitialStatesOnly,
305 stateValues, distanceBasedPriorities);
306 }
307
308 std::chrono::high_resolution_clock::time_point modelCheckingEnd = std::chrono::high_resolution_clock::now();
309 std::chrono::high_resolution_clock::time_point totalTimeEnd = std::chrono::high_resolution_clock::now();
310
311 if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) {
312 std::chrono::high_resolution_clock::duration sccDecompositionTime = sccDecompositionEnd - sccDecompositionStart;
313 std::chrono::milliseconds sccDecompositionTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(sccDecompositionTime);
314 std::chrono::high_resolution_clock::duration conversionTime = conversionEnd - conversionStart;
315 std::chrono::milliseconds conversionTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(conversionTime);
316 std::chrono::high_resolution_clock::duration modelCheckingTime = modelCheckingEnd - modelCheckingStart;
317 std::chrono::milliseconds modelCheckingTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(modelCheckingTime);
318 std::chrono::high_resolution_clock::duration totalTime = totalTimeEnd - totalTimeStart;
319 std::chrono::milliseconds totalTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(totalTime);
320
322 STORM_PRINT_AND_LOG("Time breakdown:\n");
323 STORM_PRINT_AND_LOG(" * time for SCC decomposition: " << sccDecompositionTimeInMilliseconds.count() << "ms\n");
324 STORM_PRINT_AND_LOG(" * time for conversion: " << conversionTimeInMilliseconds.count() << "ms\n");
325 STORM_PRINT_AND_LOG(" * time for checking: " << modelCheckingTimeInMilliseconds.count() << "ms\n");
326 STORM_PRINT_AND_LOG("------------------------------------------\n");
327 STORM_PRINT_AND_LOG(" * total time: " << totalTimeInMilliseconds.count() << "ms\n");
328 }
329
330 // Now, we return the value for the only initial state.
331 STORM_LOG_DEBUG("Simplifying and returning result.");
332 for (auto& value : stateValues) {
333 value = storm::utility::simplify(value);
334 }
335 return stateValues;
336}
337
338template<typename SparseDtmcModelType>
341 storm::logic::BoundedUntilFormula const& pathFormula = checkTask.getFormula();
342
343 STORM_LOG_THROW(!pathFormula.hasLowerBound() && pathFormula.hasUpperBound(), storm::exceptions::InvalidPropertyException,
344 "Formula needs to have single upper time bound.");
345 STORM_LOG_THROW(pathFormula.hasIntegerUpperBound(), storm::exceptions::InvalidPropertyException, "Formula needs to have discrete upper time bound.");
346
347 // Retrieve the appropriate bitvectors by model checking the subformulas.
348 std::unique_ptr<CheckResult> leftResultPointer = this->check(pathFormula.getLeftSubformula());
349 std::unique_ptr<CheckResult> rightResultPointer = this->check(pathFormula.getRightSubformula());
350 storm::storage::BitVector const& phiStates = leftResultPointer->asExplicitQualitativeCheckResult().getTruthValuesVector();
351 storm::storage::BitVector const& psiStates = rightResultPointer->asExplicitQualitativeCheckResult().getTruthValuesVector();
352
353 // Start by determining the states that have a non-zero probability of reaching the target states within the
354 // time bound.
356 this->getModel().getBackwardTransitions(), phiStates, psiStates, true, pathFormula.getUpperBound<uint64_t>());
357 statesWithProbabilityGreater0 &= ~psiStates;
358
359 // Determine whether we need to perform some further computation.
360 bool furtherComputationNeeded = true;
361 if (checkTask.isOnlyInitialStatesRelevantSet() && this->getModel().getInitialStates().isDisjointFrom(statesWithProbabilityGreater0)) {
362 STORM_LOG_DEBUG("The probability for all initial states was found in a preprocessing step.");
363 furtherComputationNeeded = false;
364 } else if (statesWithProbabilityGreater0.empty()) {
365 STORM_LOG_DEBUG("The probability for all states was found in a preprocessing step.");
366 furtherComputationNeeded = false;
367 }
368
369 storm::storage::SparseMatrix<ValueType> const& transitionMatrix = this->getModel().getTransitionMatrix();
370 storm::storage::BitVector const& initialStates = this->getModel().getInitialStates();
371
372 std::vector<ValueType> result(transitionMatrix.getRowCount(), storm::utility::zero<ValueType>());
373
374 if (furtherComputationNeeded) {
375 uint_fast64_t timeBound = pathFormula.getUpperBound<uint64_t>();
376
377 if (checkTask.isOnlyInitialStatesRelevantSet()) {
378 // Determine the set of states that is reachable from the initial state without jumping over a target state.
379 storm::storage::BitVector reachableStates =
380 storm::utility::graph::getReachableStates(transitionMatrix, initialStates, phiStates, psiStates, true, timeBound);
381
382 // Subtract from the maybe states the set of states that is not reachable (on a path from the initial to a target state).
383 statesWithProbabilityGreater0 &= reachableStates;
384 }
385
386 // We then build the submatrix that only has the transitions of the maybe states.
388 transitionMatrix.getSubmatrix(true, statesWithProbabilityGreater0, statesWithProbabilityGreater0, true);
389
390 std::vector<uint_fast64_t> distancesFromInitialStates;
391 storm::storage::BitVector relevantStates;
392 if (checkTask.isOnlyInitialStatesRelevantSet()) {
393 // Determine the set of initial states of the sub-model.
394 storm::storage::BitVector subInitialStates = this->getModel().getInitialStates() % statesWithProbabilityGreater0;
395
396 // Precompute the distances of the relevant states to the initial states.
397 distancesFromInitialStates = storm::utility::graph::getDistances(submatrix, subInitialStates, statesWithProbabilityGreater0);
398
399 // Set all states to be relevant for later use.
400 relevantStates = storm::storage::BitVector(statesWithProbabilityGreater0.getNumberOfSetBits(), true);
401 }
402
403 // Create the vector of one-step probabilities to go to target states.
404 std::vector<ValueType> b = transitionMatrix.getConstrainedRowSumVector(statesWithProbabilityGreater0, psiStates);
405
406 // Create the vector with which to multiply.
407 std::vector<ValueType> subresult(b);
408 std::vector<ValueType> tmp(subresult.size());
409
410 // Subtract one from the time bound because initializing the sub-result to b already accounts for one step.
411 --timeBound;
412
413 // Perform matrix-vector multiplications until the time-bound is met.
414 for (uint_fast64_t timeStep = 0; timeStep < timeBound; ++timeStep) {
415 submatrix.multiplyWithVector(subresult, tmp);
416 storm::utility::vector::addVectors(tmp, b, subresult);
417
418 // If we are computing the results for the initial states only, we can use the minimal distance from
419 // each state to the initial states to determine whether we still need to consider the values for
420 // these states. If not, we can null-out all their probabilities.
421 if (checkTask.isOnlyInitialStatesRelevantSet()) {
422 for (auto state : relevantStates) {
423 if (distancesFromInitialStates[state] > (timeBound - timeStep)) {
424 for (auto& element : submatrix.getRow(state)) {
425 element.setValue(storm::utility::zero<ValueType>());
426 }
427 b[state] = storm::utility::zero<ValueType>();
428 relevantStates.set(state, false);
429 }
430 }
431 }
432 }
433
434 // Set the values of the resulting vector accordingly.
435 storm::utility::vector::setVectorValues(result, statesWithProbabilityGreater0, subresult);
436 }
437 storm::utility::vector::setVectorValues<ValueType>(result, psiStates, storm::utility::one<ValueType>());
438
439 // Construct check result based on whether we have computed values for all states or just the initial states.
440 std::unique_ptr<CheckResult> checkResult(new ExplicitQuantitativeCheckResult<ValueType>(result));
441 if (checkTask.isOnlyInitialStatesRelevantSet()) {
442 // If we computed the results for the initial (and prob 0 and prob1) states only, we need to filter the
443 // result to only communicate these results.
444 checkResult->filter(ExplicitQualitativeCheckResult(this->getModel().getInitialStates() | psiStates));
445 }
446 return checkResult;
447}
448
449template<typename SparseDtmcModelType>
452 storm::logic::UntilFormula const& pathFormula = checkTask.getFormula();
453
454 // Retrieve the appropriate bitvectors by model checking the subformulas.
455 std::unique_ptr<CheckResult> leftResultPointer = this->check(pathFormula.getLeftSubformula());
456 std::unique_ptr<CheckResult> rightResultPointer = this->check(pathFormula.getRightSubformula());
457 storm::storage::BitVector const& phiStates = leftResultPointer->asExplicitQualitativeCheckResult().getTruthValuesVector();
458 storm::storage::BitVector const& psiStates = rightResultPointer->asExplicitQualitativeCheckResult().getTruthValuesVector();
459
460 return computeUntilProbabilities(this->getModel().getTransitionMatrix(), this->getModel().getBackwardTransitions(), this->getModel().getInitialStates(),
461 phiStates, psiStates, checkTask.isOnlyInitialStatesRelevantSet());
462}
463
464template<typename SparseDtmcModelType>
466 storm::storage::SparseMatrix<ValueType> const& probabilityMatrix, storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
467 storm::storage::BitVector const& initialStates, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates,
468 bool computeForInitialStatesOnly) {
469 // Then, compute the subset of states that has a probability of 0 or 1, respectively.
470 std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 =
471 storm::utility::graph::performProb01(backwardTransitions, phiStates, psiStates);
472 storm::storage::BitVector statesWithProbability0 = statesWithProbability01.first;
473 storm::storage::BitVector statesWithProbability1 = statesWithProbability01.second;
474 storm::storage::BitVector maybeStates = ~(statesWithProbability0 | statesWithProbability1);
475
476 // Determine whether we need to perform some further computation.
477 bool furtherComputationNeeded = true;
478 if (computeForInitialStatesOnly && initialStates.isDisjointFrom(maybeStates)) {
479 STORM_LOG_DEBUG("The probability for all initial states was found in a preprocessing step.");
480 furtherComputationNeeded = false;
481 } else if (maybeStates.empty()) {
482 STORM_LOG_DEBUG("The probability for all states was found in a preprocessing step.");
483 furtherComputationNeeded = false;
484 }
485
486 std::vector<ValueType> result(maybeStates.size());
487 if (furtherComputationNeeded) {
488 // If we compute the results for the initial states only, we can cut off all maybe state that are not
489 // reachable from them.
490 if (computeForInitialStatesOnly) {
491 // Determine the set of states that is reachable from the initial state without jumping over a target state.
492 storm::storage::BitVector reachableStates =
493 storm::utility::graph::getReachableStates(probabilityMatrix, initialStates, maybeStates, statesWithProbability1);
494
495 // Subtract from the maybe states the set of states that is not reachable (on a path from the initial to a target state).
496 maybeStates &= reachableStates;
497 }
498
499 // Create a vector for the probabilities to go to a state with probability 1 in one step.
500 std::vector<ValueType> oneStepProbabilities = probabilityMatrix.getConstrainedRowSumVector(maybeStates, statesWithProbability1);
501
502 // Determine the set of initial states of the sub-model.
503 storm::storage::BitVector newInitialStates = initialStates % maybeStates;
504
505 // We then build the submatrix that only has the transitions of the maybe states.
506 storm::storage::SparseMatrix<ValueType> submatrix = probabilityMatrix.getSubmatrix(false, maybeStates, maybeStates);
507 storm::storage::SparseMatrix<ValueType> submatrixTransposed = submatrix.transpose();
508
509 std::vector<ValueType> subresult = computeReachabilityValues(submatrix, oneStepProbabilities, submatrixTransposed, newInitialStates,
510 computeForInitialStatesOnly, oneStepProbabilities);
511 storm::utility::vector::setVectorValues<ValueType>(result, maybeStates, subresult);
512 }
513
514 // Construct full result.
515 storm::utility::vector::setVectorValues<ValueType>(result, statesWithProbability0, storm::utility::zero<ValueType>());
516 storm::utility::vector::setVectorValues<ValueType>(result, statesWithProbability1, storm::utility::one<ValueType>());
517
518 if (computeForInitialStatesOnly) {
519 // If we computed the results for the initial (and prob 0 and prob1) states only, we need to filter the
520 // result to only communicate these results.
521 std::unique_ptr<ExplicitQuantitativeCheckResult<ValueType>> checkResult = std::make_unique<ExplicitQuantitativeCheckResult<ValueType>>();
522 for (auto state : ~maybeStates | initialStates) {
523 (*checkResult)[state] = result[state];
524 }
525 return std::move(checkResult); // move() required by, e.g., clang 3.8
526 }
527 return std::make_unique<ExplicitQuantitativeCheckResult<ValueType>>(result);
528}
529
530template<typename SparseDtmcModelType>
533 storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula();
534
535 // Retrieve the appropriate bitvectors by model checking the subformulas.
536 std::unique_ptr<CheckResult> subResultPointer = this->check(eventuallyFormula.getSubformula());
537 storm::storage::BitVector trueStates(this->getModel().getNumberOfStates(), true);
538 storm::storage::BitVector const& targetStates = subResultPointer->asExplicitQualitativeCheckResult().getTruthValuesVector();
539
540 // Do some sanity checks to establish some required properties.
541 RewardModelType const& rewardModel = this->getModel().getRewardModel(checkTask.isRewardModelSet() ? checkTask.getRewardModel() : "");
542
543 STORM_LOG_THROW(!rewardModel.empty(), storm::exceptions::IllegalArgumentException, "Input model does not have a reward model.");
544 return computeReachabilityRewards(
545 this->getModel().getTransitionMatrix(), this->getModel().getBackwardTransitions(), this->getModel().getInitialStates(), targetStates,
546 [&](uint_fast64_t numberOfRows, storm::storage::SparseMatrix<ValueType> const& transitionMatrix, storm::storage::BitVector const& maybeStates) {
547 return rewardModel.getTotalRewardVector(numberOfRows, transitionMatrix, maybeStates);
548 },
550}
551
552template<typename SparseDtmcModelType>
554 storm::storage::SparseMatrix<ValueType> const& probabilityMatrix, storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
555 storm::storage::BitVector const& initialStates, storm::storage::BitVector const& targetStates, std::vector<ValueType>& stateRewardValues,
556 bool computeForInitialStatesOnly) {
557 return computeReachabilityRewards(
558 probabilityMatrix, backwardTransitions, initialStates, targetStates,
559 [&](uint_fast64_t numberOfRows, storm::storage::SparseMatrix<ValueType> const&, storm::storage::BitVector const& maybeStates) {
560 std::vector<ValueType> result(numberOfRows);
561 storm::utility::vector::selectVectorValues(result, maybeStates, stateRewardValues);
562 return result;
563 },
564 computeForInitialStatesOnly);
565}
566
567template<typename SparseDtmcModelType>
569 storm::storage::SparseMatrix<ValueType> const& probabilityMatrix, storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
570 storm::storage::BitVector const& initialStates, storm::storage::BitVector const& targetStates,
571 std::function<std::vector<ValueType>(uint_fast64_t, storm::storage::SparseMatrix<ValueType> const&, storm::storage::BitVector const&)> const&
572 totalStateRewardVectorGetter,
573 bool computeForInitialStatesOnly) {
574 uint_fast64_t numberOfStates = probabilityMatrix.getRowCount();
575
576 // Compute the subset of states that has a reachability reward less than infinity.
577 storm::storage::BitVector trueStates(numberOfStates, true);
578 storm::storage::BitVector infinityStates = storm::utility::graph::performProb1(backwardTransitions, trueStates, targetStates);
579 infinityStates.complement();
580 storm::storage::BitVector maybeStates = ~targetStates & ~infinityStates;
581
582 // Determine whether we need to perform some further computation.
583 bool furtherComputationNeeded = true;
584 if (computeForInitialStatesOnly) {
585 if (initialStates.isSubsetOf(infinityStates)) {
586 STORM_LOG_DEBUG("The reward of all initial states was found in a preprocessing step.");
587 furtherComputationNeeded = false;
588 }
589 if (initialStates.isSubsetOf(targetStates)) {
590 STORM_LOG_DEBUG("The reward of all initial states was found in a preprocessing step.");
591 furtherComputationNeeded = false;
592 }
593 }
594
595 std::vector<ValueType> result(maybeStates.size());
596 if (furtherComputationNeeded) {
597 // If we compute the results for the initial states only, we can cut off all maybe state that are not
598 // reachable from them.
599 if (computeForInitialStatesOnly) {
600 // Determine the set of states that is reachable from the initial state without jumping over a target state.
601 storm::storage::BitVector reachableStates = storm::utility::graph::getReachableStates(probabilityMatrix, initialStates, maybeStates, targetStates);
602
603 // Subtract from the maybe states the set of states that is not reachable (on a path from the initial to a target state).
604 maybeStates &= reachableStates;
605 }
606
607 // Determine the set of initial states of the sub-model.
608 storm::storage::BitVector newInitialStates = initialStates % maybeStates;
609
610 // We then build the submatrix that only has the transitions of the maybe states.
611 storm::storage::SparseMatrix<ValueType> submatrix = probabilityMatrix.getSubmatrix(false, maybeStates, maybeStates);
612 storm::storage::SparseMatrix<ValueType> submatrixTransposed = submatrix.transpose();
613
614 // Project the state reward vector to all maybe-states.
615 std::vector<ValueType> stateRewardValues = totalStateRewardVectorGetter(submatrix.getRowCount(), probabilityMatrix, maybeStates);
616
617 std::vector<ValueType> subresult =
618 computeReachabilityValues(submatrix, stateRewardValues, submatrixTransposed, newInitialStates, computeForInitialStatesOnly,
619 probabilityMatrix.getConstrainedRowSumVector(maybeStates, targetStates));
620 storm::utility::vector::setVectorValues<ValueType>(result, maybeStates, subresult);
621 }
622
623 // Construct full result.
624 storm::utility::vector::setVectorValues<ValueType>(result, infinityStates, storm::utility::infinity<ValueType>());
625 storm::utility::vector::setVectorValues<ValueType>(result, targetStates, storm::utility::zero<ValueType>());
626 if (computeForInitialStatesOnly) {
627 // If we computed the results for the initial (and inf) states only, we need to filter the result to
628 // only communicate these results.
629 std::unique_ptr<ExplicitQuantitativeCheckResult<ValueType>> checkResult = std::make_unique<ExplicitQuantitativeCheckResult<ValueType>>();
630 for (auto state : ~maybeStates | initialStates) {
631 (*checkResult)[state] = result[state];
632 }
633 return std::move(checkResult); // move() required by, e.g., clang 3.8
634 }
635 return std::make_unique<ExplicitQuantitativeCheckResult<ValueType>>(result);
636}
637
638template<typename SparseDtmcModelType>
641 storm::logic::ConditionalFormula const& conditionalFormula = checkTask.getFormula();
642
643 // Retrieve the appropriate bitvectors by model checking the subformulas.
644 STORM_LOG_THROW(conditionalFormula.getSubformula().isEventuallyFormula(), storm::exceptions::InvalidPropertyException, "Expected 'eventually' formula.");
645 STORM_LOG_THROW(conditionalFormula.getConditionFormula().isEventuallyFormula(), storm::exceptions::InvalidPropertyException,
646 "Expected 'eventually' formula.");
647
648 std::unique_ptr<CheckResult> leftResultPointer = this->check(conditionalFormula.getSubformula().asEventuallyFormula().getSubformula());
649 std::unique_ptr<CheckResult> rightResultPointer = this->check(conditionalFormula.getConditionFormula().asEventuallyFormula().getSubformula());
650 storm::storage::BitVector phiStates = leftResultPointer->asExplicitQualitativeCheckResult().getTruthValuesVector();
651 storm::storage::BitVector psiStates = rightResultPointer->asExplicitQualitativeCheckResult().getTruthValuesVector();
652 storm::storage::BitVector trueStates(this->getModel().getNumberOfStates(), true);
653
654 // Do some sanity checks to establish some required properties.
655 // STORM_LOG_WARN_COND(storm::settings::getModule<storm::settings::modules::EliminationSettings>().getEliminationMethod() ==
656 // storm::settings::modules::EliminationSettings::EliminationMethod::State, "The chosen elimination method is not available for computing conditional
657 // probabilities. Falling back to regular state elimination.");
658 STORM_LOG_THROW(this->getModel().getInitialStates().getNumberOfSetBits() == 1, storm::exceptions::IllegalArgumentException,
659 "Input model is required to have exactly one initial state.");
660 STORM_LOG_THROW(checkTask.isOnlyInitialStatesRelevantSet(), storm::exceptions::IllegalArgumentException,
661 "Cannot compute conditional probabilities for all states.");
662 storm::storage::sparse::state_type initialState = *this->getModel().getInitialStates().begin();
663
664 storm::storage::SparseMatrix<ValueType> backwardTransitions = this->getModel().getBackwardTransitions();
665
666 // Compute the 'true' psi states, i.e. those psi states that can be reached without passing through another psi state first.
667 psiStates = storm::utility::graph::getReachableStates(this->getModel().getTransitionMatrix(), this->getModel().getInitialStates(), trueStates, psiStates) &
668 psiStates;
669
670 std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 =
671 storm::utility::graph::performProb01(backwardTransitions, trueStates, psiStates);
672 storm::storage::BitVector statesWithProbabilityGreater0 = ~statesWithProbability01.first;
673 storm::storage::BitVector statesWithProbability1 = std::move(statesWithProbability01.second);
674
675 STORM_LOG_THROW(this->getModel().getInitialStates().isSubsetOf(statesWithProbabilityGreater0), storm::exceptions::InvalidPropertyException,
676 "The condition of the conditional probability has zero probability.");
677
678 // If the initial state is known to have probability 1 of satisfying the condition, we can apply regular model checking.
679 if (this->getModel().getInitialStates().isSubsetOf(statesWithProbability1)) {
680 STORM_LOG_INFO("The condition holds with probability 1, so the regular reachability probability is computed.");
681 std::shared_ptr<storm::logic::BooleanLiteralFormula> trueFormula = std::make_shared<storm::logic::BooleanLiteralFormula>(true);
682 std::shared_ptr<storm::logic::UntilFormula> untilFormula =
683 std::make_shared<storm::logic::UntilFormula>(trueFormula, conditionalFormula.getSubformula().asSharedPointer());
684 return this->computeUntilProbabilities(env, *untilFormula);
685 }
686
687 // From now on, we know the condition does not have a trivial probability in the initial state.
688
689 // Compute the states that can be reached on a path that has a psi state in it.
690 storm::storage::BitVector statesWithPsiPredecessor =
691 storm::utility::graph::performProbGreater0(this->getModel().getTransitionMatrix(), trueStates, psiStates);
692 storm::storage::BitVector statesReachingPhi = storm::utility::graph::performProbGreater0(backwardTransitions, trueStates, phiStates);
693
694 // The set of states we need to consider are those that have a non-zero probability to satisfy the condition or are on some path that has a psi state in it.
695 storm::storage::BitVector maybeStates = statesWithProbabilityGreater0 | (statesWithPsiPredecessor & statesReachingPhi);
696
697 // Determine the set of initial states of the sub-DTMC.
698 storm::storage::BitVector newInitialStates = this->getModel().getInitialStates() % maybeStates;
699
700 // Create a dummy vector for the one-step probabilities.
701 std::vector<ValueType> oneStepProbabilities(maybeStates.getNumberOfSetBits(), storm::utility::zero<ValueType>());
702
703 // We then build the submatrix that only has the transitions of the maybe states.
704 storm::storage::SparseMatrix<ValueType> submatrix = this->getModel().getTransitionMatrix().getSubmatrix(false, maybeStates, maybeStates);
705 storm::storage::SparseMatrix<ValueType> submatrixTransposed = submatrix.transpose();
706
707 // The states we want to eliminate are those that are tagged with "maybe" but are not a phi or psi state.
708 phiStates = phiStates % maybeStates;
709
710 // If there are no phi states in the reduced model, the conditional probability is trivially zero.
711 if (phiStates.empty()) {
712 return std::unique_ptr<CheckResult>(new ExplicitQuantitativeCheckResult<ValueType>(initialState, storm::utility::zero<ValueType>()));
713 }
714
715 psiStates = psiStates % maybeStates;
716
717 // Keep only the states that we do not eliminate in the maybe states.
718 maybeStates = phiStates | psiStates;
719
720 storm::storage::BitVector statesToEliminate = ~maybeStates & ~newInitialStates;
721
722 // Before starting the model checking process, we assign priorities to states so we can use them to
723 // impose ordering constraints later.
724 boost::optional<std::vector<uint_fast64_t>> distanceBasedPriorities;
726 storm::settings::getModule<storm::settings::modules::EliminationSettings>().getEliminationOrder();
728 distanceBasedPriorities = getDistanceBasedPriorities(submatrix, submatrixTransposed, newInitialStates, oneStepProbabilities,
730 }
731
732 storm::storage::FlexibleSparseMatrix<ValueType> flexibleMatrix(submatrix);
733 storm::storage::FlexibleSparseMatrix<ValueType> flexibleBackwardTransitions(submatrixTransposed, true);
734
735 std::shared_ptr<StatePriorityQueue> statePriorities =
736 createStatePriorityQueue(distanceBasedPriorities, flexibleMatrix, flexibleBackwardTransitions, oneStepProbabilities, statesToEliminate);
737
738 STORM_LOG_INFO("Computing conditional probilities.\n");
739 uint_fast64_t numberOfStatesToEliminate = statePriorities->size();
740 STORM_LOG_INFO("Eliminating " << numberOfStatesToEliminate << " states using the state elimination technique.\n");
741 performPrioritizedStateElimination(statePriorities, flexibleMatrix, flexibleBackwardTransitions, oneStepProbabilities, this->getModel().getInitialStates(),
742 true);
743
745 storm::solver::stateelimination::ConditionalStateEliminator<ValueType>(flexibleMatrix, flexibleBackwardTransitions, oneStepProbabilities, phiStates,
746 psiStates);
747
748 // Eliminate the transitions going into the initial state (if there are any).
749 if (!flexibleBackwardTransitions.getRow(*newInitialStates.begin()).empty()) {
750 stateEliminator.eliminateState(*newInitialStates.begin(), false);
751 }
752
753 // Now we need to basically eliminate all chains of not-psi states after phi states and chains of not-phi
754 // states after psi states.
755 for (auto const& trans1 : flexibleMatrix.getRow(*newInitialStates.begin())) {
756 auto initialStateSuccessor = trans1.getColumn();
757
758 STORM_LOG_TRACE("Exploring successor " << initialStateSuccessor << " of the initial state.");
759
760 if (phiStates.get(initialStateSuccessor)) {
761 STORM_LOG_TRACE("Is a phi state.");
762
763 // If the state is both a phi and a psi state, we do not need to eliminate chains.
764 if (psiStates.get(initialStateSuccessor)) {
765 continue;
766 }
767
768 // At this point, we know that the state satisfies phi and not psi.
769 // This means, we must compute the probability to reach psi states, which in turn means that we need
770 // to eliminate all chains of non-psi states between the current state and psi states.
771 bool hasNonPsiSuccessor = true;
772 while (hasNonPsiSuccessor) {
773 stateEliminator.setFilterPhi();
774 hasNonPsiSuccessor = false;
775
776 // Only treat the state if it has an outgoing transition other than a self-loop.
777 auto const currentRow = flexibleMatrix.getRow(initialStateSuccessor);
778 if (currentRow.size() > 1 || (!currentRow.empty() && currentRow.front().getColumn() != initialStateSuccessor)) {
779 for (auto const& element : currentRow) {
780 // If any of the successors is a phi state, we eliminate it (wrt. all its phi predecessors).
781 if (!psiStates.get(element.getColumn())) {
782 FlexibleRowType const& successorRow = flexibleMatrix.getRow(element.getColumn());
783 // Eliminate the successor only if there possibly is a psi state reachable through it.
784 if (successorRow.size() > 1 || (!successorRow.empty() && successorRow.front().getColumn() != element.getColumn())) {
785 STORM_LOG_TRACE("Found non-psi successor " << element.getColumn() << " that needs to be eliminated.");
786 stateEliminator.eliminateState(element.getColumn(), false);
787 hasNonPsiSuccessor = true;
788 }
789 }
790 }
791 STORM_LOG_ASSERT(!flexibleMatrix.getRow(initialStateSuccessor).empty(), "(1) New transitions expected to be non-empty.");
792 }
793 }
794 stateEliminator.unsetFilter();
795 } else {
796 STORM_LOG_ASSERT(psiStates.get(initialStateSuccessor), "Expected psi state.");
797 STORM_LOG_TRACE("Is a psi state.");
798
799 // At this point, we know that the state satisfies psi and not phi.
800 // This means, we must compute the probability to reach phi states, which in turn means that we need
801 // to eliminate all chains of non-phi states between the current state and phi states.
802
803 bool hasNonPhiSuccessor = true;
804 while (hasNonPhiSuccessor) {
805 stateEliminator.setFilterPsi();
806 hasNonPhiSuccessor = false;
807
808 // Only treat the state if it has an outgoing transition other than a self-loop.
809 auto const currentRow = flexibleMatrix.getRow(initialStateSuccessor);
810 if (currentRow.size() > 1 || (!currentRow.empty() && currentRow.front().getColumn() != initialStateSuccessor)) {
811 for (auto const& element : currentRow) {
812 // If any of the successors is a psi state, we eliminate it (wrt. all its psi predecessors).
813 if (!phiStates.get(element.getColumn())) {
814 FlexibleRowType const& successorRow = flexibleMatrix.getRow(element.getColumn());
815 if (successorRow.size() > 1 || (!successorRow.empty() && successorRow.front().getColumn() != element.getColumn())) {
816 STORM_LOG_TRACE("Found non-phi successor " << element.getColumn() << " that needs to be eliminated.");
817 stateEliminator.eliminateState(element.getColumn(), false);
818 hasNonPhiSuccessor = true;
819 }
820 }
821 }
822 }
823 }
824 stateEliminator.unsetFilter();
825 }
826 }
827
828 ValueType numerator = storm::utility::zero<ValueType>();
829 ValueType denominator = storm::utility::zero<ValueType>();
830
831 for (auto const& trans1 : flexibleMatrix.getRow(*newInitialStates.begin())) {
832 auto initialStateSuccessor = trans1.getColumn();
833 if (phiStates.get(initialStateSuccessor)) {
834 if (psiStates.get(initialStateSuccessor)) {
835 numerator += trans1.getValue();
836 denominator += trans1.getValue();
837 } else {
838 ValueType additiveTerm = storm::utility::zero<ValueType>();
839 for (auto const& trans2 : flexibleMatrix.getRow(initialStateSuccessor)) {
840 if (psiStates.get(trans2.getColumn())) {
841 additiveTerm += trans2.getValue();
842 }
843 }
844 additiveTerm *= trans1.getValue();
845 numerator += additiveTerm;
846 denominator += additiveTerm;
847 }
848 } else {
849 STORM_LOG_ASSERT(psiStates.get(initialStateSuccessor), "Expected psi state.");
850 denominator += trans1.getValue();
851 ValueType additiveTerm = storm::utility::zero<ValueType>();
852 for (auto const& trans2 : flexibleMatrix.getRow(initialStateSuccessor)) {
853 if (phiStates.get(trans2.getColumn())) {
854 additiveTerm += trans2.getValue();
855 }
856 }
857 numerator += trans1.getValue() * additiveTerm;
858 }
859 }
860
861 return std::unique_ptr<CheckResult>(new ExplicitQuantitativeCheckResult<ValueType>(initialState, numerator / denominator));
862}
863
864template<typename SparseDtmcModelType>
866 std::shared_ptr<StatePriorityQueue>& priorityQueue, storm::storage::FlexibleSparseMatrix<ValueType>& transitionMatrix,
867 storm::storage::FlexibleSparseMatrix<ValueType>& backwardTransitions, std::vector<ValueType>& values, storm::storage::BitVector const& initialStates,
868 bool computeResultsForInitialStatesOnly) {
869 storm::solver::stateelimination::PrioritizedStateEliminator<ValueType> stateEliminator(transitionMatrix, backwardTransitions, priorityQueue, values);
870
871 while (priorityQueue->hasNext()) {
872 storm::storage::sparse::state_type state = priorityQueue->pop();
873 bool removeForwardTransitions = computeResultsForInitialStatesOnly && !initialStates.get(state);
874 stateEliminator.eliminateState(state, removeForwardTransitions);
875 if (removeForwardTransitions) {
876 values[state] = storm::utility::zero<ValueType>();
877 }
878#ifdef STORM_DEV
879 STORM_LOG_ASSERT(checkConsistent(transitionMatrix, backwardTransitions), "The forward and backward transition matrices became inconsistent.");
880#endif
881 }
882}
883
884template<typename SparseDtmcModelType>
885void SparseDtmcEliminationModelChecker<SparseDtmcModelType>::performOrdinaryStateElimination(
887 storm::storage::BitVector const& subsystem, storm::storage::BitVector const& initialStates, bool computeResultsForInitialStatesOnly,
888 std::vector<ValueType>& values, boost::optional<std::vector<uint_fast64_t>> const& distanceBasedPriorities) {
889 std::shared_ptr<StatePriorityQueue> statePriorities =
890 createStatePriorityQueue(distanceBasedPriorities, transitionMatrix, backwardTransitions, values, subsystem);
891
892 std::size_t numberOfStatesToEliminate = statePriorities->size();
893 STORM_LOG_DEBUG("Eliminating " << numberOfStatesToEliminate << " states using the state elimination technique.\n");
894 performPrioritizedStateElimination(statePriorities, transitionMatrix, backwardTransitions, values, initialStates, computeResultsForInitialStatesOnly);
895 STORM_LOG_DEBUG("Eliminated " << numberOfStatesToEliminate << " states.\n");
896}
897
898template<typename SparseDtmcModelType>
899uint_fast64_t SparseDtmcEliminationModelChecker<SparseDtmcModelType>::performHybridStateElimination(
902 storm::storage::BitVector const& initialStates, bool computeResultsForInitialStatesOnly, std::vector<ValueType>& values,
903 boost::optional<std::vector<uint_fast64_t>> const& distanceBasedPriorities) {
904 // When using the hybrid technique, we recursively treat the SCCs up to some size.
905 std::vector<storm::storage::sparse::state_type> entryStateQueue;
906 STORM_LOG_DEBUG("Eliminating " << subsystem.size() << " states using the hybrid elimination technique.\n");
907 uint_fast64_t maximalDepth = treatScc(transitionMatrix, values, initialStates, subsystem, initialStates, forwardTransitions, backwardTransitions, false, 0,
908 storm::settings::getModule<storm::settings::modules::EliminationSettings>().getMaximalSccSize(), entryStateQueue,
909 computeResultsForInitialStatesOnly, distanceBasedPriorities);
910
911 // If the entry states were to be eliminated last, we need to do so now.
912 if (storm::settings::getModule<storm::settings::modules::EliminationSettings>().isEliminateEntryStatesLastSet()) {
913 STORM_LOG_DEBUG("Eliminating " << entryStateQueue.size() << " entry states as a last step.");
914 std::vector<storm::storage::sparse::state_type> sortedStates(entryStateQueue.begin(), entryStateQueue.end());
915 std::shared_ptr<StatePriorityQueue> queuePriorities = std::make_shared<StaticStatePriorityQueue>(sortedStates);
916 performPrioritizedStateElimination(queuePriorities, transitionMatrix, backwardTransitions, values, initialStates, computeResultsForInitialStatesOnly);
917 }
918 STORM_LOG_DEBUG("Eliminated " << subsystem.size() << " states.\n");
919 return maximalDepth;
920}
921
922template<typename SparseDtmcModelType>
923std::vector<typename SparseDtmcEliminationModelChecker<SparseDtmcModelType>::ValueType>
924SparseDtmcEliminationModelChecker<SparseDtmcModelType>::computeReachabilityValues(storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
925 std::vector<ValueType>& values,
926 storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
927 storm::storage::BitVector const& initialStates,
928 bool computeResultsForInitialStatesOnly,
929 std::vector<ValueType> const& oneStepProbabilitiesToTarget) {
930 // Then, we convert the reduced matrix to a more flexible format to be able to perform state elimination more easily.
931 storm::storage::FlexibleSparseMatrix<ValueType> flexibleMatrix(transitionMatrix);
932 storm::storage::FlexibleSparseMatrix<ValueType> flexibleBackwardTransitions(backwardTransitions);
933
935 storm::settings::getModule<storm::settings::modules::EliminationSettings>().getEliminationOrder();
936 boost::optional<std::vector<uint_fast64_t>> distanceBasedPriorities;
938 distanceBasedPriorities = getDistanceBasedPriorities(transitionMatrix, backwardTransitions, initialStates, oneStepProbabilitiesToTarget,
940 }
941
942 // Create a bit vector that represents the subsystem of states we still have to eliminate.
943 storm::storage::BitVector subsystem = storm::storage::BitVector(transitionMatrix.getRowCount(), true);
944
945 if (storm::settings::getModule<storm::settings::modules::EliminationSettings>().getEliminationMethod() ==
947 performOrdinaryStateElimination(flexibleMatrix, flexibleBackwardTransitions, subsystem, initialStates, computeResultsForInitialStatesOnly, values,
948 distanceBasedPriorities);
949 } else if (storm::settings::getModule<storm::settings::modules::EliminationSettings>().getEliminationMethod() ==
951 uint64_t maximalDepth = performHybridStateElimination(transitionMatrix, flexibleMatrix, flexibleBackwardTransitions, subsystem, initialStates,
952 computeResultsForInitialStatesOnly, values, distanceBasedPriorities);
953 STORM_LOG_TRACE("Maximal depth of decomposition was " << maximalDepth << ".");
954 }
955
956 STORM_LOG_ASSERT(flexibleMatrix.empty(), "Not all transitions were eliminated.");
957 STORM_LOG_ASSERT(flexibleBackwardTransitions.empty(), "Not all transitions were eliminated.");
958
959 // Now, we return the value for the only initial state.
960 STORM_LOG_DEBUG("Simplifying and returning result.");
961 for (auto& value : values) {
962 value = storm::utility::simplify(value);
963 }
964 return values;
965}
966
967template<typename SparseDtmcModelType>
968uint_fast64_t SparseDtmcEliminationModelChecker<SparseDtmcModelType>::treatScc(
969 storm::storage::FlexibleSparseMatrix<ValueType>& matrix, std::vector<ValueType>& values, storm::storage::BitVector const& entryStates,
970 storm::storage::BitVector const& scc, storm::storage::BitVector const& initialStates, storm::storage::SparseMatrix<ValueType> const& forwardTransitions,
971 storm::storage::FlexibleSparseMatrix<ValueType>& backwardTransitions, bool eliminateEntryStates, uint_fast64_t level, uint_fast64_t maximalSccSize,
972 std::vector<storm::storage::sparse::state_type>& entryStateQueue, bool computeResultsForInitialStatesOnly,
973 boost::optional<std::vector<uint_fast64_t>> const& distanceBasedPriorities) {
974 uint_fast64_t maximalDepth = level;
975
976 // If the SCCs are large enough, we try to split them further.
977 if (scc.getNumberOfSetBits() > maximalSccSize) {
978 STORM_LOG_TRACE("SCC is large enough (" << scc.getNumberOfSetBits() << " states) to be decomposed further.");
979
980 // Here, we further decompose the SCC into sub-SCCs.
981 storm::storage::BitVector nonEntrySccStates = scc & ~entryStates;
983 forwardTransitions, storm::storage::StronglyConnectedComponentDecompositionOptions().subsystem(nonEntrySccStates));
984 STORM_LOG_TRACE("Decomposed SCC into " << decomposition.size() << " sub-SCCs.");
985
986 // Store a bit vector of remaining SCCs so we can be flexible when it comes to the order in which
987 // we eliminate the SCCs.
988 storm::storage::BitVector remainingSccs(decomposition.size(), true);
989
990 // First, get rid of the trivial SCCs.
991 storm::storage::BitVector statesInTrivialSccs(matrix.getRowCount());
992 for (uint_fast64_t sccIndex = 0; sccIndex < decomposition.size(); ++sccIndex) {
993 storm::storage::StronglyConnectedComponent const& scc = decomposition.getBlock(sccIndex);
994 if (scc.isTrivial()) {
995 // Put the only state of the trivial SCC into the set of states to eliminate.
996 statesInTrivialSccs.set(*scc.begin(), true);
997 remainingSccs.set(sccIndex, false);
998 }
999 }
1000
1001 std::shared_ptr<StatePriorityQueue> statePriorities =
1002 createStatePriorityQueue(distanceBasedPriorities, matrix, backwardTransitions, values, statesInTrivialSccs);
1003 STORM_LOG_TRACE("Eliminating " << statePriorities->size() << " trivial SCCs.");
1004 performPrioritizedStateElimination(statePriorities, matrix, backwardTransitions, values, initialStates, computeResultsForInitialStatesOnly);
1005 STORM_LOG_TRACE("Eliminated all trivial SCCs.");
1006
1007 // And then recursively treat the remaining sub-SCCs.
1008 STORM_LOG_TRACE("Eliminating " << remainingSccs.getNumberOfSetBits() << " remaining SCCs on level " << level << ".");
1009 for (auto sccIndex : remainingSccs) {
1010 storm::storage::StronglyConnectedComponent const& newScc = decomposition.getBlock(sccIndex);
1011
1012 // Rewrite SCC into bit vector and subtract it from the remaining states.
1013 storm::storage::BitVector newSccAsBitVector(forwardTransitions.getRowCount(), newScc.begin(), newScc.end());
1014
1015 // Determine the set of entry states of the SCC.
1016 storm::storage::BitVector entryStates(forwardTransitions.getRowCount());
1017 for (auto const& state : newScc) {
1018 for (auto const& predecessor : backwardTransitions.getRow(state)) {
1019 if (predecessor.getValue() != storm::utility::zero<ValueType>() && !newSccAsBitVector.get(predecessor.getColumn())) {
1020 entryStates.set(state);
1021 }
1022 }
1023 }
1024
1025 // Recursively descend in SCC-hierarchy.
1026 uint_fast64_t depth =
1027 treatScc(matrix, values, entryStates, newSccAsBitVector, initialStates, forwardTransitions, backwardTransitions,
1028 eliminateEntryStates || !storm::settings::getModule<storm::settings::modules::EliminationSettings>().isEliminateEntryStatesLastSet(),
1029 level + 1, maximalSccSize, entryStateQueue, computeResultsForInitialStatesOnly, distanceBasedPriorities);
1030 maximalDepth = std::max(maximalDepth, depth);
1031 }
1032 } else {
1033 // In this case, we perform simple state elimination in the current SCC.
1034 STORM_LOG_TRACE("SCC of size " << scc.getNumberOfSetBits() << " is small enough to be eliminated directly.");
1035 std::shared_ptr<StatePriorityQueue> statePriorities =
1036 createStatePriorityQueue(distanceBasedPriorities, matrix, backwardTransitions, values, scc & ~entryStates);
1037 performPrioritizedStateElimination(statePriorities, matrix, backwardTransitions, values, initialStates, computeResultsForInitialStatesOnly);
1038 STORM_LOG_TRACE("Eliminated all states of SCC.");
1039 }
1040
1041 // Finally, eliminate the entry states (if we are required to do so).
1042 if (eliminateEntryStates) {
1043 STORM_LOG_TRACE("Finally, eliminating entry states.");
1044 std::shared_ptr<StatePriorityQueue> naivePriorities = createStatePriorityQueue(entryStates);
1045 performPrioritizedStateElimination(naivePriorities, matrix, backwardTransitions, values, initialStates, computeResultsForInitialStatesOnly);
1046 STORM_LOG_TRACE("Eliminated/added entry states.");
1047 } else {
1048 STORM_LOG_TRACE("Finally, adding entry states to queue.");
1049 for (auto state : entryStates) {
1050 entryStateQueue.push_back(state);
1051 }
1052 }
1053
1054 return maximalDepth;
1055}
1056
1057template<typename SparseDtmcModelType>
1058bool SparseDtmcEliminationModelChecker<SparseDtmcModelType>::checkConsistent(storm::storage::FlexibleSparseMatrix<ValueType>& transitionMatrix,
1060 for (uint_fast64_t forwardIndex = 0; forwardIndex < transitionMatrix.getRowCount(); ++forwardIndex) {
1061 for (auto const& forwardEntry : transitionMatrix.getRow(forwardIndex)) {
1062 if (forwardEntry.getColumn() == forwardIndex) {
1063 continue;
1064 }
1065
1066 bool foundCorrespondingElement = false;
1067 for (auto const& backwardEntry : backwardTransitions.getRow(forwardEntry.getColumn())) {
1068 if (backwardEntry.getColumn() == forwardIndex) {
1069 foundCorrespondingElement = true;
1070 }
1071 }
1072
1073 if (!foundCorrespondingElement) {
1074 return false;
1075 }
1076 }
1077 }
1078 return true;
1079}
1080
1081template class SparseDtmcEliminationModelChecker<storm::models::sparse::Dtmc<double>>;
1082
1083template class SparseDtmcEliminationModelChecker<storm::models::sparse::Dtmc<storm::RationalNumber>>;
1084template class SparseDtmcEliminationModelChecker<storm::models::sparse::Dtmc<storm::RationalFunction>>;
1085} // namespace modelchecker
1086} // namespace storm
Formula const & getRightSubformula() const
Formula const & getLeftSubformula() const
storm::expressions::Expression const & getUpperBound(unsigned i=0) const
bool hasIntegerUpperBound(unsigned i=0) const
Formula const & getConditionFormula() const
Formula const & getSubformula() const
EventuallyFormula & asEventuallyFormula()
Definition Formula.cpp:341
bool isInFragment(FragmentSpecification const &fragment) const
Definition Formula.cpp:204
virtual bool isEventuallyFormula() const
Definition Formula.cpp:88
std::shared_ptr< Formula const > asSharedPointer()
Definition Formula.cpp:571
FragmentSpecification & setOnlyEventuallyFormuluasInConditionalFormulasAllowed(bool newValue)
FragmentSpecification & setCumulativeRewardFormulasAllowed(bool newValue)
FragmentSpecification & setConditionalProbabilityFormulasAllowed(bool newValue)
FragmentSpecification & setLongRunAverageProbabilitiesAllowed(bool newValue)
FragmentSpecification & setInstantaneousFormulasAllowed(bool newValue)
FragmentSpecification & setNestedOperatorsAllowed(bool newValue)
Formula const & getSubformula() const
bool isRewardModelSet() const
Retrieves whether a reward model was set.
Definition CheckTask.h:190
std::string const & getRewardModel() const
Retrieves the reward model over which to perform the checking (if set).
Definition CheckTask.h:197
FormulaType const & getFormula() const
Retrieves the formula from this task.
Definition CheckTask.h:140
bool isOnlyInitialStatesRelevantSet() const
Retrieves whether only the initial states are relevant in the computation.
Definition CheckTask.h:204
virtual std::unique_ptr< CheckResult > computeBoundedUntilProbabilities(Environment const &env, CheckTask< storm::logic::BoundedUntilFormula, ValueType > const &checkTask) override
virtual std::unique_ptr< CheckResult > computeUntilProbabilities(Environment const &env, CheckTask< storm::logic::UntilFormula, ValueType > const &checkTask) override
virtual std::unique_ptr< CheckResult > computeLongRunAverageRewards(Environment const &env, CheckTask< storm::logic::LongRunAverageRewardFormula, ValueType > const &checkTask) override
SparseDtmcEliminationModelChecker(storm::models::sparse::Dtmc< ValueType > const &model)
Creates an elimination-based model checker for the given model.
virtual std::unique_ptr< CheckResult > computeLongRunAverageProbabilities(Environment const &env, CheckTask< storm::logic::StateFormula, ValueType > const &checkTask) override
virtual std::unique_ptr< CheckResult > computeReachabilityRewards(Environment const &env, CheckTask< storm::logic::EventuallyFormula, ValueType > const &checkTask) override
storm::storage::FlexibleSparseMatrix< ValueType >::row_type FlexibleRowType
virtual std::unique_ptr< CheckResult > computeConditionalProbabilities(Environment const &env, CheckTask< storm::logic::ConditionalFormula, ValueType > const &checkTask) override
virtual bool canHandle(CheckTask< storm::logic::Formula, ValueType > const &checkTask) const override
This class represents a discrete-time Markov chain.
Definition Dtmc.h:13
EliminationOrder
An enum that contains all available state elimination orders.
void eliminateState(storm::storage::sparse::state_type state, bool removeForwardTransitions)
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
void complement()
Negates all bits in the bit vector.
bool isDisjointFrom(BitVector const &other) const
Checks whether none of the bits that are set in the current bit vector are also set in the given bit ...
bool full() const
Retrieves whether all bits are set in this bit vector.
bool empty() const
Retrieves whether no bits are set to true in this bit vector.
bool isSubsetOf(BitVector const &other) const
Checks whether all bits that are set in the current bit vector are also set in the given bit vector.
uint64_t getNumberOfSetBits() const
Returns the number of bits that are set to true in this bit vector.
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
const_iterator begin() const
Returns an iterator to the indices of the set bits in the bit vector.
size_t size() const
Retrieves the number of bits this bit vector can store.
bool get(uint64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
The flexible sparse matrix is used during state elimination.
row_type & getRow(index_type)
Returns an object representing the given row.
index_type getRowCount() const
Returns the number of rows of the matrix.
A class that holds a possibly non-square matrix in the compressed row storage format.
const_rows getRow(index_type row) const
Returns an object representing the given row.
void multiplyWithVector(std::vector< value_type > const &vector, std::vector< value_type > &result, std::vector< value_type > const *summand=nullptr) const
Multiplies the matrix with the given vector and writes the result to the given result vector.
SparseMatrix getSubmatrix(bool useGroups, storm::storage::BitVector const &rowConstraint, storm::storage::BitVector const &columnConstraint, bool insertDiagonalEntries=false, storm::storage::BitVector const &makeZeroColumns=storm::storage::BitVector()) const
Creates a submatrix of the current matrix by dropping all rows and columns whose bits are not set to ...
std::vector< value_type > getConstrainedRowSumVector(storm::storage::BitVector const &rowConstraint, storm::storage::BitVector const &columnConstraint) const
Computes a vector whose i-th entry is the sum of the entries in the i-th selected row where only thos...
storm::storage::SparseMatrix< value_type > transpose(bool joinGroups=false, bool keepZeros=false) const
Transposes the matrix.
index_type getRowCount() const
Returns the number of rows of the matrix.
iterator begin()
Returns an iterator to the states in this SCC.
Definition StateBlock.cpp:5
iterator end()
Returns an iterator that points one past the end of the states in this SCC.
This class represents the decomposition of a graph-like structure into its strongly connected compone...
This class represents a strongly connected component, i.e., a set of states such that every state can...
bool isTrivial() const
Retrieves whether this SCC is trivial.
#define STORM_LOG_INFO(message)
Definition logging.h:24
#define STORM_LOG_DEBUG(message)
Definition logging.h:18
#define STORM_LOG_TRACE(message)
Definition logging.h:12
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
#define STORM_PRINT_AND_LOG(message)
Definition macros.h:68
SFTBDDChecker::ValueType ValueType
Expression ite(Expression const &condition, Expression const &thenExpression, Expression const &elseExpression)
FragmentSpecification prctl()
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
storm::storage::BitVector getReachableStates(storm::storage::SparseMatrix< T > const &transitionMatrix, storm::storage::BitVector const &initialStates, storm::storage::BitVector const &constraintStates, storm::storage::BitVector const &targetStates, bool useStepBound, uint_fast64_t maximalSteps, boost::optional< storm::storage::BitVector > const &choiceFilter)
Performs a forward depth-first search through the underlying graph structure to identify the states t...
Definition graph.cpp:41
storm::storage::BitVector performProbGreater0(storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, bool useStepBound, uint_fast64_t maximalSteps)
Performs a backward depth-first search trough the underlying graph structure of the given model to de...
Definition graph.cpp:315
storm::storage::BitVector performProb1(storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &, storm::storage::BitVector const &psiStates, storm::storage::BitVector const &statesWithProbabilityGreater0)
Computes the set of states of the given model for which all paths lead to the given set of target sta...
Definition graph.cpp:376
std::vector< uint_fast64_t > getDistances(storm::storage::SparseMatrix< T > const &transitionMatrix, storm::storage::BitVector const &initialStates, boost::optional< storm::storage::BitVector > const &subsystem)
Performs a breadth-first search through the underlying graph structure to compute the distance from a...
Definition graph.cpp:281
std::shared_ptr< StatePriorityQueue > createStatePriorityQueue(boost::optional< std::vector< uint_fast64_t > > const &distanceBasedStatePriorities, storm::storage::FlexibleSparseMatrix< ValueType > const &transitionMatrix, storm::storage::FlexibleSparseMatrix< ValueType > const &backwardTransitions, std::vector< ValueType > const &oneStepProbabilities, storm::storage::BitVector const &states)
bool eliminationOrderNeedsForwardDistances(storm::settings::modules::EliminationSettings::EliminationOrder const &order)
bool eliminationOrderNeedsDistances(storm::settings::modules::EliminationSettings::EliminationOrder const &order)
std::vector< uint_fast64_t > getDistanceBasedPriorities(storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &transitionMatrixTransposed, storm::storage::BitVector const &initialStates, std::vector< ValueType > const &oneStepProbabilities, bool forward, bool reverse)
bool eliminationOrderNeedsReversedDistances(storm::settings::modules::EliminationSettings::EliminationOrder const &order)
void addVectors(std::vector< InValueType1 > const &firstOperand, std::vector< InValueType2 > const &secondOperand, std::vector< OutValueType > &target)
Adds the two given vectors and writes the result to the target vector.
Definition vector.h:399
void setVectorValues(std::vector< T > &vector, storm::storage::BitVector const &positions, std::vector< T > const &values)
Sets the provided values at the provided positions in the given vector.
Definition vector.h:78
void selectVectorValues(std::vector< T > &vector, storm::storage::BitVector const &positions, std::vector< T > const &values)
Selects the elements from a vector at the specified positions and writes them consecutively into anot...
Definition vector.h:184
ValueType simplify(ValueType value)