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