Storm
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
SparseExplorationModelChecker.cpp
Go to the documentation of this file.
2
7
9
11
14
16
18
20
24
28
30#include "storm/utility/graph.h"
32#include "storm/utility/prism.h"
33
37
38namespace storm {
39namespace modelchecker {
40
41template<typename ModelType, typename StateType>
43 : program(program.substituteConstantsFormulas()),
44 randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()),
45 comparator(storm::settings::getModule<storm::settings::modules::ExplorationSettings>().getPrecision()) {
46 // Intentionally left empty.
47}
48
49template<typename ModelType, typename StateType>
55
56template<typename ModelType, typename StateType>
58 return canHandleStatic(checkTask);
59}
60
61template<typename ModelType, typename StateType>
64 storm::logic::UntilFormula const& untilFormula = checkTask.getFormula();
65 storm::logic::Formula const& conditionFormula = untilFormula.getLeftSubformula();
66 storm::logic::Formula const& targetFormula = untilFormula.getRightSubformula();
67 STORM_LOG_THROW(program.isDeterministicModel() || checkTask.isOptimizationDirectionSet(), storm::exceptions::InvalidPropertyException,
68 "For nondeterministic systems, an optimization direction (min/max) must be given in the property.");
69
71 : storm::OptimizationDirection::Maximize);
72
73 // The first row group starts at action 0.
74 explorationInformation.newRowGroup(0);
75
76 std::map<std::string, storm::expressions::Expression> labelToExpressionMapping = program.getLabelToExpressionMapping();
77 StateGeneration<StateType, ValueType> stateGeneration(program, explorationInformation,
78 conditionFormula.toExpression(program.getManager(), labelToExpressionMapping),
79 targetFormula.toExpression(program.getManager(), labelToExpressionMapping));
80
81 // Compute and return result.
82 std::tuple<StateType, ValueType, ValueType> boundsForInitialState = performExploration(stateGeneration, explorationInformation);
83 return std::make_unique<ExplicitQuantitativeCheckResult<ValueType>>(std::get<0>(boundsForInitialState), std::get<1>(boundsForInitialState));
84}
85
86template<typename ModelType, typename StateType>
87std::tuple<StateType, typename ModelType::ValueType, typename ModelType::ValueType> SparseExplorationModelChecker<ModelType, StateType>::performExploration(
89 // Generate the initial state so we know where to start the simulation.
90 stateGeneration.computeInitialStates();
91 STORM_LOG_THROW(stateGeneration.getNumberOfInitialStates() == 1, storm::exceptions::NotSupportedException,
92 "Currently only models with one initial state are supported by the exploration engine.");
93 StateType initialStateIndex = stateGeneration.getFirstInitialState();
94
95 // Create a structure that holds the bounds for the states and actions.
97
98 // Create a stack that is used to track the path we sampled.
99 StateActionStack stack;
100
101 // Now perform the actual sampling.
103 bool convergenceCriterionMet = false;
104 while (!convergenceCriterionMet) {
105 bool result = samplePathFromInitialState(stateGeneration, explorationInformation, stack, bounds, stats);
106
107 stats.sampledPath();
108 stats.updateMaxPathLength(stack.size());
109
110 // If a terminal state was found, we update the probabilities along the path contained in the stack.
111 if (result) {
112 // Update the bounds along the path to the terminal state.
113 STORM_LOG_TRACE("Found terminal state, updating probabilities along path.");
114 updateProbabilityBoundsAlongSampledPath(stack, explorationInformation, bounds);
115 } else {
116 // If not terminal state was found, the search aborted, possibly because of an EC-detection. In this
117 // case, we cannot update the probabilities.
118 STORM_LOG_TRACE("Did not find terminal state.");
119 }
120
121 STORM_LOG_DEBUG("Discovered states: " << explorationInformation.getNumberOfDiscoveredStates() << " (" << stats.numberOfExploredStates << " explored, "
122 << explorationInformation.getNumberOfUnexploredStates() << " unexplored).");
123 STORM_LOG_DEBUG("Value of initial state is in [" << bounds.getLowerBoundForState(initialStateIndex, explorationInformation) << ", "
124 << bounds.getUpperBoundForState(initialStateIndex, explorationInformation) << "].");
125 ValueType difference = bounds.getDifferenceOfStateBounds(initialStateIndex, explorationInformation);
126 STORM_LOG_DEBUG("Difference after iteration " << stats.pathsSampled << " is " << difference << ".");
127 convergenceCriterionMet = comparator.isZero(difference);
128
129 // If the number of sampled paths exceeds a certain threshold, do a precomputation.
130 if (!convergenceCriterionMet && explorationInformation.performPrecomputationExcessiveSampledPaths(stats.pathsSampledSinceLastPrecomputation)) {
131 performPrecomputation(stack, explorationInformation, bounds, stats);
132 }
133 }
134
135 // Show statistics if required.
137 stats.printToStream(std::cout, explorationInformation);
138 }
139
140 return std::make_tuple(initialStateIndex, bounds.getLowerBoundForState(initialStateIndex, explorationInformation),
141 bounds.getUpperBoundForState(initialStateIndex, explorationInformation));
142}
143
144template<typename ModelType, typename StateType>
145bool SparseExplorationModelChecker<ModelType, StateType>::samplePathFromInitialState(StateGeneration<StateType, ValueType>& stateGeneration,
146 ExplorationInformation<StateType, ValueType>& explorationInformation,
147 StateActionStack& stack, Bounds<StateType, ValueType>& bounds,
148 Statistics<StateType, ValueType>& stats) const {
149 // Start the search from the initial state.
150 stack.push_back(std::make_pair(stateGeneration.getFirstInitialState(), 0));
151
152 // As long as we didn't find a terminal (accepting or rejecting) state in the search, sample a new successor.
153 bool foundTerminalState = false;
154 while (!foundTerminalState) {
155 StateType const& currentStateId = stack.back().first;
156 STORM_LOG_TRACE("State on top of stack is: " << currentStateId << ".");
157
158 // If the state is not yet explored, we need to retrieve its behaviors.
159 auto unexploredIt = explorationInformation.findUnexploredState(currentStateId);
160 if (unexploredIt != explorationInformation.unexploredStatesEnd()) {
161 STORM_LOG_TRACE("State was not yet explored.");
162
163 // Explore the previously unexplored state.
164 storm::generator::CompressedState const& compressedState = unexploredIt->second;
165 foundTerminalState = exploreState(stateGeneration, currentStateId, compressedState, explorationInformation, bounds, stats);
166 if (foundTerminalState) {
167 STORM_LOG_TRACE("Aborting sampling of path, because a terminal state was reached.");
168 }
169 explorationInformation.removeUnexploredState(unexploredIt);
170 } else {
171 // If the state was already explored, we check whether it is a terminal state or not.
172 if (explorationInformation.isTerminal(currentStateId)) {
173 STORM_LOG_TRACE("Found already explored terminal state: " << currentStateId << ".");
174 foundTerminalState = true;
175 }
176 }
177
178 // Notify the stats about the performed exploration step.
179 stats.explorationStep();
180
181 // If the state was not a terminal state, we continue the path search and sample the next state.
182 if (!foundTerminalState) {
183 // At this point, we can be sure that the state was expanded and that we can sample according to the
184 // probabilities in the matrix.
185 uint32_t chosenAction = sampleActionOfState(currentStateId, explorationInformation, bounds);
186 stack.back().second = chosenAction;
187 STORM_LOG_TRACE("Sampled action " << chosenAction << " in state " << currentStateId << ".");
188
189 StateType successor = sampleSuccessorFromAction(chosenAction, explorationInformation, bounds);
190 STORM_LOG_TRACE("Sampled successor " << successor << " according to action " << chosenAction << " of state " << currentStateId << ".");
191
192 // Put the successor state and a dummy action on top of the stack.
193 stack.emplace_back(successor, 0);
194
195 // If the number of exploration steps exceeds a certain threshold, do a precomputation.
196 if (explorationInformation.performPrecomputationExcessiveExplorationSteps(stats.explorationStepsSinceLastPrecomputation)) {
197 performPrecomputation(stack, explorationInformation, bounds, stats);
198
199 STORM_LOG_TRACE("Aborting the search after precomputation.");
200 stack.clear();
201 break;
202 }
203 }
204 }
205
206 return foundTerminalState;
207}
208
209template<typename ModelType, typename StateType>
210bool SparseExplorationModelChecker<ModelType, StateType>::exploreState(StateGeneration<StateType, ValueType>& stateGeneration, StateType const& currentStateId,
211 storm::generator::CompressedState const& currentState,
212 ExplorationInformation<StateType, ValueType>& explorationInformation,
213 Bounds<StateType, ValueType>& bounds, Statistics<StateType, ValueType>& stats) const {
214 bool isTerminalState = false;
215 bool isTargetState = false;
216
217 ++stats.numberOfExploredStates;
218
219 // Finally, map the unexplored state to the row group.
220 explorationInformation.assignStateToNextRowGroup(currentStateId);
221 STORM_LOG_TRACE("Assigning row group " << explorationInformation.getRowGroup(currentStateId) << " to state " << currentStateId << ".");
222
223 // Initialize the bounds, because some of the following computations depend on the values to be available for
224 // all states that have been assigned to a row-group.
225 bounds.initializeBoundsForNextState();
226
227 // Before generating the behavior of the state, we need to determine whether it's a target state that
228 // does not need to be expanded.
229 stateGeneration.load(currentState);
230 if (stateGeneration.isTargetState()) {
231 ++stats.numberOfTargetStates;
232 isTargetState = true;
233 isTerminalState = true;
234 } else if (stateGeneration.isConditionState()) {
235 STORM_LOG_TRACE("Exploring state.");
236
237 // If it needs to be expanded, we use the generator to retrieve the behavior of the new state.
238 storm::generator::StateBehavior<ValueType, StateType> behavior = stateGeneration.expand();
239 STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices.");
240
241 // Clumsily check whether we have found a state that forms a trivial BMEC.
242 bool otherSuccessor = false;
243 for (auto const& choice : behavior) {
244 for (auto const& entry : choice) {
245 if (entry.first != currentStateId) {
246 otherSuccessor = true;
247 break;
248 }
249 }
250 }
251 isTerminalState = !otherSuccessor;
252
253 // If the state was neither a trivial (non-accepting) terminal state nor a target state, we
254 // need to store its behavior.
255 if (!isTerminalState) {
256 // Next, we insert the behavior into our matrix structure.
257 StateType startAction = explorationInformation.getActionCount();
258 explorationInformation.addActionsToMatrix(behavior.getNumberOfChoices());
259
260 ActionType localAction = 0;
261
262 // Retrieve the lowest state bounds (wrt. to the current optimization direction).
263 std::pair<ValueType, ValueType> stateBounds = getLowestBounds(explorationInformation.getOptimizationDirection());
264
265 for (auto const& choice : behavior) {
266 for (auto const& entry : choice) {
267 explorationInformation.getRowOfMatrix(startAction + localAction).emplace_back(entry.first, entry.second);
268 STORM_LOG_TRACE("Found transition " << currentStateId << "-[" << (startAction + localAction) << ", " << entry.second << "]-> "
269 << entry.first << ".");
270 }
271
272 std::pair<ValueType, ValueType> actionBounds = computeBoundsOfAction(startAction + localAction, explorationInformation, bounds);
273 bounds.initializeBoundsForNextAction(actionBounds);
274 stateBounds = combineBounds(explorationInformation.getOptimizationDirection(), stateBounds, actionBounds);
275
276 STORM_LOG_TRACE("Initializing bounds of action " << (startAction + localAction) << " to "
277 << bounds.getLowerBoundForAction(startAction + localAction) << " and "
278 << bounds.getUpperBoundForAction(startAction + localAction) << ".");
279
280 ++localAction;
281 }
282
283 // Terminate the row group.
284 explorationInformation.terminateCurrentRowGroup();
285
286 bounds.setBoundsForState(currentStateId, explorationInformation, stateBounds);
287 STORM_LOG_TRACE("Initializing bounds of state " << currentStateId << " to " << bounds.getLowerBoundForState(currentStateId, explorationInformation)
288 << " and " << bounds.getUpperBoundForState(currentStateId, explorationInformation) << ".");
289 }
290 } else {
291 // In this case, the state is neither a target state nor a condition state and therefore a rejecting
292 // terminal state.
293 isTerminalState = true;
294 }
295
296 if (isTerminalState) {
297 STORM_LOG_TRACE("State does not need to be explored, because it is " << (isTargetState ? "a target state" : "a rejecting terminal state") << ".");
298 explorationInformation.addTerminalState(currentStateId);
299
300 if (isTargetState) {
301 bounds.setBoundsForState(currentStateId, explorationInformation,
302 std::make_pair(storm::utility::one<ValueType>(), storm::utility::one<ValueType>()));
303 bounds.initializeBoundsForNextAction(std::make_pair(storm::utility::one<ValueType>(), storm::utility::one<ValueType>()));
304 } else {
305 bounds.setBoundsForState(currentStateId, explorationInformation,
306 std::make_pair(storm::utility::zero<ValueType>(), storm::utility::zero<ValueType>()));
307 bounds.initializeBoundsForNextAction(std::make_pair(storm::utility::zero<ValueType>(), storm::utility::zero<ValueType>()));
308 }
309
310 // Increase the size of the matrix, but leave the row empty.
311 explorationInformation.addActionsToMatrix(1);
312
313 // Terminate the row group.
314 explorationInformation.newRowGroup();
315 }
316
317 return isTerminalState;
318}
319
320template<typename ModelType, typename StateType>
321typename SparseExplorationModelChecker<ModelType, StateType>::ActionType SparseExplorationModelChecker<ModelType, StateType>::sampleActionOfState(
322 StateType const& currentStateId, ExplorationInformation<StateType, ValueType> const& explorationInformation, Bounds<StateType, ValueType>& bounds) const {
323 // Determine the values of all available actions.
324 std::vector<std::pair<ActionType, ValueType>> actionValues;
325 StateType rowGroup = explorationInformation.getRowGroup(currentStateId);
326
327 // Check for cases in which we do not need to perform more work.
328 if (explorationInformation.onlyOneActionAvailable(rowGroup)) {
329 return explorationInformation.getStartRowOfGroup(rowGroup);
330 }
331
332 // If there are more choices to consider, start by gathering the values of relevant actions.
333 STORM_LOG_TRACE("Sampling from actions leaving the state.");
334
335 for (uint32_t row = explorationInformation.getStartRowOfGroup(rowGroup); row < explorationInformation.getStartRowOfGroup(rowGroup + 1); ++row) {
336 actionValues.push_back(std::make_pair(row, bounds.getBoundForAction(explorationInformation.getOptimizationDirection(), row)));
337 }
338
339 STORM_LOG_ASSERT(!actionValues.empty(), "Values for actions must not be empty.");
340
341 // Sort the actions wrt. to the optimization direction.
342 if (explorationInformation.maximize()) {
343 std::sort(actionValues.begin(), actionValues.end(),
344 [](std::pair<ActionType, ValueType> const& a, std::pair<ActionType, ValueType> const& b) { return a.second > b.second; });
345 } else {
346 std::sort(actionValues.begin(), actionValues.end(),
347 [](std::pair<ActionType, ValueType> const& a, std::pair<ActionType, ValueType> const& b) { return a.second < b.second; });
348 }
349
350 // Determine the first elements of the sorted range that agree on their value.
351 auto end = ++actionValues.begin();
352 while (end != actionValues.end() && comparator.isEqual(actionValues.begin()->second, end->second)) {
353 ++end;
354 }
355
356 // Now sample from all maximizing actions.
357 std::uniform_int_distribution<ActionType> distribution(0, std::distance(actionValues.begin(), end) - 1);
358 return actionValues[distribution(randomGenerator)].first;
359}
360
361template<typename ModelType, typename StateType>
362StateType SparseExplorationModelChecker<ModelType, StateType>::sampleSuccessorFromAction(
363 ActionType const& chosenAction, ExplorationInformation<StateType, ValueType> const& explorationInformation,
364 Bounds<StateType, ValueType> const& bounds) const {
365 std::vector<storm::storage::MatrixEntry<StateType, ValueType>> const& row = explorationInformation.getRowOfMatrix(chosenAction);
366 if (row.size() == 1) {
367 return row.front().getColumn();
368 }
369
370 // Depending on the selected next-state heuristic, we give the states other likelihoods of getting chosen.
371 if (explorationInformation.useDifferenceProbabilitySumHeuristic() || explorationInformation.useProbabilityHeuristic()) {
372 std::vector<ValueType> probabilities(row.size());
373 if (explorationInformation.useDifferenceProbabilitySumHeuristic()) {
374 std::transform(row.begin(), row.end(), probabilities.begin(),
375 [&bounds, &explorationInformation](storm::storage::MatrixEntry<StateType, ValueType> const& entry) {
376 return entry.getValue() + bounds.getDifferenceOfStateBounds(entry.getColumn(), explorationInformation);
377 });
378 } else if (explorationInformation.useProbabilityHeuristic()) {
379 std::transform(row.begin(), row.end(), probabilities.begin(),
380 [](storm::storage::MatrixEntry<StateType, ValueType> const& entry) { return entry.getValue(); });
381 }
382
383 // Now sample according to the probabilities.
384 std::discrete_distribution<StateType> distribution(probabilities.begin(), probabilities.end());
385 return row[distribution(randomGenerator)].getColumn();
386 } else {
387 STORM_LOG_ASSERT(explorationInformation.useUniformHeuristic(), "Illegal next-state heuristic.");
388 std::uniform_int_distribution<ActionType> distribution(0, row.size() - 1);
389 return row[distribution(randomGenerator)].getColumn();
390 }
391}
392
393template<typename ModelType, typename StateType>
394bool SparseExplorationModelChecker<ModelType, StateType>::performPrecomputation(StateActionStack const& stack,
395 ExplorationInformation<StateType, ValueType>& explorationInformation,
396 Bounds<StateType, ValueType>& bounds,
397 Statistics<StateType, ValueType>& stats) const {
398 ++stats.numberOfPrecomputations;
399
400 // Outline:
401 // 1. construct a sparse transition matrix of the relevant part of the state space.
402 // 2. use this matrix to compute states with probability 0/1 and an MEC decomposition (in the max case).
403 // 3. use MEC decomposition to collapse MECs.
404 STORM_LOG_TRACE("Starting " << (explorationInformation.useLocalPrecomputation() ? "local" : "global") << " precomputation.");
405
406 // Construct the matrix that represents the fragment of the system contained in the currently sampled path.
407 storm::storage::SparseMatrixBuilder<ValueType> builder(0, 0, 0, false, true, 0);
408
409 // Determine the set of states that was expanded.
410 std::vector<StateType> relevantStates;
411 if (explorationInformation.useLocalPrecomputation()) {
412 for (auto const& stateActionPair : stack) {
413 if (explorationInformation.maximize() || !storm::utility::isOne(bounds.getLowerBoundForState(stateActionPair.first, explorationInformation))) {
414 relevantStates.push_back(stateActionPair.first);
415 }
416 }
417 std::sort(relevantStates.begin(), relevantStates.end());
418 auto newEnd = std::unique(relevantStates.begin(), relevantStates.end());
419 relevantStates.resize(std::distance(relevantStates.begin(), newEnd));
420 } else {
421 for (StateType state = 0; state < explorationInformation.getNumberOfDiscoveredStates(); ++state) {
422 // Add the state to the relevant states if they are not unexplored.
423 if (!explorationInformation.isUnexplored(state)) {
424 relevantStates.push_back(state);
425 }
426 }
427 }
428 StateType sink = relevantStates.size();
429
430 // Create a mapping for faster look-up during the translation of flexible matrix to the real sparse matrix.
431 // While doing so, record all target states.
432 std::unordered_map<StateType, StateType> relevantStateToNewRowGroupMapping;
433 storm::storage::BitVector targetStates(sink + 1);
434 for (StateType index = 0; index < relevantStates.size(); ++index) {
435 relevantStateToNewRowGroupMapping.emplace(relevantStates[index], index);
436 if (storm::utility::isOne(bounds.getLowerBoundForState(relevantStates[index], explorationInformation))) {
437 targetStates.set(index);
438 }
439 }
440
441 // Do the actual translation.
442 StateType currentRow = 0;
443 for (auto const& state : relevantStates) {
444 builder.newRowGroup(currentRow);
445 StateType rowGroup = explorationInformation.getRowGroup(state);
446 for (auto row = explorationInformation.getStartRowOfGroup(rowGroup); row < explorationInformation.getStartRowOfGroup(rowGroup + 1); ++row) {
447 ValueType unexpandedProbability = storm::utility::zero<ValueType>();
448 for (auto const& entry : explorationInformation.getRowOfMatrix(row)) {
449 auto it = relevantStateToNewRowGroupMapping.find(entry.getColumn());
450 if (it != relevantStateToNewRowGroupMapping.end()) {
451 // If the entry is a relevant state, we copy it over (and compensate for the offset change).
452 builder.addNextValue(currentRow, it->second, entry.getValue());
453 } else {
454 // If the entry is an unexpanded state, we gather the probability to later redirect it to an unexpanded sink.
455 unexpandedProbability += entry.getValue();
456 }
457 }
458 if (unexpandedProbability != storm::utility::zero<ValueType>()) {
459 builder.addNextValue(currentRow, sink, unexpandedProbability);
460 }
461 ++currentRow;
462 }
463 }
464 // Then, make the unexpanded state absorbing.
465 builder.newRowGroup(currentRow);
466 builder.addNextValue(currentRow, sink, storm::utility::one<ValueType>());
467 storm::storage::SparseMatrix<ValueType> relevantStatesMatrix = builder.build();
468 storm::storage::SparseMatrix<ValueType> transposedMatrix = relevantStatesMatrix.transpose(true);
469 STORM_LOG_TRACE("Successfully built matrix for precomputation.");
470
471 storm::storage::BitVector allStates(sink + 1, true);
472 storm::storage::BitVector statesWithProbability0;
473 storm::storage::BitVector statesWithProbability1;
474 if (explorationInformation.maximize()) {
475 // If we are computing maximal probabilities, we first perform a detection of states that have
476 // probability 01 and then additionally perform an MEC decomposition. The reason for this somewhat
477 // duplicate work is the following. Optimally, we would only do the MEC decomposition, because we need
478 // it anyway. However, when only detecting (accepting) MECs, we do not infer which of the other states
479 // (not contained in MECs) also have probability 0/1.
480 targetStates.set(sink, true);
481 statesWithProbability0 = storm::utility::graph::performProb0A(transposedMatrix, allStates, targetStates);
482 targetStates.set(sink, false);
483 statesWithProbability1 =
484 storm::utility::graph::performProb1E(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates);
485
486 storm::storage::MaximalEndComponentDecomposition<ValueType> mecDecomposition(relevantStatesMatrix, relevantStatesMatrix.transpose(true));
487 ++stats.ecDetections;
488 STORM_LOG_TRACE("Successfully computed MEC decomposition. Found " << (mecDecomposition.size() > 1 ? (mecDecomposition.size() - 1) : 0) << " MEC(s).");
489
490 // If the decomposition contains only the MEC consisting of the sink state, we count it as 'failed'.
491 STORM_LOG_ASSERT(mecDecomposition.size() > 0, "Expected at least one MEC (the trivial sink MEC).");
492 if (mecDecomposition.size() == 1) {
493 ++stats.failedEcDetections;
494 } else {
495 stats.totalNumberOfEcDetected += mecDecomposition.size() - 1;
496
497 // 3. Analyze the MEC decomposition.
498 for (auto const& mec : mecDecomposition) {
499 // Ignore the (expected) MEC of the sink state.
500 if (mec.containsState(sink)) {
501 continue;
502 }
503
504 collapseMec(mec, relevantStates, relevantStatesMatrix, explorationInformation, bounds);
505 }
506 }
507 } else {
508 // If we are computing minimal probabilities, we do not need to perform an EC-detection. We rather
509 // compute all states (of the considered fragment) that have probability 0/1. For states with
510 // probability 0, we have to mark the sink as being a target. For states with probability 1, however,
511 // we must treat the sink as being rejecting.
512 targetStates.set(sink, true);
513 statesWithProbability0 =
514 storm::utility::graph::performProb0E(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates);
515 targetStates.set(sink, false);
516 statesWithProbability1 =
517 storm::utility::graph::performProb1A(relevantStatesMatrix, relevantStatesMatrix.getRowGroupIndices(), transposedMatrix, allStates, targetStates);
518 }
519
520 // Set the bounds of the identified states.
521 STORM_LOG_ASSERT((statesWithProbability0 & statesWithProbability1).empty(), "States with probability 0 and 1 overlap.");
522 for (auto state : statesWithProbability0) {
523 // Skip the sink state as it is not contained in the original system.
524 if (state == sink) {
525 continue;
526 }
527
528 StateType originalState = relevantStates[state];
529 bounds.setUpperBoundForState(originalState, explorationInformation, storm::utility::zero<ValueType>());
530 explorationInformation.addTerminalState(originalState);
531 }
532 for (auto state : statesWithProbability1) {
533 // Skip the sink state as it is not contained in the original system.
534 if (state == sink) {
535 continue;
536 }
537
538 StateType originalState = relevantStates[state];
539 bounds.setLowerBoundForState(originalState, explorationInformation, storm::utility::one<ValueType>());
540 explorationInformation.addTerminalState(originalState);
541 }
542 return true;
543}
544
545template<typename ModelType, typename StateType>
546void SparseExplorationModelChecker<ModelType, StateType>::collapseMec(storm::storage::MaximalEndComponent const& mec,
547 std::vector<StateType> const& relevantStates,
548 storm::storage::SparseMatrix<ValueType> const& relevantStatesMatrix,
549 ExplorationInformation<StateType, ValueType>& explorationInformation,
550 Bounds<StateType, ValueType>& bounds) const {
551 bool containsTargetState = false;
552
553 // Now we record all actions leaving the EC.
554 std::vector<ActionType> leavingActions;
555 for (auto const& stateAndChoices : mec) {
556 // Compute the state of the original model that corresponds to the current state.
557 StateType originalState = relevantStates[stateAndChoices.first];
558 StateType originalRowGroup = explorationInformation.getRowGroup(originalState);
559
560 // Check whether a target state is contained in the MEC.
561 if (!containsTargetState && storm::utility::isOne(bounds.getLowerBoundForRowGroup(originalRowGroup))) {
562 containsTargetState = true;
563 }
564
565 // For each state, compute the actions that leave the MEC.
566 auto includedChoicesIt = stateAndChoices.second.begin();
567 auto includedChoicesIte = stateAndChoices.second.end();
568 for (auto action = explorationInformation.getStartRowOfGroup(originalRowGroup);
569 action < explorationInformation.getStartRowOfGroup(originalRowGroup + 1); ++action) {
570 if (includedChoicesIt != includedChoicesIte) {
571 STORM_LOG_TRACE("Next (local) choice contained in MEC is "
572 << (*includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first]));
573 STORM_LOG_TRACE("Current (local) choice iterated is " << (action - explorationInformation.getStartRowOfGroup(originalRowGroup)));
574 if (action - explorationInformation.getStartRowOfGroup(originalRowGroup) !=
575 *includedChoicesIt - relevantStatesMatrix.getRowGroupIndices()[stateAndChoices.first]) {
576 STORM_LOG_TRACE("Choice leaves the EC.");
577 leavingActions.push_back(action);
578 } else {
579 STORM_LOG_TRACE("Choice stays in the EC.");
580 ++includedChoicesIt;
581 }
582 } else {
583 STORM_LOG_TRACE("Choice leaves the EC, because there is no more choice staying in the EC.");
584 leavingActions.push_back(action);
585 }
586 }
587 }
588
589 // Now, we need to collapse the EC only if it does not contain a target state and the leaving actions are
590 // non-empty, because only then have the states a (potentially) non-zero, non-one probability.
591 if (!containsTargetState && !leavingActions.empty()) {
592 // In this case, no target state is contained in the MEC, but there are actions leaving the MEC. To
593 // prevent the simulation getting stuck in this MEC again, we replace all states in the MEC by a new
594 // state whose outgoing actions are the ones leaving the MEC. We do this, by assigning all states in the
595 // MEC to a new row group, which will then hold all the outgoing choices.
596
597 // Remap all contained states to the new row group.
598 StateType nextRowGroup = explorationInformation.getNextRowGroup();
599 for (auto const& stateAndChoices : mec) {
600 StateType originalState = relevantStates[stateAndChoices.first];
601 explorationInformation.assignStateToRowGroup(originalState, nextRowGroup);
602 }
603
604 bounds.initializeBoundsForNextState();
605
606 // Add to the new row group all leaving actions of contained states and set the appropriate bounds for
607 // the actions and the new state.
608 std::pair<ValueType, ValueType> stateBounds = getLowestBounds(explorationInformation.getOptimizationDirection());
609 for (auto const& action : leavingActions) {
610 explorationInformation.moveActionToBackOfMatrix(action);
611 std::pair<ValueType, ValueType> actionBounds = bounds.getBoundsForAction(action);
612 bounds.initializeBoundsForNextAction(actionBounds);
613 stateBounds = combineBounds(explorationInformation.getOptimizationDirection(), stateBounds, actionBounds);
614 }
615 bounds.setBoundsForRowGroup(nextRowGroup, stateBounds);
616
617 // Terminate the row group of the newly introduced state.
618 explorationInformation.terminateCurrentRowGroup();
619 }
620}
621
622template<typename ModelType, typename StateType>
623typename ModelType::ValueType SparseExplorationModelChecker<ModelType, StateType>::computeLowerBoundOfAction(
624 ActionType const& action, ExplorationInformation<StateType, ValueType> const& explorationInformation, Bounds<StateType, ValueType> const& bounds) const {
625 ValueType result = storm::utility::zero<ValueType>();
626 for (auto const& element : explorationInformation.getRowOfMatrix(action)) {
627 result += element.getValue() * bounds.getLowerBoundForState(element.getColumn(), explorationInformation);
628 }
629 return result;
630}
631
632template<typename ModelType, typename StateType>
633typename ModelType::ValueType SparseExplorationModelChecker<ModelType, StateType>::computeUpperBoundOfAction(
634 ActionType const& action, ExplorationInformation<StateType, ValueType> const& explorationInformation, Bounds<StateType, ValueType> const& bounds) const {
635 ValueType result = storm::utility::zero<ValueType>();
636 for (auto const& element : explorationInformation.getRowOfMatrix(action)) {
637 result += element.getValue() * bounds.getUpperBoundForState(element.getColumn(), explorationInformation);
638 }
639 return result;
640}
641
642template<typename ModelType, typename StateType>
643std::pair<typename ModelType::ValueType, typename ModelType::ValueType> SparseExplorationModelChecker<ModelType, StateType>::computeBoundsOfAction(
644 ActionType const& action, ExplorationInformation<StateType, ValueType> const& explorationInformation, Bounds<StateType, ValueType> const& bounds) const {
645 // TODO: take into account self-loops?
646 std::pair<ValueType, ValueType> result = std::make_pair(storm::utility::zero<ValueType>(), storm::utility::zero<ValueType>());
647 for (auto const& element : explorationInformation.getRowOfMatrix(action)) {
648 result.first += element.getValue() * bounds.getLowerBoundForState(element.getColumn(), explorationInformation);
649 result.second += element.getValue() * bounds.getUpperBoundForState(element.getColumn(), explorationInformation);
650 }
651 return result;
652}
653
654template<typename ModelType, typename StateType>
655std::pair<typename ModelType::ValueType, typename ModelType::ValueType> SparseExplorationModelChecker<ModelType, StateType>::computeBoundsOfState(
656 StateType const& currentStateId, ExplorationInformation<StateType, ValueType> const& explorationInformation,
657 Bounds<StateType, ValueType> const& bounds) const {
658 StateType group = explorationInformation.getRowGroup(currentStateId);
659 std::pair<ValueType, ValueType> result = getLowestBounds(explorationInformation.getOptimizationDirection());
660 for (ActionType action = explorationInformation.getStartRowOfGroup(group); action < explorationInformation.getStartRowOfGroup(group + 1); ++action) {
661 std::pair<ValueType, ValueType> actionValues = computeBoundsOfAction(action, explorationInformation, bounds);
662 result = combineBounds(explorationInformation.getOptimizationDirection(), result, actionValues);
663 }
664 return result;
665}
666
667template<typename ModelType, typename StateType>
668void SparseExplorationModelChecker<ModelType, StateType>::updateProbabilityBoundsAlongSampledPath(
669 StateActionStack& stack, ExplorationInformation<StateType, ValueType> const& explorationInformation, Bounds<StateType, ValueType>& bounds) const {
670 stack.pop_back();
671 while (!stack.empty()) {
672 updateProbabilityOfAction(stack.back().first, stack.back().second, explorationInformation, bounds);
673 stack.pop_back();
674 }
675}
676
677template<typename ModelType, typename StateType>
678void SparseExplorationModelChecker<ModelType, StateType>::updateProbabilityOfAction(StateType const& state, ActionType const& action,
679 ExplorationInformation<StateType, ValueType> const& explorationInformation,
680 Bounds<StateType, ValueType>& bounds) const {
681 // Compute the new lower/upper values of the action.
682 std::pair<ValueType, ValueType> newBoundsForAction = computeBoundsOfAction(action, explorationInformation, bounds);
683
684 // And set them as the current value.
685 bounds.setBoundsForAction(action, newBoundsForAction);
686
687 // Check if we need to update the values for the states.
688 if (explorationInformation.maximize()) {
689 bounds.setLowerBoundOfStateIfGreaterThanOld(state, explorationInformation, newBoundsForAction.first);
690
691 StateType rowGroup = explorationInformation.getRowGroup(state);
692 if (newBoundsForAction.second < bounds.getUpperBoundForRowGroup(rowGroup)) {
693 if (explorationInformation.getRowGroupSize(rowGroup) > 1) {
694 newBoundsForAction.second = std::max(newBoundsForAction.second, computeBoundOverAllOtherActions(storm::OptimizationDirection::Maximize, state,
695 action, explorationInformation, bounds));
696 }
697
698 bounds.setUpperBoundForRowGroup(rowGroup, newBoundsForAction.second);
699 }
700 } else {
701 bounds.setUpperBoundOfStateIfLessThanOld(state, explorationInformation, newBoundsForAction.second);
702
703 StateType rowGroup = explorationInformation.getRowGroup(state);
704 if (bounds.getLowerBoundForRowGroup(rowGroup) < newBoundsForAction.first) {
705 if (explorationInformation.getRowGroupSize(rowGroup) > 1) {
706 ValueType min = computeBoundOverAllOtherActions(storm::OptimizationDirection::Minimize, state, action, explorationInformation, bounds);
707 newBoundsForAction.first = std::min(newBoundsForAction.first, min);
708 }
709
710 bounds.setLowerBoundForRowGroup(rowGroup, newBoundsForAction.first);
711 }
712 }
713}
714
715template<typename ModelType, typename StateType>
716typename ModelType::ValueType SparseExplorationModelChecker<ModelType, StateType>::computeBoundOverAllOtherActions(
717 storm::OptimizationDirection const& direction, StateType const& state, ActionType const& action,
718 ExplorationInformation<StateType, ValueType> const& explorationInformation, Bounds<StateType, ValueType> const& bounds) const {
719 ValueType bound = getLowestBound(explorationInformation.getOptimizationDirection());
720
721 ActionType group = explorationInformation.getRowGroup(state);
722 for (auto currentAction = explorationInformation.getStartRowOfGroup(group); currentAction < explorationInformation.getStartRowOfGroup(group + 1);
723 ++currentAction) {
724 if (currentAction == action) {
725 continue;
726 }
727
728 if (direction == storm::OptimizationDirection::Maximize) {
729 bound = std::max(bound, computeUpperBoundOfAction(currentAction, explorationInformation, bounds));
730 } else {
731 bound = std::min(bound, computeLowerBoundOfAction(currentAction, explorationInformation, bounds));
732 }
733 }
734 return bound;
735}
736
737template<typename ModelType, typename StateType>
738std::pair<typename ModelType::ValueType, typename ModelType::ValueType> SparseExplorationModelChecker<ModelType, StateType>::getLowestBounds(
739 storm::OptimizationDirection const& direction) const {
740 ValueType val = getLowestBound(direction);
741 return std::make_pair(val, val);
742}
743
744template<typename ModelType, typename StateType>
745typename ModelType::ValueType SparseExplorationModelChecker<ModelType, StateType>::getLowestBound(storm::OptimizationDirection const& direction) const {
746 if (direction == storm::OptimizationDirection::Maximize) {
747 return storm::utility::zero<ValueType>();
748 } else {
749 return storm::utility::one<ValueType>();
750 }
751}
752
753template<typename ModelType, typename StateType>
754std::pair<typename ModelType::ValueType, typename ModelType::ValueType> SparseExplorationModelChecker<ModelType, StateType>::combineBounds(
755 storm::OptimizationDirection const& direction, std::pair<ValueType, ValueType> const& bounds1, std::pair<ValueType, ValueType> const& bounds2) const {
756 if (direction == storm::OptimizationDirection::Maximize) {
757 return std::make_pair(std::max(bounds1.first, bounds2.first), std::max(bounds1.second, bounds2.second));
758 } else {
759 return std::make_pair(std::min(bounds1.first, bounds2.first), std::min(bounds1.second, bounds2.second));
760 }
761}
762
763template class SparseExplorationModelChecker<storm::models::sparse::Dtmc<double>, uint32_t>;
764template class SparseExplorationModelChecker<storm::models::sparse::Mdp<double>, uint32_t>;
765} // namespace modelchecker
766} // namespace storm
std::size_t getNumberOfChoices() const
Retrieves the number of choices in the behavior.
Formula const & getRightSubformula() const
Formula const & getLeftSubformula() const
storm::expressions::Expression toExpression(storm::expressions::ExpressionManager const &manager, std::map< std::string, storm::expressions::Expression > const &labelToExpressionMapping={}) const
Takes the formula and converts it to an equivalent expression.
Definition Formula.cpp:538
bool isInFragment(FragmentSpecification const &fragment) const
Definition Formula.cpp:196
bool isOptimizationDirectionSet() const
Retrieves whether an optimization direction was set.
Definition CheckTask.h:147
FormulaType const & getFormula() const
Retrieves the formula from this task.
Definition CheckTask.h:140
storm::OptimizationDirection const & getOptimizationDirection() const
Retrieves the optimization direction (if set).
Definition CheckTask.h:154
bool isOnlyInitialStatesRelevantSet() const
Retrieves whether only the initial states are relevant in the computation.
Definition CheckTask.h:204
virtual std::unique_ptr< CheckResult > computeUntilProbabilities(Environment const &env, CheckTask< storm::logic::UntilFormula, ValueType > const &checkTask) override
static bool canHandleStatic(CheckTask< storm::logic::Formula, ValueType > const &checkTask)
SparseExplorationModelChecker(storm::prism::Program const &program)
virtual bool canHandle(CheckTask< storm::logic::Formula, ValueType > const &checkTask) const override
ValueType getDifferenceOfStateBounds(StateType const &state, ExplorationInformation< StateType, ValueType > const &explorationInformation) const
Definition Bounds.cpp:77
ValueType getLowerBoundForState(StateType const &state, ExplorationInformation< StateType, ValueType > const &explorationInformation) const
Definition Bounds.cpp:21
ValueType getUpperBoundForState(StateType const &state, ExplorationInformation< StateType, ValueType > const &explorationInformation) const
Definition Bounds.cpp:37
bool performPrecomputationExcessiveSampledPaths(std::size_t &numberOfSampledPathsSinceLastPrecomputation) const
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:18
void set(uint_fast64_t index, bool value=true)
Sets the given truth value at the given index.
This class represents the decomposition of a nondeterministic model into its maximal end components.
This class represents a maximal end-component of a nondeterministic model.
A class that can be used to build a sparse matrix by adding value by value.
A class that holds a possibly non-square matrix in the compressed row storage format.
std::vector< index_type > const & getRowGroupIndices() const
Returns the grouping of rows of this matrix.
storm::storage::SparseMatrix< value_type > transpose(bool joinGroups=false, bool keepZeros=false) const
Transposes the matrix.
#define STORM_LOG_DEBUG(message)
Definition logging.h:23
#define STORM_LOG_TRACE(message)
Definition logging.h:17
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
SFTBDDChecker::ValueType ValueType
FragmentSpecification reachability()
SettingsType const & getModule()
Get module.
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:997
storm::storage::BitVector performProb0A(storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates)
Definition graph.cpp:749
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:976
storm::storage::BitVector performProb1E(storm::storage::SparseMatrix< T > const &transitionMatrix, std::vector< uint_fast64_t > const &nondeterministicChoiceIndices, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, boost::optional< storm::storage::BitVector > const &choiceConstraint)
Computes the sets of states that have probability 1 of satisfying phi until psi under at least one po...
Definition graph.cpp:757
bool isOne(ValueType const &a)
Definition constants.cpp:36
ValueType min(ValueType const &first, ValueType const &second)
LabParser.cpp.
Definition cli.cpp:18
void printToStream(std::ostream &out, ExplorationInformation< StateType, ValueType > const &explorationInformation) const
void updateMaxPathLength(std::size_t const &currentPathLength)