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