Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
ConditionalHelper.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <stack>
5
28#include "storm/utility/graph.h"
31
32namespace storm::modelchecker {
33
34namespace internal {
35
36template<typename ValueType>
37std::optional<typename storm::transformer::EndComponentEliminator<ValueType>::EndComponentEliminatorReturnType> eliminateEndComponents(
38 storm::storage::BitVector const& possibleEcStates, bool addRowAtRepresentativeState, std::optional<uint64_t> const representativeRowEntry,
39 storm::storage::SparseMatrix<ValueType>& matrix, storm::storage::BitVector& rowsWithSum1, std::vector<ValueType>& rowValues1,
40 storm::OptionalRef<std::vector<ValueType>> rowValues2 = {}) {
41 storm::storage::MaximalEndComponentDecomposition<ValueType> ecs(matrix, matrix.transpose(true), possibleEcStates, rowsWithSum1);
42 if (ecs.empty()) {
43 return {}; // nothing to do
44 }
45
46 storm::storage::BitVector allRowGroups(matrix.getRowGroupCount(), true);
48 matrix, ecs, allRowGroups, addRowAtRepresentativeState ? allRowGroups : ~allRowGroups, representativeRowEntry.has_value());
49
50 // Update matrix
51 matrix = std::move(ecElimResult.matrix);
52 if (addRowAtRepresentativeState && representativeRowEntry) {
53 auto const columnIndex = ecElimResult.oldToNewStateMapping[*representativeRowEntry];
54 for (auto representativeRowIndex : ecElimResult.sinkRows) {
55 auto row = matrix.getRow(representativeRowIndex);
56 STORM_LOG_ASSERT(row.getNumberOfEntries() == 1, "Unexpected number of entries in representative row.");
57 auto& entry = *row.begin();
58 entry.setColumn(columnIndex);
59 }
60 }
61
62 // update vectors
63 auto updateRowValue = [&ecElimResult](std::vector<ValueType>& rowValues) {
64 std::vector<ValueType> newRowValues;
65 newRowValues.reserve(ecElimResult.newToOldRowMapping.size());
66 for (auto oldRowIndex : ecElimResult.newToOldRowMapping) {
67 newRowValues.push_back(rowValues[oldRowIndex]);
68 }
69 rowValues = std::move(newRowValues);
71 std::all_of(ecElimResult.sinkRows.begin(), ecElimResult.sinkRows.end(), [&rowValues](auto i) { return storm::utility::isZero(rowValues[i]); }),
72 "Sink rows are expected to have zero value");
73 };
74 updateRowValue(rowValues1);
75 if (rowValues2) {
76 updateRowValue(*rowValues2);
77 }
78
79 // update bitvector
80 storm::storage::BitVector newRowsWithSum1(ecElimResult.newToOldRowMapping.size(), true);
81 uint64_t newRowIndex = 0;
82 for (auto oldRowIndex : ecElimResult.newToOldRowMapping) {
83 if ((addRowAtRepresentativeState && !representativeRowEntry.has_value() && ecElimResult.sinkRows.get(newRowIndex)) || !rowsWithSum1.get(oldRowIndex)) {
84 newRowsWithSum1.set(newRowIndex, false);
85 }
86 ++newRowIndex;
87 }
88 rowsWithSum1 = std::move(newRowsWithSum1);
89
90 return ecElimResult;
91}
92
93template<typename ValueType, typename SolutionType = ValueType>
95 std::vector<ValueType> const& rowValues, storm::storage::BitVector const& rowsWithSum1,
96 storm::solver::SolveGoal<ValueType, SolutionType> const& goal, uint64_t const initialState,
97 std::optional<std::vector<uint64_t>>& schedulerOutput) {
98 // Initialize the solution vector.
99 std::vector<SolutionType> x(matrix.getRowGroupCount(), storm::utility::zero<ValueType>());
100
101 // Set up the solver.
103 storm::storage::BitVector relevantValues(matrix.getRowGroupCount(), false);
104 relevantValues.set(initialState, true);
105 auto getGoal = [&goal, &relevantValues]() -> storm::solver::SolveGoal<ValueType, SolutionType> {
106 if (goal.isBounded()) {
107 return {goal.direction(), goal.boundComparisonType(), goal.thresholdValue(), relevantValues};
108 } else {
109 return {goal.direction(), relevantValues};
110 }
111 };
112 auto solver = storm::solver::configureMinMaxLinearEquationSolver(env, getGoal(), factory, matrix);
113
115 solver->setOptimizationDirection(goal.direction());
116 solver->setRequirementsChecked();
117 solver->setHasUniqueSolution(true);
118 solver->setHasNoEndComponents(true);
119 solver->setLowerBound(storm::utility::zero<ValueType>());
120 solver->setUpperBound(storm::utility::one<ValueType>());
121 solver->setTrackScheduler(schedulerOutput.has_value());
122
123 // Solve the corresponding system of equations.
124 solver->solveEquations(env, x, rowValues);
125
126 if (schedulerOutput) {
127 *schedulerOutput = std::move(solver->getSchedulerChoices());
128 }
129
130 return x[initialState];
131}
132
137template<typename ValueType>
138std::unique_ptr<storm::storage::Scheduler<ValueType>> computeReachabilityProbabilities(
139 Environment const& env, std::map<uint64_t, ValueType>& nonZeroResults, storm::solver::OptimizationDirection const dir,
140 storm::storage::SparseMatrix<ValueType> const& transitionMatrix, storm::storage::BitVector const& initialStates,
141 storm::storage::BitVector const& allowedStates, storm::storage::BitVector const& targetStates, bool computeScheduler = true) {
142 std::unique_ptr<storm::storage::Scheduler<ValueType>> scheduler;
143 if (computeScheduler) {
144 scheduler = std::make_unique<storm::storage::Scheduler<ValueType>>(transitionMatrix.getRowGroupCount());
145 }
146
147 auto reachabilityEnv = env;
148 reachabilityEnv.solver().minMax().setPrecision(env.modelchecker().conditional().getPrecision());
149 reachabilityEnv.solver().minMax().setRelativeTerminationCriterion(env.modelchecker().conditional().isRelativePrecision());
150
151 if (initialStates.empty()) { // nothing to do
152 return scheduler;
153 }
154 auto const reachableStates = storm::utility::graph::getReachableStates(transitionMatrix, initialStates, allowedStates, targetStates);
155 auto const subTargets = targetStates % reachableStates;
156 // Catch the case where no target is reachable from an initial state. In this case, there is nothing to do since all probabilities are zero.
157 if (subTargets.empty()) {
158 return scheduler;
159 }
160 auto const subInits = initialStates % reachableStates;
161 auto const submatrix = transitionMatrix.getSubmatrix(true, reachableStates, reachableStates);
163 reachabilityEnv, storm::solver::SolveGoal<ValueType>(dir, subInits), submatrix, submatrix.transpose(true),
164 storm::storage::BitVector(subTargets.size(), true), subTargets, false, computeScheduler);
165
166 auto origInitIt = initialStates.begin();
167 for (auto subInit : subInits) {
168 auto const& val = subResult.values[subInit];
169 if (!storm::utility::isZero(val)) {
170 nonZeroResults.emplace(*origInitIt, val);
171 }
172 ++origInitIt;
173 }
174
175 if (computeScheduler) {
176 auto submatrixIdx = 0;
177 for (auto state : reachableStates) {
178 scheduler->setChoice(subResult.scheduler->getChoice(submatrixIdx), state);
179 ++submatrixIdx;
180 }
181 }
182
183 return scheduler;
184}
185
186template<typename ValueType>
188 storm::storage::BitVector const maybeStates; // Those states that can be reached from initial without reaching a terminal state
189 storm::storage::BitVector const terminalStates; // Those states where we already know the probability to reach the condition and the target value
190 storm::storage::BitVector const conditionStates; // Those states where the condition holds almost surely (under all schedulers)
191 storm::storage::BitVector const targetStates; // Those states where the target holds almost surely (under all schedulers)
192 storm::storage::BitVector const universalObservationFailureStates; // Those states where the condition is not reachable (under all schedulers)
193 storm::storage::BitVector const existentialObservationFailureStates; // Those states s where a scheduler exists that (i) does not reach the condition from
194 // s and (ii) acts optimal in all terminal states
195 std::map<uint64_t, ValueType> const nonZeroTargetStateValues; // The known non-zero target values. (default is zero)
196 // There are three cases of terminal states:
197 // 1. conditionStates: The condition holds, so the target value is the optimal probability to reach target from there
198 // 2. targetStates: The target is reached, so the target value is the optimal probability to reach a condition from there.
199 // The remaining probability mass is the probability of an observation failure
200 // 3. states that can not reach the condition under any scheduler. The target value is zero.
201
202 // TerminalStates is a superset of conditionStates and dom(nonZeroTargetStateValues).
203 // For a terminalState that is not a conditionState, it is impossible to (reach the condition and not reach the target).
204
205 std::unique_ptr<storm::storage::Scheduler<ValueType>>
206 schedulerChoicesForReachingTargetStates; // Scheduler choices for reaching target states, used for constructing the resulting scheduler
207 std::unique_ptr<storm::storage::Scheduler<ValueType>>
208 schedulerChoicesForReachingConditionStates; // Scheduler choices for reaching condition states, used for constructing the resulting scheduler
209
210 ValueType getTargetValue(uint64_t state) const {
211 STORM_LOG_ASSERT(terminalStates.get(state), "Tried to get target value for non-terminal state.");
212 auto const it = nonZeroTargetStateValues.find(state);
213 return it == nonZeroTargetStateValues.end() ? storm::utility::zero<ValueType>() : it->second;
214 }
215
216 ValueType failProbability(uint64_t state) const {
217 STORM_LOG_ASSERT(terminalStates.get(state), "Tried to get fail probability for non-terminal state.");
218 STORM_LOG_ASSERT(!conditionStates.get(state), "Tried to get fail probability for a condition state.");
219 // condition states have fail probability zero
221 }
222};
223
224template<typename ValueType>
226 storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
227 storm::storage::SparseMatrix<ValueType> const& backwardTransitions, storm::storage::BitVector const& relevantStates,
228 storm::storage::BitVector const& targetStates, storm::storage::BitVector const& conditionStates) {
229 storm::storage::BitVector const allStates(transitionMatrix.getRowGroupCount(), true);
230 auto extendedConditionStates =
231 storm::utility::graph::performProb1A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, allStates, conditionStates);
232 auto universalObservationFailureStates = storm::utility::graph::performProb0A(backwardTransitions, allStates, extendedConditionStates);
233 std::map<uint64_t, ValueType> nonZeroTargetStateValues;
234 auto const extendedTargetStates =
235 storm::utility::graph::performProb1A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, allStates, targetStates);
236 auto const targetAndNotCondFailStates = extendedTargetStates & ~(extendedConditionStates | universalObservationFailureStates);
237
238 // compute schedulers for reaching target and condition states from target and condition states
239 std::unique_ptr<storm::storage::Scheduler<ValueType>> schedulerChoicesForReachingTargetStates = computeReachabilityProbabilities(
240 env, nonZeroTargetStateValues, dir, transitionMatrix, extendedConditionStates, allStates, extendedTargetStates, computeScheduler);
241 std::unique_ptr<storm::storage::Scheduler<ValueType>> schedulerChoicesForReachingConditionStates = computeReachabilityProbabilities(
242 env, nonZeroTargetStateValues, dir, transitionMatrix, targetAndNotCondFailStates, allStates, extendedConditionStates, computeScheduler);
243
244 // get states where the optimal policy reaches the condition with positive probability
245 auto terminalStatesThatReachCondition = extendedConditionStates;
246 for (auto state : targetAndNotCondFailStates) {
247 if (nonZeroTargetStateValues.contains(state)) {
248 terminalStatesThatReachCondition.set(state, true);
249 }
250 }
251
252 // get the terminal states following the three cases described above
253 auto terminalStates = extendedConditionStates | extendedTargetStates | universalObservationFailureStates;
254 if (storm::solver::minimize(dir)) {
255 // There can be target states from which (only) the *minimal* probability to reach a condition is zero.
256 // For those states, the optimal policy is to enforce observation failure.
257 // States that can only reach (target states with almost sure observation failure) or observation failure will be treated as terminal states with
258 // targetValue zero and failProbability one.
259 terminalStates |= storm::utility::graph::performProb0A(backwardTransitions, ~terminalStates, terminalStatesThatReachCondition);
260 }
261
262 auto nonTerminalStates = ~terminalStates;
263
264 auto existentialObservationFailureStates = storm::utility::graph::performProb0E(transitionMatrix, transitionMatrix.getRowGroupIndices(),
265 backwardTransitions, nonTerminalStates, terminalStatesThatReachCondition);
266
267 // Restrict non-terminal states to those that are still relevant
268 nonTerminalStates &= storm::utility::graph::getReachableStates(transitionMatrix, relevantStates, nonTerminalStates, terminalStates);
269
270 return NormalFormData<ValueType>{.maybeStates = std::move(nonTerminalStates),
271 .terminalStates = std::move(terminalStates),
272 .conditionStates = std::move(extendedConditionStates),
273 .targetStates = std::move(extendedTargetStates),
274 .universalObservationFailureStates = std::move(universalObservationFailureStates),
275 .existentialObservationFailureStates = std::move(existentialObservationFailureStates),
276 .nonZeroTargetStateValues = std::move(nonZeroTargetStateValues),
277 .schedulerChoicesForReachingTargetStates = std::move(schedulerChoicesForReachingTargetStates),
278 .schedulerChoicesForReachingConditionStates = std::move(schedulerChoicesForReachingConditionStates)};
279}
280
281// computes the scheduler that reaches the EC exits from the maybe states that were removed by EC elimination
282template<typename ValueType, typename SolutionType = ValueType>
284 storm::storage::SparseMatrix<ValueType> const& backwardTransitions, storm::storage::BitVector const& maybeStates,
285 storm::storage::BitVector const& maybeStatesWithoutChoice, storm::storage::BitVector const& maybeStatesWithChoice,
286 std::vector<uint64_t> const& stateToFinalEc, NormalFormData<ValueType> const& normalForm, uint64_t initialComponentIndex,
287 storm::storage::BitVector const& initialComponentExitStates, storm::storage::BitVector const& initialComponentExitRows,
288 uint64_t chosenInitialComponentExitState, uint64_t chosenInitialComponentExit) {
289 // Compute the EC stay choices for the states in maybeStatesWithChoice
290 storm::storage::BitVector ecStayChoices(transitionMatrix.getRowCount(), false);
291 storm::storage::BitVector initialComponentStates(transitionMatrix.getRowGroupCount(), false);
292
293 // compute initial component states and all choices that stay within a given EC
294 for (uint64_t state : maybeStates) {
295 auto ecIndex = stateToFinalEc[state];
296 if (ecIndex == initialComponentIndex) {
297 initialComponentStates.set(state, true);
298 continue; // state part of the initial component
299 } else if (ecIndex == std::numeric_limits<uint64_t>::max()) {
300 continue;
301 }
302 for (auto choiceIndex : transitionMatrix.getRowGroupIndices(state)) {
303 bool isEcStayChoice = true;
304 for (auto const& entry : transitionMatrix.getRow(choiceIndex)) {
305 auto targetState = entry.getColumn();
306 if (stateToFinalEc[targetState] != ecIndex) {
307 isEcStayChoice = false;
308 break;
309 }
310 }
311 if (isEcStayChoice) {
312 ecStayChoices.set(choiceIndex, true);
313 }
314 }
315 }
316
317 // fill choices for ECs that reach the chosen EC exit
318 auto const maybeNonICStatesWithoutChoice = maybeStatesWithoutChoice & ~initialComponentStates;
319 storm::utility::graph::computeSchedulerProb1E(maybeNonICStatesWithoutChoice, transitionMatrix, backwardTransitions, maybeStates, maybeStatesWithChoice,
320 scheduler, ecStayChoices);
321
322 // collect all choices from the initial component states and the choices that were selected by the scheduler so far
323 auto const condOrTargetStates = normalForm.conditionStates | normalForm.targetStates;
324 auto const& rowGroups = transitionMatrix.getRowGroupIndices();
325 storm::storage::BitVector allowedChoices(transitionMatrix.getRowCount(), false);
326 auto const rowGroupCount = transitionMatrix.getRowGroupCount();
327 for (uint64_t state = 0; state < rowGroupCount; ++state) {
328 if (scheduler.isChoiceSelected(state)) {
329 auto choiceIndex = scheduler.getChoice(state).getDeterministicChoice();
330 allowedChoices.set(rowGroups[state] + choiceIndex, true);
331 } else if (initialComponentStates.get(state) || condOrTargetStates.get(state)) {
332 for (auto choiceIndex : transitionMatrix.getRowGroupIndices(state)) {
333 allowedChoices.set(choiceIndex, true);
334 }
335 }
336 }
337
338 // dfs to find which choices in initial component states lead to condOrTargetStates
339 storm::storage::BitVector choicesThatCanVisitCondOrTargetStates(transitionMatrix.getRowCount(), false);
340 std::stack<uint64_t> toProcess;
341 for (auto state : condOrTargetStates) {
342 toProcess.push(state);
343 }
344 auto visitedStates = condOrTargetStates;
345 while (!toProcess.empty()) {
346 auto currentState = toProcess.top();
347 toProcess.pop();
348 for (auto const& entry : backwardTransitions.getRow(currentState)) {
349 uint64_t const predecessorState = entry.getColumn();
350 for (uint64_t const predecessorChoice : transitionMatrix.getRowGroupIndices(predecessorState)) {
351 if (!allowedChoices.get(predecessorChoice) || choicesThatCanVisitCondOrTargetStates.get(predecessorChoice)) {
352 continue; // The choice is either not allowed or has been considered already
353 }
354 if (auto const r = transitionMatrix.getRow(predecessorChoice);
355 std::none_of(r.begin(), r.end(), [&currentState](auto const& e) { return e.getColumn() == currentState; })) {
356 continue; // not an actual predecessor choice
357 }
358 choicesThatCanVisitCondOrTargetStates.set(predecessorChoice, true);
359 if (!visitedStates.get(predecessorState)) {
360 visitedStates.set(predecessorState, true);
361 toProcess.push(predecessorState);
362 }
363 }
364 }
365 }
366
367 // we want to disallow taking initial component exits that can lead to a condition or target state, beside the one exit that was chosen
368 storm::storage::BitVector disallowedInitialComponentExits = initialComponentExitRows & choicesThatCanVisitCondOrTargetStates;
369 disallowedInitialComponentExits.set(chosenInitialComponentExit, false);
370 storm::storage::BitVector choicesAllowedForInitialComponent = allowedChoices & ~disallowedInitialComponentExits;
371
372 storm::storage::BitVector goodInitialComponentStates = initialComponentStates;
373 bool progress = false;
374 for (uint64_t state : initialComponentExitStates) {
375 auto const groupStart = transitionMatrix.getRowGroupIndices()[state];
376 auto const groupEnd = transitionMatrix.getRowGroupIndices()[state + 1];
377 bool const allChoicesAreDisallowed = disallowedInitialComponentExits.getNextUnsetIndex(groupStart) >= groupEnd;
378 if (allChoicesAreDisallowed) {
379 goodInitialComponentStates.set(state, false);
380 progress = true;
381 }
382 }
383 while (progress) {
384 progress = false;
385 for (uint64_t state : goodInitialComponentStates) {
386 bool allChoicesAreDisallowed = true;
387 for (auto choiceIndex : transitionMatrix.getRowGroupIndices(state)) {
388 auto row = transitionMatrix.getRow(choiceIndex);
389 bool const hasBadSuccessor = std::any_of(
390 row.begin(), row.end(), [&goodInitialComponentStates](auto const& entry) { return !goodInitialComponentStates.get(entry.getColumn()); });
391 if (hasBadSuccessor) {
392 choicesAllowedForInitialComponent.set(choiceIndex, false);
393 } else {
394 allChoicesAreDisallowed = false;
395 }
396 }
397 if (allChoicesAreDisallowed) {
398 goodInitialComponentStates.set(state, false);
399 progress = true;
400 }
401 }
402 }
403
404 storm::storage::BitVector exitStateBitvector(transitionMatrix.getRowGroupCount(), false);
405 exitStateBitvector.set(chosenInitialComponentExitState, true);
406
407 storm::utility::graph::computeSchedulerProbGreater0E(transitionMatrix, backwardTransitions, initialComponentStates, exitStateBitvector, scheduler,
408 choicesAllowedForInitialComponent);
409
410 // fill the choices of initial component states that do not have a choice yet
411 // these states should not reach the condition or target states under the constructed scheduler
412 for (uint64_t state : initialComponentStates) {
413 if (!scheduler.isChoiceSelected(state)) {
414 for (auto choiceIndex : transitionMatrix.getRowGroupIndices(state)) {
415 if (choicesAllowedForInitialComponent.get(choiceIndex)) {
416 scheduler.setChoice(choiceIndex - rowGroups[state], state);
417 break;
418 }
419 }
420 }
421 }
422}
423
424template<typename ValueType, typename SolutionType = ValueType>
428 // Intentionally left empty.
429 }
430
431 bool hasScheduler() const {
432 return static_cast<bool>(scheduler);
433 }
434
436 std::unique_ptr<storm::storage::Scheduler<SolutionType>> scheduler;
437};
438
443template<typename ValueType, typename SolutionType = ValueType>
444typename internal::ResultReturnType<ValueType> computeViaRestartMethod(Environment const& env, uint64_t const initialState,
445 storm::solver::SolveGoal<ValueType, SolutionType> const& goal, bool computeScheduler,
446 storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
447 storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
448 NormalFormData<ValueType> const& normalForm) {
449 auto const& maybeStates = normalForm.maybeStates;
450 auto originalToReducedStateIndexMap = maybeStates.getNumberOfSetBitsBeforeIndices();
451 auto const numMaybeStates = maybeStates.getNumberOfSetBits();
452 auto const numMaybeChoices = transitionMatrix.getNumRowsInRowGroups(maybeStates);
453
454 // Build the transitions that include a backwards loop to the initial state
455 storm::storage::SparseMatrixBuilder<ValueType> matrixBuilder(numMaybeChoices, numMaybeStates, 0, true, true, numMaybeStates);
456 std::vector<ValueType> rowValues;
457 storm::storage::BitVector rowsWithSum1(numMaybeChoices, true);
458 rowValues.reserve(numMaybeChoices);
459 uint64_t currentRow = 0;
460 for (auto state : maybeStates) {
461 matrixBuilder.newRowGroup(currentRow);
462 for (auto origRowIndex : transitionMatrix.getRowGroupIndices(state)) {
463 // We make two passes over the successors. First, we find out the reset probabilities and target probabilities
464 // Then, we insert the matrix entries in the correct order
465 // This two-phase approach is to avoid a costly out-of-order insertion into the matrix
466 ValueType targetProbability = storm::utility::zero<ValueType>();
467 ValueType restartProbability = storm::utility::zero<ValueType>();
468 bool rowSumIsLess1 = false;
469 for (auto const& entry : transitionMatrix.getRow(origRowIndex)) {
470 if (normalForm.terminalStates.get(entry.getColumn())) {
471 ValueType const targetValue = normalForm.getTargetValue(entry.getColumn());
472 targetProbability += targetValue * entry.getValue();
473 if (normalForm.conditionStates.get(entry.getColumn())) {
474 rowSumIsLess1 = true;
475 } else {
476 if (!storm::utility::isZero(targetValue)) {
477 rowSumIsLess1 = true;
478 }
479 restartProbability += entry.getValue() * normalForm.failProbability(entry.getColumn());
480 }
481 }
482 }
483 if (rowSumIsLess1) {
484 rowsWithSum1.set(currentRow, false);
485 }
486 rowValues.push_back(targetProbability);
487 bool addRestartTransition = !storm::utility::isZero(restartProbability);
488 for (auto const& entry : transitionMatrix.getRow(origRowIndex)) {
489 // Insert backloop probability if we haven't done so yet and are past the initial state index
490 // This is to avoid a costly out-of-order insertion into the matrix
491 if (addRestartTransition && entry.getColumn() > initialState) {
492 matrixBuilder.addNextValue(currentRow, originalToReducedStateIndexMap[initialState], restartProbability);
493 addRestartTransition = false;
494 }
495 if (maybeStates.get(entry.getColumn())) {
496 matrixBuilder.addNextValue(currentRow, originalToReducedStateIndexMap[entry.getColumn()], entry.getValue());
497 }
498 }
499 // Add the backloop if we haven't done this already
500 if (addRestartTransition) {
501 matrixBuilder.addNextValue(currentRow, originalToReducedStateIndexMap[initialState], restartProbability);
502 }
503 ++currentRow;
504 }
505 }
506 STORM_LOG_ASSERT(currentRow == numMaybeChoices, "Unexpected number of constructed rows.");
507
508 auto matrix = matrixBuilder.build();
509 auto initStateInReduced = originalToReducedStateIndexMap[initialState];
510
511 // Eliminate end components in two phases
512 // First, we catch all end components that do not contain the initial state. It is possible to stay in those ECs forever
513 // without reaching the condition. This is reflected by a backloop to the initial state.
514 storm::storage::BitVector selectedStatesInReduced(numMaybeStates, true);
515 selectedStatesInReduced.set(initStateInReduced, false);
516 auto ecElimResult1 = eliminateEndComponents(selectedStatesInReduced, true, initStateInReduced, matrix, rowsWithSum1, rowValues);
517 selectedStatesInReduced.set(initStateInReduced, true);
518 if (ecElimResult1) {
519 selectedStatesInReduced.resize(matrix.getRowGroupCount(), true);
520 initStateInReduced = ecElimResult1->oldToNewStateMapping[initStateInReduced];
521 }
522 // Second, eliminate the remaining ECs. These must involve the initial state and might have been introduced in the previous step.
523 // A policy selecting such an EC must reach the condition with probability zero and is thus invalid.
524 auto ecElimResult2 = eliminateEndComponents(selectedStatesInReduced, false, std::nullopt, matrix, rowsWithSum1, rowValues);
525 if (ecElimResult2) {
526 initStateInReduced = ecElimResult2->oldToNewStateMapping[initStateInReduced];
527 }
528
529 STORM_LOG_INFO("Processed model has " << matrix.getRowGroupCount() << " states and " << matrix.getRowGroupCount() << " choices and "
530 << matrix.getEntryCount() << " transitions.");
531
532 // Finally, solve the equation system, potentially computing a scheduler
533 std::optional<std::vector<uint64_t>> reducedSchedulerChoices;
534 if (computeScheduler) {
535 reducedSchedulerChoices.emplace();
536 }
537 auto resultValue = solveMinMaxEquationSystem(env, matrix, rowValues, rowsWithSum1, goal, initStateInReduced, reducedSchedulerChoices);
538
539 // Create result (scheduler potentially added below)
540 auto finalResult = ResultReturnType<ValueType, SolutionType>(resultValue);
541
542 if (!computeScheduler) {
543 return finalResult;
544 }
545 // At this point we have to reconstruct the scheduler for the original model
546 STORM_LOG_ASSERT(reducedSchedulerChoices.has_value() && reducedSchedulerChoices->size() == matrix.getRowGroupCount(),
547 "Requested scheduler, but it was not computed or has invalid size.");
548 // For easier access, we create and update some index mappings
549 std::vector<uint64_t> originalRowToStateIndexMap; // maps original row indices to original state indices. transitionMatrix.getRowGroupIndices() are the
550 // inverse of that mapping
551 originalRowToStateIndexMap.reserve(transitionMatrix.getRowCount());
552 for (uint64_t originalStateIndex = 0; originalStateIndex < transitionMatrix.getRowGroupCount(); ++originalStateIndex) {
553 originalRowToStateIndexMap.insert(originalRowToStateIndexMap.end(), transitionMatrix.getRowGroupSize(originalStateIndex), originalStateIndex);
554 }
555 std::vector<uint64_t> reducedToOriginalRowIndexMap; // maps row indices of the reduced model to the original ones
556 reducedToOriginalRowIndexMap.reserve(numMaybeChoices);
557 for (uint64_t const originalMaybeState : maybeStates) {
558 for (auto const originalRowIndex : transitionMatrix.getRowGroupIndices(originalMaybeState)) {
559 reducedToOriginalRowIndexMap.push_back(originalRowIndex);
560 }
561 }
562 if (ecElimResult1.has_value() || ecElimResult2.has_value()) {
563 // reducedToOriginalRowIndexMap needs to be updated so it maps from rows of the ec-eliminated system
564 std::vector<uint64_t> tmpReducedToOriginalRowIndexMap;
565 tmpReducedToOriginalRowIndexMap.reserve(matrix.getRowCount());
566 for (uint64_t reducedRow = 0; reducedRow < matrix.getRowCount(); ++reducedRow) {
567 uint64_t intermediateRow = reducedRow;
568 if (ecElimResult2.has_value()) {
569 intermediateRow = ecElimResult2->newToOldRowMapping.at(intermediateRow);
570 }
571 if (ecElimResult1.has_value()) {
572 intermediateRow = ecElimResult1->newToOldRowMapping.at(intermediateRow);
573 }
574 tmpReducedToOriginalRowIndexMap.push_back(reducedToOriginalRowIndexMap[intermediateRow]);
575 }
576 reducedToOriginalRowIndexMap = std::move(tmpReducedToOriginalRowIndexMap);
577 // originalToReducedStateIndexMap needs to be updated so it maps into the ec-eliminated system
578 for (uint64_t originalStateIndex = 0; originalStateIndex < transitionMatrix.getRowGroupCount(); ++originalStateIndex) {
579 auto& reducedIndex = originalToReducedStateIndexMap[originalStateIndex];
580 if (maybeStates.get(originalStateIndex)) {
581 if (ecElimResult1.has_value()) {
582 reducedIndex = ecElimResult1->oldToNewStateMapping.at(reducedIndex);
583 }
584 if (ecElimResult2.has_value()) {
585 reducedIndex = ecElimResult2->oldToNewStateMapping.at(reducedIndex);
586 }
587 } else {
588 reducedIndex = std::numeric_limits<uint64_t>::max(); // The original state does not exist in the reduced model.
589 }
590 }
591 }
592
593 storm::storage::BitVector initialComponentExitRows(transitionMatrix.getRowCount(), false);
594 storm::storage::BitVector initialComponentExitStates(transitionMatrix.getRowGroupCount(), false);
595 for (auto const reducedRowIndex : matrix.getRowGroupIndices(initStateInReduced)) {
596 uint64_t const originalRowIndex = reducedToOriginalRowIndexMap[reducedRowIndex];
597 uint64_t const originalState = originalRowToStateIndexMap[originalRowIndex];
598
599 initialComponentExitRows.set(originalRowIndex, true);
600 initialComponentExitStates.set(originalState, true);
601 }
602
603 // If requested, construct the scheduler for the original model
604 storm::storage::BitVector maybeStatesWithChoice(maybeStates.size(), false);
605 uint64_t chosenInitialComponentExitState = std::numeric_limits<uint64_t>::max();
606 uint64_t chosenInitialComponentExit = std::numeric_limits<uint64_t>::max();
607 auto scheduler = std::make_unique<storm::storage::Scheduler<SolutionType>>(transitionMatrix.getRowGroupCount());
608
609 uint64_t reducedState = 0;
610 for (auto const& choice : reducedSchedulerChoices.value()) {
611 uint64_t const reducedRowIndex = matrix.getRowGroupIndices()[reducedState] + choice;
612 uint64_t const originalRowIndex = reducedToOriginalRowIndexMap[reducedRowIndex];
613 uint64_t const originalState = originalRowToStateIndexMap[originalRowIndex];
614 uint64_t const originalChoice = originalRowIndex - transitionMatrix.getRowGroupIndices()[originalState];
615 scheduler->setChoice(originalChoice, originalState);
616 maybeStatesWithChoice.set(originalState, true);
617 if (reducedState == initStateInReduced) {
618 chosenInitialComponentExitState = originalState;
619 chosenInitialComponentExit = originalRowIndex;
620 }
621 ++reducedState;
622 }
623
624 auto const maybeStatesWithoutChoice = maybeStates & ~maybeStatesWithChoice;
625 finalizeSchedulerForMaybeStates(*scheduler, transitionMatrix, backwardTransitions, maybeStates, maybeStatesWithoutChoice, maybeStatesWithChoice,
626 originalToReducedStateIndexMap, normalForm, initStateInReduced, initialComponentExitStates, initialComponentExitRows,
627 chosenInitialComponentExitState, chosenInitialComponentExit);
628
629 finalResult.scheduler = std::move(scheduler);
630
631 return finalResult;
632}
633
639template<typename ValueType, typename SolutionType = ValueType>
641 public:
642 WeightedReachabilityHelper(uint64_t const initialState, storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
643 NormalFormData<ValueType> const& normalForm, bool computeScheduler) {
644 // Determine rowgroups (states) and rows (choices) of the submatrix
645 auto subMatrixRowGroups = normalForm.maybeStates;
646 // Identify and eliminate the initial component to enforce that it is eventually exited
647 // The initial component is the largest subset of maybestates C such that
648 // (i) the initial state is contained in C
649 // (ii) each state in C can be reached from the initial state while only playing actions that stay inside C or observation failure and
650 // (iii) for each state in C except the initial state there is a policy that almost surely reaches an observation failure
651 // An optimal scheduler can intuitively pick the best exiting action of C and enforce that all paths that satisfy the condition exit C through that
652 // action. By eliminating the initial component, we ensure that only policies that actually exit C are considered. The remaining policies have
653 // probability zero of satisfying the condition.
654 initialComponentExitRows = storm::storage::BitVector(transitionMatrix.getRowCount(), false);
655 initialComponentExitStates = storm::storage::BitVector(transitionMatrix.getRowGroupCount(), false);
656 subMatrixRowGroups.set(initialState, false); // temporarily unset initial state
657 std::vector<uint64_t> dfsStack = {initialState};
658 while (!dfsStack.empty()) {
659 auto const state = dfsStack.back();
660 dfsStack.pop_back();
661 for (auto rowIndex : transitionMatrix.getRowGroupIndices(state)) {
662 auto const row = transitionMatrix.getRow(rowIndex);
663 if (std::all_of(row.begin(), row.end(),
664 [&normalForm](auto const& entry) { return normalForm.existentialObservationFailureStates.get(entry.getColumn()); })) {
665 for (auto const& entry : row) {
666 auto const& successor = entry.getColumn();
667 if (subMatrixRowGroups.get(successor)) {
668 subMatrixRowGroups.set(successor, false);
669 dfsStack.push_back(successor);
670 }
671 }
672 } else {
673 initialComponentExitRows.set(rowIndex, true);
674 initialComponentExitStates.set(state, true);
675 }
676 }
677 }
678 auto const numSubmatrixRows = transitionMatrix.getNumRowsInRowGroups(subMatrixRowGroups) + initialComponentExitRows.getNumberOfSetBits();
679 subMatrixRowGroups.set(initialState, true); // set initial state again, as single representative state for the initial component
680 auto const numSubmatrixRowGroups = subMatrixRowGroups.getNumberOfSetBits();
681
682 if (computeScheduler) {
683 reducedToOriginalRowIndexMap.reserve(numSubmatrixRows);
684 }
685
686 // state index mapping and initial state
687 originalToReducedStateIndexMap = subMatrixRowGroups.getNumberOfSetBitsBeforeIndices();
688 initialStateInSubmatrix = originalToReducedStateIndexMap[initialState];
689 auto const eliminatedInitialComponentStates = normalForm.maybeStates & ~subMatrixRowGroups;
690
691 // Inital component states do not have the correct mapping yet.
692 for (auto state : eliminatedInitialComponentStates) {
693 originalToReducedStateIndexMap[state] = initialStateInSubmatrix;
694 }
695
696 // build matrix, rows that sum up to 1, target values, condition values
697 storm::storage::SparseMatrixBuilder<ValueType> matrixBuilder(numSubmatrixRows, numSubmatrixRowGroups, 0, true, true, numSubmatrixRowGroups);
698 rowsWithSum1 = storm::storage::BitVector(numSubmatrixRows, true);
699 targetRowValues.reserve(numSubmatrixRows);
700 conditionRowValues.reserve(numSubmatrixRows);
701 uint64_t currentRow = 0;
702 for (auto state : subMatrixRowGroups) {
703 matrixBuilder.newRowGroup(currentRow);
704
705 // Put the row processing into a lambda for avoiding code duplications
706 auto processRow = [&](uint64_t origRowIndex) {
707 // insert the submatrix entries and find out the target and condition probabilities for this row
708 ValueType targetProbability = storm::utility::zero<ValueType>();
709 ValueType conditionProbability = storm::utility::zero<ValueType>();
710 bool rowSumIsLess1 = false;
711 for (auto const& entry : transitionMatrix.getRow(origRowIndex)) {
712 if (normalForm.terminalStates.get(entry.getColumn())) {
713 STORM_LOG_ASSERT(!storm::utility::isZero(entry.getValue()), "Transition probability must be non-zero.");
714 rowSumIsLess1 = true;
715 ValueType const scaledTargetValue = normalForm.getTargetValue(entry.getColumn()) * entry.getValue();
716 targetProbability += scaledTargetValue;
717 if (normalForm.conditionStates.get(entry.getColumn())) {
718 conditionProbability += entry.getValue(); // conditionValue of successor is 1
719 } else {
720 conditionProbability += scaledTargetValue; // for terminal, non-condition states, the condition value equals the target value
721 }
722 } else if (eliminatedInitialComponentStates.get(entry.getColumn())) {
723 rowSumIsLess1 = true;
724 } else {
725 auto const columnIndex = originalToReducedStateIndexMap[entry.getColumn()];
726 matrixBuilder.addNextValue(currentRow, columnIndex, entry.getValue());
727 }
728 }
729 if (rowSumIsLess1) {
730 rowsWithSum1.set(currentRow, false);
731 }
732 targetRowValues.push_back(targetProbability);
733 conditionRowValues.push_back(conditionProbability);
734 ++currentRow;
735 };
736 // invoke the lambda
737 if (state == initialState) {
738 for (uint64_t origRowIndex : initialComponentExitRows) {
739 processRow(origRowIndex);
740 reducedToOriginalRowIndexMap.push_back(origRowIndex);
741 }
742 } else {
743 for (auto origRowIndex : transitionMatrix.getRowGroupIndices(state)) {
744 processRow(origRowIndex);
745 reducedToOriginalRowIndexMap.push_back(origRowIndex);
746 }
747 }
748 }
749 submatrix = matrixBuilder.build();
750
751 // eliminate ECs if present. We already checked that the initial state can not yield observation failure, so it cannot be part of an EC.
752 // For all remaining ECs, staying in an EC forever is reflected by collecting a value of zero for both, target and condition
753 storm::storage::BitVector allExceptInit(numSubmatrixRowGroups, true);
754 allExceptInit.set(initialStateInSubmatrix, false);
755 ecResult = eliminateEndComponents<ValueType>(allExceptInit, true, std::nullopt, submatrix, rowsWithSum1, targetRowValues, conditionRowValues);
756 if (ecResult) {
757 initialStateInSubmatrix = ecResult->oldToNewStateMapping[initialStateInSubmatrix];
758 }
759 isAcyclic = !storm::utility::graph::hasCycle(submatrix);
760 STORM_LOG_INFO("Processed model has " << submatrix.getRowGroupCount() << " states and " << submatrix.getRowGroupCount() << " choices and "
761 << submatrix.getEntryCount() << " transitions. Matrix is " << (isAcyclic ? "acyclic." : "cyclic."));
762
763 if (computeScheduler) {
764 // For easier conversion of schedulers to the original model, we create and update some index mappings
765 STORM_LOG_ASSERT(reducedToOriginalRowIndexMap.size() == numSubmatrixRows, "Unexpected size of reducedToOriginalRowIndexMap.");
766 if (ecResult.has_value()) {
767 // reducedToOriginalRowIndexMap needs to be updated so it maps from rows of the ec-eliminated system
768 std::vector<uint64_t> tmpReducedToOriginalRowIndexMap;
769 tmpReducedToOriginalRowIndexMap.reserve(submatrix.getRowCount());
770 for (uint64_t reducedRow = 0; reducedRow < submatrix.getRowCount(); ++reducedRow) {
771 uint64_t intermediateRow = ecResult->newToOldRowMapping.at(reducedRow);
772 tmpReducedToOriginalRowIndexMap.push_back(reducedToOriginalRowIndexMap[intermediateRow]);
773 }
774 reducedToOriginalRowIndexMap = std::move(tmpReducedToOriginalRowIndexMap);
775 // originalToReducedStateIndexMap needs to be updated so it maps into the ec-eliminated system
776 for (uint64_t originalStateIndex = 0; originalStateIndex < transitionMatrix.getRowGroupCount(); ++originalStateIndex) {
777 auto& reducedIndex = originalToReducedStateIndexMap[originalStateIndex];
778 if (subMatrixRowGroups.get(originalStateIndex)) {
779 reducedIndex = ecResult->oldToNewStateMapping.at(reducedIndex);
780 } else {
781 reducedIndex = std::numeric_limits<uint64_t>::max(); // The original state does not exist in the reduced model.
782 }
783 }
784 }
785 } else {
786 // Clear data that is only needed if we compute schedulers
787 originalToReducedStateIndexMap.clear();
788 reducedToOriginalRowIndexMap.clear();
789 ecResult.emplace();
790 initialComponentExitRows.clear();
791 initialComponentExitStates.clear();
792 }
793 }
794
795 SolutionType computeWeightedDiff(storm::Environment const& env, storm::OptimizationDirection const dir, ValueType const& targetWeight,
796 ValueType const& conditionWeight, storm::OptionalRef<std::vector<uint64_t>> schedulerOutput = {}) {
797 // Set up the solver.
798 if (!cachedSolver) {
799 auto solverEnv = env;
800 if (isAcyclic) {
801 STORM_LOG_INFO("Using acyclic min-max solver for weighted reachability computation.");
802 solverEnv.solver().minMax().setMethod(storm::solver::MinMaxMethod::Acyclic);
803 }
805 cachedSolver->setCachingEnabled(true);
806 cachedSolver->setRequirementsChecked();
807 cachedSolver->setHasUniqueSolution(true);
808 cachedSolver->setHasNoEndComponents(true);
809 cachedSolver->setLowerBound(-storm::utility::one<ValueType>());
810 cachedSolver->setUpperBound(storm::utility::one<ValueType>());
811 }
812 cachedSolver->setTrackScheduler(schedulerOutput.has_value());
813 cachedSolver->setOptimizationDirection(dir);
814
815 // Initialize the right-hand side vector.
816 createScaledVector(cachedB, targetWeight, targetRowValues, conditionWeight, conditionRowValues);
817
818 // Initialize the solution vector.
819 cachedX.assign(submatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
820
821 cachedSolver->solveEquations(env, cachedX, cachedB);
822 if (schedulerOutput) {
823 *schedulerOutput = cachedSolver->getSchedulerChoices();
824 }
825 return cachedX[initialStateInSubmatrix];
826 }
827
829 return initialStateInSubmatrix;
830 }
831
832 void evaluateScheduler(storm::Environment const& env, std::vector<uint64_t>& scheduler, std::vector<SolutionType>& targetResults,
833 std::vector<SolutionType>& conditionResults) {
834 if (scheduler.empty()) {
835 scheduler.resize(submatrix.getRowGroupCount(), 0);
836 }
837 if (targetResults.empty()) {
838 targetResults.resize(submatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
839 }
840 if (conditionResults.empty()) {
841 conditionResults.resize(submatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
842 }
843 auto solver = getScheduledSolver(env, scheduler);
844
845 cachedB.resize(submatrix.getRowGroupCount());
846 storm::utility::vector::selectVectorValues<ValueType>(cachedB, scheduler, submatrix.getRowGroupIndices(), targetRowValues);
847 solver->solveEquations(env, targetResults, cachedB);
848
849 storm::utility::vector::selectVectorValues<ValueType>(cachedB, scheduler, submatrix.getRowGroupIndices(), conditionRowValues);
850 solver->solveEquations(env, conditionResults, cachedB);
851 }
852
853 SolutionType evaluateScheduler(storm::Environment const& env, std::vector<uint64_t> const& scheduler) {
854 STORM_LOG_ASSERT(scheduler.size() == submatrix.getRowGroupCount(), "Scheduler size does not match number of row groups.");
855 auto solver = getScheduledSolver(env, scheduler);
856 cachedB.resize(submatrix.getRowGroupCount());
857 cachedX.resize(submatrix.getRowGroupCount());
858
859 storm::utility::vector::selectVectorValues<ValueType>(cachedB, scheduler, submatrix.getRowGroupIndices(), targetRowValues);
860 solver->solveEquations(env, cachedX, cachedB);
861 SolutionType targetValue = cachedX[initialStateInSubmatrix];
862
863 storm::utility::vector::selectVectorValues<ValueType>(cachedB, scheduler, submatrix.getRowGroupIndices(), conditionRowValues);
864 solver->solveEquations(env, cachedX, cachedB);
865 SolutionType conditionValue = cachedX[initialStateInSubmatrix];
866
867 return targetValue / conditionValue;
868 }
869
870 template<OptimizationDirection Dir>
871 bool improveScheduler(std::vector<uint64_t>& scheduler, ValueType const& lambda, std::vector<SolutionType> const& targetResults,
872 std::vector<SolutionType> const& conditionResults) {
873 bool improved = false;
874 for (uint64_t rowGroupIndex = 0; rowGroupIndex < scheduler.size(); ++rowGroupIndex) {
876 uint64_t optimalRowIndex{0};
877 ValueType scheduledValue;
878 for (auto rowIndex : submatrix.getRowGroupIndices(rowGroupIndex)) {
879 ValueType rowValue = targetRowValues[rowIndex] - lambda * conditionRowValues[rowIndex];
880 for (auto const& entry : submatrix.getRow(rowIndex)) {
881 rowValue += entry.getValue() * (targetResults[entry.getColumn()] - lambda * conditionResults[entry.getColumn()]);
882 }
883 if (rowIndex == scheduler[rowGroupIndex] + submatrix.getRowGroupIndices()[rowGroupIndex]) {
884 scheduledValue = rowValue;
885 }
886 if (groupValue &= rowValue) {
887 optimalRowIndex = rowIndex;
888 }
889 }
890 if (scheduledValue != *groupValue) {
891 scheduler[rowGroupIndex] = optimalRowIndex - submatrix.getRowGroupIndices()[rowGroupIndex];
892 improved = true;
893 }
894 }
895 return improved;
896 }
897
898 std::unique_ptr<storm::storage::Scheduler<ValueType>> constructSchedulerForInputModel(
899 std::vector<uint64_t> const& schedulerForReducedModel, storm::storage::SparseMatrix<ValueType> const& originalTransitionMatrix,
900 storm::storage::SparseMatrix<ValueType> const& originalBackwardTransitions, NormalFormData<ValueType> const& normalForm) const {
901 std::vector<uint64_t> originalRowToStateIndexMap; // maps original row indices to original state indices. transitionMatrix.getRowGroupIndices() are
902 // the inverse of that mapping
903 originalRowToStateIndexMap.reserve(originalTransitionMatrix.getRowCount());
904 for (uint64_t originalStateIndex = 0; originalStateIndex < originalTransitionMatrix.getRowGroupCount(); ++originalStateIndex) {
905 originalRowToStateIndexMap.insert(originalRowToStateIndexMap.end(), originalTransitionMatrix.getRowGroupSize(originalStateIndex),
906 originalStateIndex);
907 }
908
909 storm::storage::BitVector maybeStatesWithChoice(normalForm.maybeStates.size(), false);
910 uint64_t chosenInitialComponentExitState = std::numeric_limits<uint64_t>::max();
911 uint64_t chosenInitialComponentExit = std::numeric_limits<uint64_t>::max();
912 auto scheduler = std::make_unique<storm::storage::Scheduler<SolutionType>>(originalTransitionMatrix.getRowGroupCount());
913
914 uint64_t reducedState = 0;
915 for (auto const& choice : schedulerForReducedModel) {
916 uint64_t const reducedRowIndex = submatrix.getRowGroupIndices()[reducedState] + choice;
917 uint64_t const originalRowIndex = reducedToOriginalRowIndexMap[reducedRowIndex];
918 uint64_t const originalState = originalRowToStateIndexMap[originalRowIndex];
919 uint64_t const originalChoice = originalRowIndex - originalTransitionMatrix.getRowGroupIndices()[originalState];
920 scheduler->setChoice(originalChoice, originalState);
921 maybeStatesWithChoice.set(originalState, true);
922 if (reducedState == initialStateInSubmatrix) {
923 chosenInitialComponentExitState = originalState;
924 chosenInitialComponentExit = originalRowIndex;
925 }
926 ++reducedState;
927 }
928
929 auto const maybeStatesWithoutChoice = normalForm.maybeStates & ~maybeStatesWithChoice;
930 finalizeSchedulerForMaybeStates(*scheduler, originalTransitionMatrix, originalBackwardTransitions, normalForm.maybeStates, maybeStatesWithoutChoice,
931 maybeStatesWithChoice, originalToReducedStateIndexMap, normalForm, initialStateInSubmatrix, initialComponentExitStates,
932 initialComponentExitRows, chosenInitialComponentExitState, chosenInitialComponentExit);
933 return scheduler;
934 }
935
936 private:
937 void createScaledVector(std::vector<ValueType>& out, ValueType const& w1, std::vector<ValueType> const& v1, ValueType const& w2,
938 std::vector<ValueType> const& v2) const {
939 STORM_LOG_ASSERT(v1.size() == v2.size(), "Vector sizes must match.");
940 out.resize(v1.size());
941 storm::utility::vector::applyPointwise(v1, v2, out, [&w1, &w2](ValueType const& a, ValueType const& b) -> ValueType { return w1 * a + w2 * b; });
942 }
943
944 auto getScheduledSolver(storm::Environment const& env, std::vector<uint64_t> const& scheduler) const {
945 // apply the scheduler
947 bool const convertToEquationSystem = factory.getEquationProblemFormat(env) == storm::solver::LinearEquationSolverProblemFormat::EquationSystem;
948 auto scheduledMatrix = submatrix.selectRowsFromRowGroups(scheduler, convertToEquationSystem);
949 if (convertToEquationSystem) {
950 scheduledMatrix.convertToEquationSystem();
951 }
952 auto solver = factory.create(env, std::move(scheduledMatrix));
954 solver->setCachingEnabled(true);
955 return solver;
956 }
957
958 storm::storage::SparseMatrix<ValueType> submatrix;
959 storm::storage::BitVector rowsWithSum1;
960 std::vector<ValueType> targetRowValues;
961 std::vector<ValueType> conditionRowValues;
962 uint64_t initialStateInSubmatrix;
963 bool isAcyclic;
964 std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType, SolutionType>> cachedSolver;
965 std::vector<ValueType> cachedX;
966 std::vector<ValueType> cachedB;
967
968 // Data used to translate schedulers:
969 std::vector<uint64_t> originalToReducedStateIndexMap;
970 std::vector<uint64_t> reducedToOriginalRowIndexMap;
971 std::optional<typename storm::transformer::EndComponentEliminator<ValueType>::EndComponentEliminatorReturnType> ecResult;
972 storm::storage::BitVector initialComponentExitRows;
973 storm::storage::BitVector initialComponentExitStates;
974};
975
976template<typename ValueType, typename SolutionType = ValueType>
977typename internal::ResultReturnType<ValueType> computeViaBisection(Environment const& env, bool const useAdvancedBounds, bool const usePolicyTracking,
978 uint64_t const initialState, storm::solver::SolveGoal<ValueType, SolutionType> const& goal,
979 bool computeScheduler, storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
980 storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
981 NormalFormData<ValueType> const& normalForm) {
982 // We currently handle sound model checking incorrectly: we would need the actual lower/upper bounds of the weightedReachabilityHelper
984 "Bisection method does not adequately handle propagation of errors. Result is not necessarily sound.");
985
986 bool const relative = env.modelchecker().conditional().isRelativePrecision();
988
989 WeightedReachabilityHelper<ValueType, SolutionType> wrh(initialState, transitionMatrix, normalForm, computeScheduler);
990 SolutionType pMin{storm::utility::zero<SolutionType>()};
991 SolutionType pMax{storm::utility::one<SolutionType>()};
992
993 if (useAdvancedBounds) {
994 pMin = wrh.computeWeightedDiff(env, storm::OptimizationDirection::Minimize, storm::utility::zero<ValueType>(), storm::utility::one<ValueType>());
995 pMax = wrh.computeWeightedDiff(env, storm::OptimizationDirection::Maximize, storm::utility::zero<ValueType>(), storm::utility::one<ValueType>());
996 STORM_LOG_TRACE("Conditioning event bounds:\n\t Lower bound: " << storm::utility::convertNumber<double>(pMin)
997 << ",\n\t Upper bound: " << storm::utility::convertNumber<double>(pMax));
998 }
1001
1002 std::optional<std::vector<uint64_t>> lowerScheduler, upperScheduler, middleScheduler;
1003 storm::OptionalRef<std::vector<uint64_t>> middleSchedulerRef;
1004 if (usePolicyTracking) {
1005 lowerScheduler.emplace();
1006 upperScheduler.emplace();
1007 middleScheduler.emplace();
1008 middleSchedulerRef.reset(*middleScheduler);
1009 }
1010
1011 SolutionType middle = goal.isBounded() ? goal.thresholdValue() : (*lowerBound + *upperBound) / 2;
1012 [[maybe_unused]] SolutionType rationalCandiate = middle; // relevant for exact computations
1013 [[maybe_unused]] uint64_t rationalCandidateCount = 0;
1014 std::set<SolutionType> checkedMiddleValues; // Middle values that have been checked already
1015 bool terminatedThroughPolicyTracking = false;
1016 for (uint64_t iterationCount = 1; true; ++iterationCount) {
1017 // evaluate the current middle
1018 SolutionType const middleValue = wrh.computeWeightedDiff(env, goal.direction(), storm::utility::one<ValueType>(), -middle, middleSchedulerRef);
1019 checkedMiddleValues.insert(middle);
1020 // update the bounds and new middle value according to the bisection method
1021 if (!useAdvancedBounds) {
1022 if (middleValue >= storm::utility::zero<ValueType>()) {
1023 if (lowerBound &= middle) {
1024 lowerScheduler.swap(middleScheduler);
1025 }
1026 }
1027 if (middleValue <= storm::utility::zero<ValueType>()) {
1028 if (upperBound &= middle) {
1029 upperScheduler.swap(middleScheduler);
1030 }
1031 }
1032 middle = (*lowerBound + *upperBound) / 2; // update middle to the average of the bounds
1033 } else {
1034 if (middleValue >= storm::utility::zero<ValueType>()) {
1035 if (lowerBound &= middle + (middleValue / pMax)) {
1036 lowerScheduler.swap(middleScheduler);
1037 }
1038 upperBound &= middle + (middleValue / pMin);
1039 }
1040 if (middleValue <= storm::utility::zero<ValueType>()) {
1041 lowerBound &= middle + (middleValue / pMin);
1042 if (upperBound &= middle + (middleValue / pMax)) {
1043 upperScheduler.swap(middleScheduler);
1044 }
1045 }
1046 // update middle to the average of the bounds, but use the middleValue as a hint:
1047 // If middleValue is close to -1, we use a value close to lowerBound
1048 // If middleValue is close to 0, we use a value close to the avg(lowerBound, upperBound)
1049 // If middleValue is close to +1, we use a value close to upperBound
1050 middle = *lowerBound + (storm::utility::one<SolutionType>() + middleValue) * (*upperBound - *lowerBound) / 2;
1051
1053 if (*lowerBound > *upperBound) {
1054 std::swap(*lowerBound, *upperBound);
1055 }
1056 } else {
1057 STORM_LOG_ASSERT(middle >= *lowerBound && middle <= *upperBound, "Bisection method bounds are inconsistent.");
1058 }
1059 }
1060 // check for convergence
1061 SolutionType const boundDiff = *upperBound - *lowerBound;
1062 STORM_LOG_TRACE("Iteration #" << iterationCount << ":\n\t Lower bound: " << *lowerBound << ",\n\t Upper bound: " << *upperBound
1063 << ",\n\t Difference: " << boundDiff << ",\n\t Middle val: " << middleValue
1064 << ",\n\t Difference bound: " << (relative ? (precision * *lowerBound) : precision) << ".");
1065 if (goal.isBounded()) {
1066 STORM_LOG_TRACE("Using threshold " << storm::utility::convertNumber<double>(goal.thresholdValue()) << " with comparison "
1067 << (goal.boundIsALowerBound() ? (goal.boundIsStrict() ? ">" : ">=") : (goal.boundIsStrict() ? "<" : "<="))
1068 << ".");
1069 }
1070 if (boundDiff <= (relative ? (precision * *lowerBound) : precision)) {
1071 STORM_LOG_INFO("Bisection method converged after " << iterationCount << " iterations. Difference is "
1072 << std::setprecision(std::numeric_limits<double>::digits10)
1073 << storm::utility::convertNumber<double>(boundDiff) << ".");
1074 break;
1075 } else if (usePolicyTracking && lowerScheduler && upperScheduler && (*lowerScheduler == *upperScheduler)) {
1076 STORM_LOG_INFO("Bisection method converged after " << iterationCount << " iterations due to identical schedulers for lower and upper bound.");
1077 auto result = wrh.evaluateScheduler(env, *lowerScheduler);
1078 lowerBound &= result;
1079 upperBound &= result;
1080 terminatedThroughPolicyTracking = true;
1081 break;
1082 }
1083 // Check if bounds are fully below or above threshold
1084 if (goal.isBounded() && (*upperBound <= goal.thresholdValue() || (*lowerBound >= goal.thresholdValue()))) {
1085 STORM_LOG_INFO("Bisection method determined result after " << iterationCount << " iterations. Found bounds are ["
1086 << storm::utility::convertNumber<double>(*lowerBound) << ", "
1087 << storm::utility::convertNumber<double>(*upperBound) << "], threshold is "
1089 break;
1090 }
1092 STORM_LOG_WARN("Precision of non-exact type exceeded: Bisection method has not terminated, but the difference between upper and lower bound is "
1093 << boundDiff << ".");
1094 }
1095 // check for early termination
1097 STORM_LOG_WARN("Bisection solver aborted after " << iterationCount << "iterations. Bound difference is "
1098 << storm::utility::convertNumber<double>(boundDiff) << ".");
1099 break;
1100 }
1101 // process the middle value for the next iteration
1102 // This sets the middle value to a rational number with the smallest enumerator/denominator that is still within the bounds
1103 // With close bounds this can lead to the middle being set to exactly the lower or upper bound, thus allowing for an exact answer.
1105 // Check if the rationalCandidate has been within the bounds for four iterations.
1106 // If yes, we take that as our next "middle".
1107 // Otherwise, we set a new rationalCandidate.
1108 // This heuristic ensures that we eventually check every rational number without affecting the binary search too much
1109 if (rationalCandidateCount >= 4 && rationalCandiate >= *lowerBound && rationalCandiate <= *upperBound &&
1110 !checkedMiddleValues.contains(rationalCandiate)) {
1111 middle = rationalCandiate;
1112 rationalCandidateCount = 0;
1113 } else {
1114 // find a rational number with a concise representation within our current bounds
1115 bool const includeLower = !checkedMiddleValues.contains(*lowerBound);
1116 bool const includeUpper = !checkedMiddleValues.contains(*upperBound);
1117 auto newRationalCandiate = storm::utility::findRational(*lowerBound, includeLower, *upperBound, includeUpper);
1118 if (rationalCandiate == newRationalCandiate) {
1119 ++rationalCandidateCount;
1120 } else {
1121 rationalCandiate = newRationalCandiate;
1122 rationalCandidateCount = 0;
1123 }
1124 // Also simplify the middle value
1125 SolutionType delta =
1126 std::min<SolutionType>(*upperBound - middle, middle - *lowerBound) / storm::utility::convertNumber<SolutionType, uint64_t>(16);
1127 middle = storm::utility::findRational(middle - delta, true, middle + delta, true);
1128 }
1129 }
1130 // Since above code might never set 'middle' to exactly zero or one, we check if that could be necessary after a couple of iterations
1131 if (iterationCount == 8) { // 8 is just a heuristic value, it could be any number
1132 if (storm::utility::isZero(*lowerBound) && !checkedMiddleValues.contains(storm::utility::zero<SolutionType>())) {
1134 } else if (storm::utility::isOne(*upperBound) && !checkedMiddleValues.contains(storm::utility::one<SolutionType>())) {
1136 }
1137 }
1138 }
1139
1140 // Create result without scheduler
1141 auto finalResult = ResultReturnType<ValueType>((*lowerBound + *upperBound) / 2);
1142
1143 if (!computeScheduler) {
1144 return finalResult; // nothing else to do
1145 }
1146 // If requested, construct the scheduler for the original model
1147 std::vector<uint64_t> reducedSchedulerChoices;
1148 if (terminatedThroughPolicyTracking) {
1149 // We already have computed a scheduler
1150 reducedSchedulerChoices = std::move(*lowerScheduler);
1151 } else {
1152 // Compute a scheduler on the middle result by performing one more iteration
1153 wrh.computeWeightedDiff(env, goal.direction(), storm::utility::one<ValueType>(), -finalResult.initialStateValue, reducedSchedulerChoices);
1154 }
1155 finalResult.scheduler = wrh.constructSchedulerForInputModel(reducedSchedulerChoices, transitionMatrix, backwardTransitions, normalForm);
1156 return finalResult;
1157}
1158
1159template<typename ValueType, typename SolutionType = ValueType>
1161 storm::solver::SolveGoal<ValueType, SolutionType> const& goal, bool computeScheduler,
1162 storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
1163 storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
1164 NormalFormData<ValueType> const& normalForm) {
1165 using enum ConditionalAlgorithmSetting;
1167 "Unhandled Bisection algorithm " << alg << ".");
1168 bool const useAdvancedBounds = (alg == BisectionAdvanced || alg == BisectionAdvancedPolicyTracking);
1169 bool const usePolicyTracking = (alg == BisectionPolicyTracking || alg == BisectionAdvancedPolicyTracking);
1170 return computeViaBisection(env, useAdvancedBounds, usePolicyTracking, initialState, goal, computeScheduler, transitionMatrix, backwardTransitions,
1171 normalForm);
1172}
1173
1174template<typename ValueType, typename SolutionType = ValueType>
1175typename internal::ResultReturnType<ValueType> decideThreshold(Environment const& env, uint64_t const initialState,
1176 storm::OptimizationDirection const& direction, SolutionType const& threshold,
1177 bool computeScheduler, storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
1178 storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
1179 NormalFormData<ValueType> const& normalForm) {
1180 // We currently handle sound model checking incorrectly: we would need the actual lower/upper bounds of the weightedReachabilityHelper
1181
1182 WeightedReachabilityHelper<ValueType, SolutionType> wrh(initialState, transitionMatrix, normalForm, computeScheduler);
1183
1184 std::optional<std::vector<uint64_t>> scheduler;
1186 if (computeScheduler) {
1187 scheduler.emplace();
1188 schedulerRef.reset(*scheduler);
1189 }
1190
1191 SolutionType val = wrh.computeWeightedDiff(env, direction, storm::utility::one<ValueType>(), -threshold, schedulerRef);
1192 SolutionType outputProbability;
1194 // if val is positive, the conditional probability is (strictly) greater than threshold
1195 outputProbability = storm::utility::one<SolutionType>();
1196 } else if (val < storm::utility::zero<SolutionType>()) {
1197 // if val is negative, the conditional probability is (strictly) smaller than threshold
1198 outputProbability = storm::utility::zero<SolutionType>();
1199 } else {
1200 // if val is zero, the conditional probability equals the threshold
1201 outputProbability = threshold;
1202 }
1203 auto finalResult = ResultReturnType<SolutionType>(outputProbability);
1204
1205 if (computeScheduler) {
1206 // If requested, construct the scheduler for the original model
1207 finalResult.scheduler = wrh.constructSchedulerForInputModel(scheduler.value(), transitionMatrix, backwardTransitions, normalForm);
1208 }
1209 return finalResult;
1210}
1211
1212template<typename ValueType, typename SolutionType = ValueType>
1215 storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
1216 NormalFormData<ValueType> const& normalForm) {
1217 WeightedReachabilityHelper<ValueType, SolutionType> wrh(initialState, transitionMatrix, normalForm, false); // scheduler computation not yet implemented.
1218
1219 std::vector<uint64_t> scheduler;
1220 std::vector<SolutionType> targetResults, conditionResults;
1221 for (uint64_t iterationCount = 1; true; ++iterationCount) {
1222 wrh.evaluateScheduler(env, scheduler, targetResults, conditionResults);
1224 targetResults[wrh.getInternalInitialState()] <= conditionResults[wrh.getInternalInitialState()],
1225 "Potential numerical issues: the probability to reach the target is greater than the probability to reach the condition. Difference is "
1227 conditionResults[wrh.getInternalInitialState()]))
1228 << ".");
1229 ValueType const lambda = storm::utility::isZero(conditionResults[wrh.getInternalInitialState()])
1231 : ValueType(targetResults[wrh.getInternalInitialState()] / conditionResults[wrh.getInternalInitialState()]);
1232 bool schedulerChanged{false};
1233 if (storm::solver::minimize(dir)) {
1234 schedulerChanged = wrh.template improveScheduler<storm::OptimizationDirection::Minimize>(scheduler, lambda, targetResults, conditionResults);
1235 } else {
1236 schedulerChanged = wrh.template improveScheduler<storm::OptimizationDirection::Maximize>(scheduler, lambda, targetResults, conditionResults);
1237 }
1238 if (!schedulerChanged) {
1239 STORM_LOG_INFO("Policy iteration for conditional probabilities converged after " << iterationCount << " iterations.");
1240 return lambda;
1241 }
1243 STORM_LOG_WARN("Policy iteration for conditional probabilities converged aborted after " << iterationCount << "iterations.");
1244 return lambda;
1245 }
1246 }
1247}
1248
1249template<typename ValueType, typename SolutionType = ValueType>
1250std::optional<SolutionType> handleTrivialCases(uint64_t const initialState, NormalFormData<ValueType> const& normalForm) {
1251 if (normalForm.terminalStates.get(initialState)) {
1252 STORM_LOG_DEBUG("Initial state is terminal.");
1253 if (normalForm.conditionStates.get(initialState)) {
1254 return normalForm.getTargetValue(initialState); // The value is already known, nothing to do.
1255 } else {
1256 STORM_LOG_THROW(!normalForm.universalObservationFailureStates.get(initialState), storm::exceptions::InvalidPropertyException,
1257 "Trying to compute undefined conditional probability: the condition has probability 0 under all policies.");
1258 // The last case for a terminal initial state is that it is already target and the condition is reachable with non-zero probability.
1259 // In this case, all schedulers induce a conditional probability of 1 (or do not reach the condition, i.e., have undefined value)
1261 }
1262 } else {
1263 // Catch the case where all terminal states have value zero
1264 if (normalForm.nonZeroTargetStateValues.empty()) {
1266 }
1267 }
1268 return std::nullopt; // No trivial case applies, we need to compute the value.
1269}
1270
1271} // namespace internal
1272
1273template<typename ValueType, typename SolutionType>
1275 bool produceSchedulers, storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
1276 storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
1277 storm::storage::BitVector const& targetStates, storm::storage::BitVector const& conditionStates) {
1278 auto precision = env.modelchecker().conditional().getPrecision();
1280 STORM_LOG_INFO("Setting the conditional precision to 0 since the value type is exact and the precision was not explicitly set by the user.");
1282 }
1283
1284 // We might require adapting the precision of the solver to counter error propagation (e.g. when computing the normal form).
1285 auto normalFormConstructionEnv = env;
1286 auto analysisEnv = env;
1287 if (env.solver().isForceSoundness()) {
1288 // We intuitively have to divide the precision into two parts, one for computations when constructing the normal form and one for the actual analysis.
1289 // As the former is usually less numerically challenging, we use a factor of 1/10 for the normal form construction and 9/10 for the analysis.
1290 auto const normalFormPrecisionFactor = storm::utility::convertNumber<storm::RationalNumber, std::string>("1/10");
1291 normalFormConstructionEnv.modelchecker().conditional().setPrecision(precision * normalFormPrecisionFactor, false);
1292 analysisEnv.modelchecker().conditional().setPrecision(precision * (storm::utility::one<storm::RationalNumber>() - normalFormPrecisionFactor), false);
1293 } else {
1294 normalFormConstructionEnv.modelchecker().conditional().setPrecision(precision, false);
1295 analysisEnv.modelchecker().conditional().setPrecision(precision, false);
1296 }
1297
1298 // We first translate the problem into a normal form.
1299 // @see doi.org/10.1007/978-3-642-54862-8_43
1300 STORM_LOG_THROW(goal.hasRelevantValues(), storm::exceptions::NotSupportedException,
1301 "No initial state given. Conditional probabilities can only be computed for models with a single initial state.");
1302 STORM_LOG_THROW(goal.relevantValues().hasUniqueSetBit(), storm::exceptions::NotSupportedException,
1303 "Only one initial state is supported for conditional probabilities.");
1304 STORM_LOG_TRACE("Computing conditional probabilities for a model with " << transitionMatrix.getRowGroupCount() << " states and "
1305 << transitionMatrix.getEntryCount() << " transitions.");
1306 auto normalFormData = internal::obtainNormalForm(normalFormConstructionEnv, goal.direction(), produceSchedulers, transitionMatrix, backwardTransitions,
1307 goal.relevantValues(), targetStates, conditionStates);
1308 // Then, we solve the induced problem using the selected algorithm
1309 auto const initialState = *goal.relevantValues().begin();
1310 ValueType initialStateValue = -storm::utility::one<ValueType>();
1311 std::unique_ptr<storm::storage::Scheduler<SolutionType>> scheduler = nullptr;
1312 if (auto trivialValue = internal::handleTrivialCases<ValueType, SolutionType>(initialState, normalFormData); trivialValue.has_value()) {
1313 initialStateValue = *trivialValue;
1314 if (initialStateValue == storm::utility::zero<ValueType>() && !normalFormData.terminalStates.get(initialState) && produceSchedulers) {
1315 // we need to compute a scheduler that at least reaches the condition with non-zero probability
1316 auto initialStateBitVector = storm::storage::BitVector(transitionMatrix.getRowGroupCount(), false);
1317 initialStateBitVector.set(initialState, true);
1319 env, storm::solver::SolveGoal<ValueType>(storm::solver::OptimizationDirection::Maximize, initialStateBitVector), transitionMatrix,
1320 transitionMatrix.transpose(true), storm::storage::BitVector(conditionStates.size(), true), conditionStates, false, true);
1321 scheduler =
1322 std::unique_ptr<storm::storage::Scheduler<SolutionType>>(new storm::storage::Scheduler<SolutionType>(transitionMatrix.getRowGroupCount()));
1323 auto stateId = 0;
1324 for (uint64_t state = 0; state < transitionMatrix.getRowGroupCount(); ++state) {
1325 scheduler->setChoice(conditionReachResult.scheduler->getChoice(stateId), state);
1326 ++stateId;
1327 }
1328 } else {
1329 scheduler =
1330 std::unique_ptr<storm::storage::Scheduler<SolutionType>>(new storm::storage::Scheduler<SolutionType>(transitionMatrix.getRowGroupCount()));
1331 }
1332 STORM_LOG_DEBUG("Initial state has trivial value " << initialStateValue);
1333 } else {
1334 STORM_LOG_ASSERT(normalFormData.maybeStates.get(initialState), "Initial state must be a maybe state if it is not a terminal state.");
1335 auto alg = analysisEnv.modelchecker().conditional().getAlgorithm();
1338 }
1339
1340 STORM_LOG_INFO("Analyzing normal form with " << normalFormData.maybeStates.getNumberOfSetBits() << " maybe states using algorithm '" << alg << ".");
1342 switch (alg) {
1344 auto restartEnv = analysisEnv;
1345 restartEnv.solver().minMax().setPrecision(analysisEnv.modelchecker().conditional().getPrecision());
1346 restartEnv.solver().minMax().setRelativeTerminationCriterion(analysisEnv.modelchecker().conditional().isRelativePrecision());
1347
1348 result =
1349 internal::computeViaRestartMethod(restartEnv, initialState, goal, produceSchedulers, transitionMatrix, backwardTransitions, normalFormData);
1350 break;
1351 }
1356 if (goal.isBounded()) {
1357 result = internal::decideThreshold(analysisEnv, initialState, goal.direction(), goal.thresholdValue(), produceSchedulers, transitionMatrix,
1358 backwardTransitions, normalFormData);
1359 } else {
1360 result = internal::computeViaBisection(analysisEnv, alg, initialState, goal, produceSchedulers, transitionMatrix, backwardTransitions,
1361 normalFormData);
1362 }
1363 break;
1364 }
1366 result = internal::computeViaPolicyIteration(analysisEnv, initialState, goal.direction(), transitionMatrix, normalFormData);
1367 break;
1368 }
1369 default: {
1370 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Unknown conditional probability algorithm: " << alg << ".");
1371 }
1372 }
1373 initialStateValue = result.initialStateValue;
1374 scheduler = std::move(result.scheduler);
1375 }
1376 std::unique_ptr<CheckResult> result(new ExplicitQuantitativeCheckResult<SolutionType>(initialState, initialStateValue));
1377
1378 // if produce schedulers was set, we have to construct a scheduler with memory
1379 if (produceSchedulers && scheduler) {
1380 storm::utility::graph::computeSchedulerProb1E(normalFormData.targetStates, transitionMatrix, backwardTransitions, normalFormData.targetStates,
1381 targetStates, *scheduler);
1382 storm::utility::graph::computeSchedulerProb1E(normalFormData.conditionStates, transitionMatrix, backwardTransitions, normalFormData.conditionStates,
1383 conditionStates, *scheduler);
1384 // fill in the scheduler with default choices for states that are missing a choice, these states should be just the ones from which the condition is
1385 // unreachable this is also used to fill choices for the trivial cases
1386 for (uint64_t state = 0; state < transitionMatrix.getRowGroupCount(); ++state) {
1387 if (!scheduler->isChoiceSelected(state)) {
1388 // select an arbitrary choice
1389 scheduler->setChoice(0, state);
1390 }
1391 }
1392
1393 // create scheduler with memory structure
1394 storm::storage::MemoryStructure::TransitionMatrix memoryTransitions(3, std::vector<boost::optional<storm::storage::BitVector>>(3, boost::none));
1395 storm::models::sparse::StateLabeling memoryStateLabeling(3);
1396 memoryStateLabeling.addLabel("init_memory");
1397 memoryStateLabeling.addLabel("condition_reached");
1398 memoryStateLabeling.addLabel("target_reached");
1399 memoryStateLabeling.addLabelToState("init_memory", 0);
1400 memoryStateLabeling.addLabelToState("condition_reached", 1);
1401 memoryStateLabeling.addLabelToState("target_reached", 2);
1402
1403 storm::storage::BitVector allTransitions(transitionMatrix.getEntryCount(), true);
1404 storm::storage::BitVector conditionExitTransitions(transitionMatrix.getEntryCount(), false);
1405 storm::storage::BitVector targetExitTransitions(transitionMatrix.getEntryCount(), false);
1406
1407 for (uint64_t state : conditionStates) {
1408 for (auto choice : transitionMatrix.getRowGroupIndices(state)) {
1409 for (auto entryIt = transitionMatrix.getRow(choice).begin(); entryIt < transitionMatrix.getRow(choice).end(); ++entryIt) {
1410 conditionExitTransitions.set(entryIt - transitionMatrix.begin(), true);
1411 }
1412 }
1413 }
1414 for (uint64_t state : targetStates) {
1415 for (auto choice : transitionMatrix.getRowGroupIndices(state)) {
1416 for (auto entryIt = transitionMatrix.getRow(choice).begin(); entryIt < transitionMatrix.getRow(choice).end(); ++entryIt) {
1417 targetExitTransitions.set(entryIt - transitionMatrix.begin(), true);
1418 }
1419 }
1420 }
1421
1422 memoryTransitions[0][0] =
1423 allTransitions & ~conditionExitTransitions & ~targetExitTransitions; // if neither condition nor target reached, stay in init_memory
1424 memoryTransitions[0][1] = conditionExitTransitions;
1425 memoryTransitions[0][2] = targetExitTransitions & ~conditionExitTransitions;
1426 memoryTransitions[1][1] = allTransitions; // once condition reached, stay in that memory state
1427 memoryTransitions[2][2] = allTransitions; // once target reached, stay in that memory state
1428
1429 // this assumes there is a single initial state
1430 auto memoryStructure = storm::storage::MemoryStructure(memoryTransitions, memoryStateLabeling, std::vector<uint64_t>(1, 0), true);
1431
1432 auto finalScheduler = std::unique_ptr<storm::storage::Scheduler<SolutionType>>(
1433 new storm::storage::Scheduler<SolutionType>(transitionMatrix.getRowGroupCount(), std::move(memoryStructure)));
1434
1435 for (uint64_t state = 0; state < transitionMatrix.getRowGroupCount(); ++state) {
1436 // set choices for memory 0
1437 if (conditionStates.get(state)) {
1438 if (normalFormData.schedulerChoicesForReachingTargetStates->isChoiceSelected(state)) {
1439 finalScheduler->setChoice(normalFormData.schedulerChoicesForReachingTargetStates->getChoice(state), state, 0);
1440 } else {
1441 finalScheduler->setChoice(0, state, 0); // arbitrary choice if no choice was recorded.
1442 }
1443 } else if (targetStates.get(state)) {
1444 if (normalFormData.schedulerChoicesForReachingConditionStates->isChoiceSelected(state)) {
1445 finalScheduler->setChoice(normalFormData.schedulerChoicesForReachingConditionStates->getChoice(state), state, 0);
1446 } else {
1447 finalScheduler->setChoice(0, state, 0); // arbitrary choice if no choice was recorded.
1448 }
1449 } else {
1450 finalScheduler->setChoice(scheduler->getChoice(state), state, 0);
1451 }
1452
1453 // set choices for memory 1, these are the choices after condition was reached
1454 if (normalFormData.schedulerChoicesForReachingTargetStates->isChoiceSelected(state)) {
1455 finalScheduler->setChoice(normalFormData.schedulerChoicesForReachingTargetStates->getChoice(state), state, 1);
1456 } else {
1457 finalScheduler->setChoice(0, state, 1); // arbitrary choice if no choice was recorded.
1458 }
1459 // set choices for memory 2, these are the choices after target was reached
1460 if (normalFormData.schedulerChoicesForReachingConditionStates->isChoiceSelected(state)) {
1461 finalScheduler->setChoice(normalFormData.schedulerChoicesForReachingConditionStates->getChoice(state), state, 2);
1462 } else {
1463 finalScheduler->setChoice(0, state, 2); // arbitrary choice if no choice was recorded.
1464 }
1465 }
1466
1467 result->asExplicitQuantitativeCheckResult<SolutionType>().setScheduler(std::move(finalScheduler));
1468 }
1469 return result;
1470}
1471
1472template std::unique_ptr<CheckResult> computeConditionalProbabilities(Environment const& env, storm::solver::SolveGoal<double>&& goal, bool produceSchedulers,
1473 storm::storage::SparseMatrix<double> const& transitionMatrix,
1474 storm::storage::SparseMatrix<double> const& backwardTransitions,
1475 storm::storage::BitVector const& targetStates,
1476 storm::storage::BitVector const& conditionStates);
1477
1479 bool produceSchedulers,
1481 storm::storage::SparseMatrix<storm::RationalNumber> const& backwardTransitions,
1482 storm::storage::BitVector const& targetStates,
1483 storm::storage::BitVector const& conditionStates);
1484
1485} // namespace storm::modelchecker
SolverEnvironment & solver()
ModelCheckerEnvironment & modelchecker()
void setPrecision(storm::RationalNumber value)
ConditionalModelCheckerEnvironment & conditional()
Helper class that optionally holds a reference to an object of type T.
Definition OptionalRef.h:48
void reset()
Unsets the reference.
MinMaxSolverEnvironment & minMax()
A quantitative check result over the states of a sparse model.
static MDPSparseModelCheckingHelperReturnType< SolutionType > computeUntilProbabilities(Environment const &env, storm::solver::SolveGoal< ValueType, SolutionType > &&goal, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, bool qualitative, bool produceScheduler, ModelCheckerHint const &hint=ModelCheckerHint())
A helper class that computes (weighted) reachability probabilities for a given MDP in normal form.
bool improveScheduler(std::vector< uint64_t > &scheduler, ValueType const &lambda, std::vector< SolutionType > const &targetResults, std::vector< SolutionType > const &conditionResults)
void evaluateScheduler(storm::Environment const &env, std::vector< uint64_t > &scheduler, std::vector< SolutionType > &targetResults, std::vector< SolutionType > &conditionResults)
SolutionType evaluateScheduler(storm::Environment const &env, std::vector< uint64_t > const &scheduler)
SolutionType computeWeightedDiff(storm::Environment const &env, storm::OptimizationDirection const dir, ValueType const &targetWeight, ValueType const &conditionWeight, storm::OptionalRef< std::vector< uint64_t > > schedulerOutput={})
WeightedReachabilityHelper(uint64_t const initialState, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, NormalFormData< ValueType > const &normalForm, bool computeScheduler)
std::unique_ptr< storm::storage::Scheduler< ValueType > > constructSchedulerForInputModel(std::vector< uint64_t > const &schedulerForReducedModel, storm::storage::SparseMatrix< ValueType > const &originalTransitionMatrix, storm::storage::SparseMatrix< ValueType > const &originalBackwardTransitions, NormalFormData< ValueType > const &normalForm) const
void addLabel(std::string const &label)
Adds a new label to the labelings.
This class manages the labeling of the state space with a number of (atomic) labels.
void addLabelToState(std::string const &label, storm::storage::sparse::state_type state)
Adds a label to a given state.
virtual std::unique_ptr< LinearEquationSolver< ValueType > > create(Environment const &env) const override
Creates an equation solver with the current settings, but without a matrix.
virtual std::unique_ptr< MinMaxLinearEquationSolver< ValueType, SolutionType > > create(Environment const &env) const override
virtual LinearEquationSolverProblemFormat getEquationProblemFormat(Environment const &env) const
Retrieves the problem format that the solver expects if it was created with the current settings.
bool boundIsALowerBound() const
SolutionType const & thresholdValue() const
OptimizationDirection direction() const
Definition SolveGoal.cpp:89
storm::logic::ComparisonType boundComparisonType() const
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
std::vector< uint64_t > getNumberOfSetBitsBeforeIndices() const
Retrieves a vector that holds at position i the number of bits set before index i.
bool empty() const
Retrieves whether no bits are set to true in this bit vector.
uint64_t getNextUnsetIndex(uint64_t startingIndex) const
Retrieves the index of the bit that is the next bit set to false in the 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.
void resize(uint64_t newLength, bool init=false)
Resizes the bit vector to hold the given new number of bits.
bool get(uint64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
This class represents the decomposition of a nondeterministic model into its maximal end components.
This class represents a (deterministic) memory structure that can be used to encode certain events (s...
std::vector< std::vector< boost::optional< storm::storage::BitVector > > > TransitionMatrix
This class defines which action is chosen in a particular state of a non-deterministic model.
Definition Scheduler.h:18
SchedulerChoice< ValueType > const & getChoice(uint_fast64_t modelState, uint_fast64_t memoryState=0) const
Gets the choice defined by the scheduler for the given model and memory state.
Definition Scheduler.cpp:94
void setChoice(SchedulerChoice< ValueType > const &choice, uint_fast64_t modelState, uint_fast64_t memoryState=0)
Sets the choice defined by the scheduler for the given state.
Definition Scheduler.cpp:38
bool isChoiceSelected(BitVector const &selectedStates, uint64_t memoryState=0) const
Is the scheduler defined on the states indicated by the selected-states bitvector?
Definition Scheduler.cpp:69
const_iterator end() const
Retrieves an iterator that points past the last entry of the rows.
const_iterator begin() const
Retrieves an iterator that points to the beginning of the rows.
A class that can be used to build a sparse matrix by adding value by value.
void addNextValue(index_type row, index_type column, value_type const &value)
Sets the matrix entry at the given row and column to the given value.
void newRowGroup(index_type startingRow)
Starts a new row group in the matrix.
SparseMatrix< value_type > build(index_type overriddenRowCount=0, index_type overriddenColumnCount=0, index_type overriddenRowGroupCount=0)
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.
index_type getEntryCount() const
Returns the number of entries in the matrix.
index_type getNumRowsInRowGroups(storm::storage::BitVector const &groupConstraint) const
Returns the total number of rows that are in one of the specified row groups.
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 ...
const_iterator begin(index_type row) const
Retrieves an iterator that points to the beginning of the given row.
index_type getRowGroupCount() const
Returns the number of row groups in the matrix.
std::vector< index_type > const & getRowGroupIndices() const
Returns the grouping of rows of this matrix.
index_type getRowGroupSize(index_type group) const
Returns the size of the given row group.
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.
static EndComponentEliminatorReturnType transform(storm::storage::SparseMatrix< ValueType > const &originalMatrix, storm::storage::MaximalEndComponentDecomposition< ValueType > ecs, storm::storage::BitVector const &subsystemStates, storm::storage::BitVector const &addSinkRowStates, bool addSelfLoopAtSinkStates=false)
Stores and manages an extremal (maximal or minimal) value.
Definition Extremum.h:17
#define STORM_LOG_INFO(message)
Definition logging.h:34
#define STORM_LOG_WARN(message)
Definition logging.h:35
#define STORM_LOG_DEBUG(message)
Definition logging.h:28
#define STORM_LOG_TRACE(message)
Definition logging.h:22
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:54
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:39
std::unique_ptr< storm::storage::Scheduler< ValueType > > computeReachabilityProbabilities(Environment const &env, std::map< uint64_t, ValueType > &nonZeroResults, storm::solver::OptimizationDirection const dir, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::BitVector const &initialStates, storm::storage::BitVector const &allowedStates, storm::storage::BitVector const &targetStates, bool computeScheduler=true)
Computes the reachability probabilities for the given target states and inserts all non-zero values i...
NormalFormData< ValueType > obtainNormalForm(Environment const &env, storm::solver::OptimizationDirection const dir, bool computeScheduler, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, storm::storage::BitVector const &relevantStates, storm::storage::BitVector const &targetStates, storm::storage::BitVector const &conditionStates)
std::optional< typename storm::transformer::EndComponentEliminator< ValueType >::EndComponentEliminatorReturnType > eliminateEndComponents(storm::storage::BitVector const &possibleEcStates, bool addRowAtRepresentativeState, std::optional< uint64_t > const representativeRowEntry, storm::storage::SparseMatrix< ValueType > &matrix, storm::storage::BitVector &rowsWithSum1, std::vector< ValueType > &rowValues1, storm::OptionalRef< std::vector< ValueType > > rowValues2={})
internal::ResultReturnType< ValueType > computeViaRestartMethod(Environment const &env, uint64_t const initialState, storm::solver::SolveGoal< ValueType, SolutionType > const &goal, bool computeScheduler, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, NormalFormData< ValueType > const &normalForm)
Uses the restart method by Baier et al.
internal::ResultReturnType< SolutionType > computeViaPolicyIteration(Environment const &env, uint64_t const initialState, storm::solver::OptimizationDirection const dir, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, NormalFormData< ValueType > const &normalForm)
SolutionType solveMinMaxEquationSystem(storm::Environment const &env, storm::storage::SparseMatrix< ValueType > const &matrix, std::vector< ValueType > const &rowValues, storm::storage::BitVector const &rowsWithSum1, storm::solver::SolveGoal< ValueType, SolutionType > const &goal, uint64_t const initialState, std::optional< std::vector< uint64_t > > &schedulerOutput)
internal::ResultReturnType< ValueType > computeViaBisection(Environment const &env, bool const useAdvancedBounds, bool const usePolicyTracking, uint64_t const initialState, storm::solver::SolveGoal< ValueType, SolutionType > const &goal, bool computeScheduler, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, NormalFormData< ValueType > const &normalForm)
std::optional< SolutionType > handleTrivialCases(uint64_t const initialState, NormalFormData< ValueType > const &normalForm)
internal::ResultReturnType< ValueType > decideThreshold(Environment const &env, uint64_t const initialState, storm::OptimizationDirection const &direction, SolutionType const &threshold, bool computeScheduler, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, NormalFormData< ValueType > const &normalForm)
void finalizeSchedulerForMaybeStates(storm::storage::Scheduler< SolutionType > &scheduler, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, storm::storage::BitVector const &maybeStates, storm::storage::BitVector const &maybeStatesWithoutChoice, storm::storage::BitVector const &maybeStatesWithChoice, std::vector< uint64_t > const &stateToFinalEc, NormalFormData< ValueType > const &normalForm, uint64_t initialComponentIndex, storm::storage::BitVector const &initialComponentExitStates, storm::storage::BitVector const &initialComponentExitRows, uint64_t chosenInitialComponentExitState, uint64_t chosenInitialComponentExit)
std::unique_ptr< CheckResult > computeConditionalProbabilities(Environment const &env, storm::solver::SolveGoal< ValueType, SolutionType > &&goal, bool produceSchedulers, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, storm::storage::BitVector const &targetStates, storm::storage::BitVector const &conditionStates)
std::unique_ptr< storm::solver::MinMaxLinearEquationSolver< ValueType, SolutionType > > configureMinMaxLinearEquationSolver(Environment const &env, SolveGoal< ValueType, SolutionType > &&goal, storm::solver::MinMaxLinearEquationSolverFactory< ValueType, SolutionType > const &factory, MatrixType &&matrix, OptimizationDirectionSetting optimizationDirectionSetting=OptimizationDirectionSetting::Unset)
Definition SolveGoal.h:110
bool constexpr minimize(OptimizationDirection d)
void computeSchedulerProbGreater0E(storm::storage::SparseMatrix< T > const &transitionMatrix, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, storm::storage::Scheduler< SchedulerValueType > &scheduler, boost::optional< storm::storage::BitVector > const &rowFilter)
Computes a scheduler for the ProbGreater0E-States such that in the induced system the given psiStates...
Definition graph.cpp:536
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
void computeSchedulerProb1E(storm::storage::BitVector const &prob1EStates, storm::storage::SparseMatrix< T > const &transitionMatrix, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, storm::storage::Scheduler< SchedulerValueType > &scheduler, boost::optional< storm::storage::BitVector > const &rowFilter)
Computes a scheduler for the given prob1EStates such that in the induced system the given psiStates a...
Definition graph.cpp:609
bool hasCycle(storm::storage::SparseMatrix< T > const &transitionMatrix, boost::optional< storm::storage::BitVector > const &subsystem)
Returns true if the graph represented by the given matrix has a cycle.
Definition graph.cpp:137
storm::storage::BitVector performProb1A(storm::models::sparse::NondeterministicModel< T, RM > const &model, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates)
Computes the sets of states that have probability 1 of satisfying phi until psi under all possible re...
Definition graph.cpp:1019
storm::storage::BitVector performProb0A(storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates)
Definition graph.cpp:732
storm::storage::BitVector performProb0E(storm::models::sparse::NondeterministicModel< T, RM > const &model, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates)
Computes the sets of states that have probability 0 of satisfying phi until psi under at least one po...
Definition graph.cpp:998
bool isTerminate()
Check whether the program should terminate (due to some abort signal).
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
void applyPointwise(std::vector< InValueType1 > const &firstOperand, std::vector< InValueType2 > const &secondOperand, std::vector< OutValueType > &target, Operation f=Operation())
Applies the given operation pointwise on the two given vectors and writes the result to the third vec...
Definition vector.h:375
bool isOne(ValueType const &a)
Definition constants.cpp:39
Extremum< storm::OptimizationDirection::Maximize, ValueType > Maximum
Definition Extremum.h:166
Extremum< storm::OptimizationDirection::Minimize, ValueType > Minimum
Definition Extremum.h:168
bool isAlmostZero(ValueType const &a)
Definition constants.cpp:98
bool isZero(ValueType const &a)
Definition constants.cpp:44
storm::RationalNumber findRational(storm::RationalNumber const &lowerBound, bool lowerInclusive, storm::RationalNumber const &upperBound, bool upperInclusive)
Finds the "simplest" rational number in the given interval, where "simplest" means having the smalles...
ValueType zero()
Definition constants.cpp:26
ValueType one()
Definition constants.cpp:21
TargetType convertNumber(SourceType const &number)
solver::OptimizationDirection OptimizationDirection
static const bool IsExact
std::unique_ptr< storm::storage::Scheduler< ValueType > > schedulerChoicesForReachingTargetStates
storm::storage::BitVector const existentialObservationFailureStates
std::unique_ptr< storm::storage::Scheduler< ValueType > > schedulerChoicesForReachingConditionStates
ValueType failProbability(uint64_t state) const
std::map< uint64_t, ValueType > const nonZeroTargetStateValues
storm::storage::BitVector const conditionStates
storm::storage::BitVector const universalObservationFailureStates
ResultReturnType(ValueType initialStateValue, std::unique_ptr< storm::storage::Scheduler< ValueType > > &&scheduler=nullptr)
std::unique_ptr< storm::storage::Scheduler< SolutionType > > scheduler