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