Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
BeliefExplorationPomdpModelChecker.cpp
Go to the documentation of this file.
2
3#include <tuple>
4
8
12
16
20#include "storm/utility/graph.h"
22
23namespace storm {
24namespace pomdp {
25namespace modelchecker {
26
27/* Struct Functions */
28
29template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
34
35template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
41 "Upper bound '" << upperBound << "' is smaller than lower bound '" << lowerBound << "': Difference is " << diff << ".");
43 }
44 if (relative && !storm::utility::isZero(upperBound)) {
46 }
47 return diff;
48}
49
50template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
52 if (value > lowerBound) {
53 lowerBound = value;
54 return true;
55 }
56 return false;
57}
58
59template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
61 if (value < upperBound) {
62 upperBound = value;
63 return true;
64 }
65 return false;
66}
67
68template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
69BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::Statistics::Statistics()
70 : beliefMdpDetectedToBeFinite(false),
71 refinementFixpointDetected(false),
72 overApproximationBuildAborted(false),
73 underApproximationBuildAborted(false),
74 aborted(false) {
75 // intentionally left empty;
76}
77
78template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
80 Options options)
81 : options(options),
82 inputPomdp(pomdp),
83 beliefTypeCC(storm::utility::convertNumber<BeliefValueType>(this->options.numericPrecision), false),
84 valueTypeCC(this->options.numericPrecision, false) {
85 STORM_LOG_ASSERT(inputPomdp, "The given POMDP is not initialized.");
86 STORM_LOG_ERROR_COND(inputPomdp->isCanonic(), "Input Pomdp is not known to be canonic. This might lead to unexpected verification results.");
87}
88
89/* Public Functions */
90
91template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
93 storm::Environment const& preProcEnv) {
94 auto formulaInfo = storm::pomdp::analysis::getFormulaInformation(pomdp(), formula);
95
96 // Compute some initial bounds on the values for each state of the pomdp
97 // We work with the Belief MDP value type, so if the POMDP is exact, but the belief MDP is not, we need to convert
98 auto preProcessingMC = PreprocessingPomdpValueBoundsModelChecker<ValueType>(pomdp());
99 auto initialPomdpValueBounds = preProcessingMC.getValueBounds(preProcEnv, formula);
100 pomdpValueBounds.trivialPomdpValueBounds = initialPomdpValueBounds;
101
102 // If we clip and compute rewards, compute the values necessary for the correction terms
103 if (options.useClipping && formula.isRewardOperatorFormula()) {
104 pomdpValueBounds.extremePomdpValueBound = preProcessingMC.getExtremeValueBound(preProcEnv, formula);
105 }
106}
107
108template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
111 storm::Environment const& env, storm::logic::Formula const& formula,
112 std::vector<std::vector<std::unordered_map<uint64_t, ValueType>>> const& additionalUnderApproximationBounds) {
113 return check(env, formula, env, additionalUnderApproximationBounds);
114}
115
116template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
119 storm::logic::Formula const& formula, std::vector<std::vector<std::unordered_map<uint64_t, ValueType>>> const& additionalUnderApproximationBounds) {
121 return check(env, formula, env, additionalUnderApproximationBounds);
122}
123
124template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
127 storm::logic::Formula const& formula, storm::Environment const& preProcEnv,
128 std::vector<std::vector<std::unordered_map<uint64_t, ValueType>>> const& additionalUnderApproximationBounds) {
130 return check(env, formula, preProcEnv, additionalUnderApproximationBounds);
131}
132
133template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
136 storm::Environment const& env, storm::logic::Formula const& formula, storm::Environment const& preProcEnv,
137 std::vector<std::vector<std::unordered_map<uint64_t, ValueType>>> const& additionalUnderApproximationBounds) {
138 STORM_LOG_ASSERT(options.unfold || options.discretize || options.interactiveUnfolding,
139 "Invoked belief exploration but no task (unfold or discretize) given.");
140 // Potentially reset preprocessed model from previous call
141 preprocessedPomdp.reset();
142
143 // Reset all collected statistics
144 statistics = Statistics();
145 statistics.totalTime.start();
146 // Extract the relevant information from the formula
147 auto formulaInfo = storm::pomdp::analysis::getFormulaInformation(pomdp(), formula);
148
149 precomputeValueBounds(formula, preProcEnv);
150 if (!additionalUnderApproximationBounds.empty()) {
151 pomdpValueBounds.fmSchedulerValueList = additionalUnderApproximationBounds;
152 }
153 uint64_t initialPomdpState = pomdp().getInitialStates().getNextSetIndex(0);
154 Result result(pomdpValueBounds.trivialPomdpValueBounds.getHighestLowerBound(initialPomdpState),
155 pomdpValueBounds.trivialPomdpValueBounds.getSmallestUpperBound(initialPomdpState));
156 STORM_LOG_INFO("Initial value bounds are [" << result.lowerBound << ", " << result.upperBound << "]");
157
158 std::optional<std::string> rewardModelName;
159 std::set<uint32_t> targetObservations;
160 if (formulaInfo.isNonNestedReachabilityProbability() || formulaInfo.isNonNestedExpectedRewardFormula()) {
161 if (formulaInfo.getTargetStates().observationClosed) {
162 targetObservations = formulaInfo.getTargetStates().observations;
163 } else {
165 std::tie(preprocessedPomdp, targetObservations) = obsCloser.transform(formulaInfo.getTargetStates().states);
166 }
167 if (formulaInfo.isNonNestedReachabilityProbability()) {
168 if (!formulaInfo.getSinkStates().empty()) {
170 components.stateLabeling = pomdp().getStateLabeling();
171 components.rewardModels = pomdp().getRewardModels();
172 auto matrix = pomdp().getTransitionMatrix();
173 matrix.makeRowGroupsAbsorbing(formulaInfo.getSinkStates().states);
174 components.transitionMatrix = matrix;
175 components.observabilityClasses = pomdp().getObservations();
176 if (pomdp().hasChoiceLabeling()) {
177 components.choiceLabeling = pomdp().getChoiceLabeling();
178 }
179 if (pomdp().hasObservationValuations()) {
180 components.observationValuations = pomdp().getObservationValuations();
181 }
182 preprocessedPomdp = std::make_shared<storm::models::sparse::Pomdp<ValueType>>(std::move(components), true);
183 auto reachableFromSinkStates = storm::utility::graph::getReachableStates(
184 pomdp().getTransitionMatrix(), formulaInfo.getSinkStates().states, formulaInfo.getSinkStates().states, ~formulaInfo.getSinkStates().states);
185 reachableFromSinkStates &= ~formulaInfo.getSinkStates().states;
186 STORM_LOG_THROW(reachableFromSinkStates.empty(), storm::exceptions::NotSupportedException,
187 "There are sink states that can reach non-sink states. This is currently not supported.");
188 }
189 } else {
190 // Expected reward formula!
191 rewardModelName = formulaInfo.getRewardModelName();
192 }
193 } else {
194 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Unsupported formula '" << formula << "'.");
195 }
196 if (storm::pomdp::detectFiniteBeliefMdp(pomdp(), formulaInfo.getTargetStates().states)) {
197 STORM_LOG_INFO("Detected that the belief MDP is finite.");
198 statistics.beliefMdpDetectedToBeFinite = true;
199 }
200 if (options.interactiveUnfolding) {
201 unfoldInteractively(env, targetObservations, formulaInfo.minimize(), rewardModelName, pomdpValueBounds, result);
202 } else {
203 refineReachability(env, targetObservations, formulaInfo.minimize(), rewardModelName, pomdpValueBounds, result);
204 }
205 // "clear" results in case they were actually not requested (this will make the output a bit more clear)
206 if ((formulaInfo.minimize() && !options.discretize) || (formulaInfo.maximize() && !options.unfold)) {
208 }
209 if ((formulaInfo.maximize() && !options.discretize) || (formulaInfo.minimize() && !options.unfold)) {
211 }
212
214 statistics.aborted = true;
215 }
216 statistics.totalTime.stop();
217 return result;
218}
219
220template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
222 stream << "##### POMDP Approximation Statistics ######\n";
223 stream << "# Input model: \n";
224 pomdp().printModelInformationToStream(stream);
225 stream << "# Max. Number of states with same observation: " << pomdp().getMaxNrStatesWithSameObservation() << '\n';
226 if (statistics.beliefMdpDetectedToBeFinite) {
227 stream << "# Pre-computations detected that the belief MDP is finite.\n";
228 }
229 if (statistics.aborted) {
230 stream << "# Computation aborted early\n";
231 }
232
233 stream << "# Total check time: " << statistics.totalTime << '\n';
234 // Refinement information:
235 if (statistics.refinementSteps) {
236 stream << "# Number of refinement steps: " << statistics.refinementSteps.value() << '\n';
237 }
238 if (statistics.refinementFixpointDetected) {
239 stream << "# Detected a refinement fixpoint.\n";
240 }
241
242 // The overapproximation MDP:
243 if (statistics.overApproximationStates) {
244 stream << "# Number of states in the ";
245 if (options.refine) {
246 stream << "final ";
247 }
248 stream << "grid MDP for the over-approximation: ";
249 if (statistics.overApproximationBuildAborted) {
250 stream << ">=";
251 }
252 stream << statistics.overApproximationStates.value() << '\n';
253 stream << "# Maximal resolution for over-approximation: " << statistics.overApproximationMaxResolution.value() << '\n';
254 stream << "# Time spend for building the over-approx grid MDP(s): " << statistics.overApproximationBuildTime << '\n';
255 stream << "# Time spend for checking the over-approx grid MDP(s): " << statistics.overApproximationCheckTime << '\n';
256 }
257
258 // The underapproximation MDP:
259 if (statistics.underApproximationStates) {
260 stream << "# Number of states in the ";
261 if (options.refine) {
262 stream << "final ";
263 }
264 stream << "belief MDP for the under-approximation: ";
265 if (statistics.underApproximationBuildAborted) {
266 stream << ">=";
267 }
268 stream << statistics.underApproximationStates.value() << '\n';
269 if (statistics.nrClippingAttempts) {
270 stream << "# Clipping attempts (clipped states) for the under-approximation: ";
271 if (statistics.underApproximationBuildAborted) {
272 stream << ">=";
273 }
274 stream << statistics.nrClippingAttempts.value() << " (" << statistics.nrClippedStates.value() << ")\n";
275 stream << "# Total clipping preprocessing time: " << statistics.clippingPreTime << "\n";
276 stream << "# Total clipping time: " << statistics.clipWatch << "\n";
277 } else if (statistics.nrTruncatedStates) {
278 stream << "# Truncated states for the under-approximation: ";
279 if (statistics.underApproximationBuildAborted) {
280 stream << ">=";
281 }
282 stream << statistics.nrTruncatedStates.value() << "\n";
283 }
284 if (statistics.underApproximationStateLimit) {
285 stream << "# Exploration state limit for under-approximation: " << statistics.underApproximationStateLimit.value() << '\n';
286 }
287 stream << "# Time spend for building the under-approx grid MDP(s): " << statistics.underApproximationBuildTime << '\n';
288 stream << "# Time spend for checking the under-approx grid MDP(s): " << statistics.underApproximationCheckTime << '\n';
289 }
290
291 stream << "##########################################\n";
292}
293
294/* Private Functions */
295
296template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
297PomdpModelType const& BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::pomdp() const {
298 if (preprocessedPomdp) {
299 return *preprocessedPomdp;
300 } else {
301 return *inputPomdp;
302 }
303}
304
305template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
306void BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::refineReachability(
307 storm::Environment const& env, std::set<uint32_t> const& targetObservations, bool min, std::optional<std::string> rewardModelName,
308 storm::pomdp::modelchecker::POMDPValueBounds<ValueType> const& valueBounds, Result& result) {
309 statistics.refinementSteps = 0;
310 auto trivialPOMDPBounds = valueBounds.trivialPomdpValueBounds;
311 // Set up exploration data
312 std::vector<BeliefValueType> observationResolutionVector;
313 std::shared_ptr<BeliefManagerType> overApproxBeliefManager;
314 std::shared_ptr<ExplorerType> overApproximation;
315 HeuristicParameters overApproxHeuristicPar{};
316 if (options.discretize) { // Setup and build first OverApproximation
317 observationResolutionVector =
318 std::vector<BeliefValueType>(pomdp().getNrObservations(), storm::utility::convertNumber<BeliefValueType>(options.resolutionInit));
319 overApproxBeliefManager = std::make_shared<BeliefManagerType>(
320 pomdp(), storm::utility::convertNumber<BeliefValueType>(options.numericPrecision),
321 options.dynamicTriangulation ? BeliefManagerType::TriangulationMode::Dynamic : BeliefManagerType::TriangulationMode::Static);
322 if (rewardModelName) {
323 overApproxBeliefManager->setRewardModel(rewardModelName);
324 }
325 overApproximation = std::make_shared<ExplorerType>(overApproxBeliefManager, trivialPOMDPBounds, storm::builder::ExplorationHeuristic::BreadthFirst);
326 overApproxHeuristicPar.gapThreshold = options.gapThresholdInit;
327 overApproxHeuristicPar.observationThreshold = options.obsThresholdInit;
328 overApproxHeuristicPar.sizeThreshold = options.sizeThresholdInit == 0 ? std::numeric_limits<uint64_t>::max() : options.sizeThresholdInit;
329 overApproxHeuristicPar.optimalChoiceValueEpsilon = options.optimalChoiceValueThresholdInit;
330
331 buildOverApproximation(env, targetObservations, min, rewardModelName.has_value(), false, overApproxHeuristicPar, observationResolutionVector,
332 overApproxBeliefManager, overApproximation);
333 if (!overApproximation->hasComputedValues() || storm::utility::resources::isTerminate()) {
334 return;
335 }
336 ValueType const& newValue = overApproximation->getComputedValueAtInitialState();
337 bool betterBound = min ? result.updateLowerBound(newValue) : result.updateUpperBound(newValue);
338 if (betterBound) {
339 STORM_LOG_INFO("Initial Over-approx result obtained after " << statistics.totalTime << ". Value is '" << newValue << "'.\n");
340 }
341 }
342
343 std::shared_ptr<BeliefManagerType> underApproxBeliefManager;
344 std::shared_ptr<ExplorerType> underApproximation;
345 HeuristicParameters underApproxHeuristicPar{};
346 if (options.unfold) { // Setup and build first UnderApproximation
347 underApproxBeliefManager = std::make_shared<BeliefManagerType>(
348 pomdp(), storm::utility::convertNumber<BeliefValueType>(options.numericPrecision),
349 options.dynamicTriangulation ? BeliefManagerType::TriangulationMode::Dynamic : BeliefManagerType::TriangulationMode::Static);
350 if (rewardModelName) {
351 underApproxBeliefManager->setRewardModel(rewardModelName);
352 }
353 underApproximation = std::make_shared<ExplorerType>(underApproxBeliefManager, trivialPOMDPBounds, options.explorationHeuristic);
354 underApproxHeuristicPar.gapThreshold = options.gapThresholdInit;
355 underApproxHeuristicPar.optimalChoiceValueEpsilon = options.optimalChoiceValueThresholdInit;
356 underApproxHeuristicPar.sizeThreshold = options.sizeThresholdInit;
357 if (underApproxHeuristicPar.sizeThreshold == 0) {
358 if (!options.refine && options.explorationTimeLimit != 0) {
359 underApproxHeuristicPar.sizeThreshold = std::numeric_limits<uint64_t>::max();
360 } else {
361 underApproxHeuristicPar.sizeThreshold = pomdp().getNumberOfStates() * pomdp().getMaxNrStatesWithSameObservation();
362 STORM_LOG_INFO("Heuristically selected an under-approximation MDP size threshold of " << underApproxHeuristicPar.sizeThreshold << ".\n");
363 }
364 }
365
366 if (options.useClipping && rewardModelName.has_value()) {
367 underApproximation->setExtremeValueBound(valueBounds.extremePomdpValueBound);
368 }
369 if (!valueBounds.fmSchedulerValueList.empty()) {
370 underApproximation->setFMSchedValueList(valueBounds.fmSchedulerValueList);
371 }
372 buildUnderApproximation(env, targetObservations, min, rewardModelName.has_value(), false, underApproxHeuristicPar, underApproxBeliefManager,
373 underApproximation, false);
374 if (!underApproximation->hasComputedValues() || storm::utility::resources::isTerminate()) {
375 return;
376 }
377 ValueType const& newValue = underApproximation->getComputedValueAtInitialState();
378 bool betterBound = min ? result.updateUpperBound(newValue) : result.updateLowerBound(newValue);
379 if (betterBound) {
380 STORM_LOG_INFO("Initial Under-approx result obtained after " << statistics.totalTime << ". Value is '" << newValue << "'.\n");
381 }
382 }
383
384 // Do some output
385 STORM_LOG_INFO("Completed (initial) computation. Current checktime is " << statistics.totalTime << ".");
386 bool computingLowerBound = false;
387 bool computingUpperBound = false;
388 if (options.discretize) {
389 STORM_LOG_INFO("\tOver-approx MDP has size " << overApproximation->getExploredMdp()->getNumberOfStates() << ".");
390 (min ? computingLowerBound : computingUpperBound) = true;
391 }
392 if (options.unfold) {
393 STORM_LOG_INFO("\tUnder-approx MDP has size " << underApproximation->getExploredMdp()->getNumberOfStates() << ".");
394 (min ? computingUpperBound : computingLowerBound) = true;
395 }
396 if (computingLowerBound && computingUpperBound) {
397 STORM_LOG_INFO("\tObtained result is [" << result.lowerBound << ", " << result.upperBound << "].");
398 } else if (computingLowerBound) {
399 STORM_LOG_INFO("\tObtained result is ≥" << result.lowerBound << ".");
400 } else if (computingUpperBound) {
401 STORM_LOG_INFO("\tObtained result is ≤" << result.upperBound << ".");
402 }
403
404 // Start refinement
405 if (options.refine) {
406 STORM_LOG_WARN_COND(options.refineStepLimit != 0 || !storm::utility::isZero(options.refinePrecision),
407 "No termination criterion for refinement given. Consider to specify a steplimit, a non-zero precisionlimit, or a timeout");
408 STORM_LOG_WARN_COND(storm::utility::isZero(options.refinePrecision) || (options.unfold && options.discretize),
409 "Refinement goal precision is given, but only one bound is going to be refined.");
410 while ((options.refineStepLimit == 0 || statistics.refinementSteps.value() < options.refineStepLimit) && result.diff() > options.refinePrecision) {
411 bool overApproxFixPoint = true;
412 bool underApproxFixPoint = true;
413 if (options.discretize) {
414 // Refine over-approximation
415 if (min) {
416 overApproximation->takeCurrentValuesAsLowerBounds();
417 } else {
418 overApproximation->takeCurrentValuesAsUpperBounds();
419 }
420 overApproxHeuristicPar.gapThreshold *= options.gapThresholdFactor;
421 overApproxHeuristicPar.sizeThreshold = storm::utility::convertNumber<uint64_t, ValueType>(
422 storm::utility::convertNumber<ValueType, uint64_t>(overApproximation->getExploredMdp()->getNumberOfStates()) * options.sizeThresholdFactor);
423 overApproxHeuristicPar.observationThreshold +=
424 options.obsThresholdIncrementFactor * (storm::utility::one<ValueType>() - overApproxHeuristicPar.observationThreshold);
425 overApproxHeuristicPar.optimalChoiceValueEpsilon *= options.optimalChoiceValueThresholdFactor;
426 overApproxFixPoint = buildOverApproximation(env, targetObservations, min, rewardModelName.has_value(), true, overApproxHeuristicPar,
427 observationResolutionVector, overApproxBeliefManager, overApproximation);
428 if (overApproximation->hasComputedValues() && !storm::utility::resources::isTerminate()) {
429 ValueType const& newValue = overApproximation->getComputedValueAtInitialState();
430 bool betterBound = min ? result.updateLowerBound(newValue) : result.updateUpperBound(newValue);
431 if (betterBound) {
432 STORM_LOG_INFO("Over-approx result for refinement improved after " << statistics.totalTime << " in refinement step #"
433 << (statistics.refinementSteps.value() + 1) << ". New value is '"
434 << newValue << "'.");
435 }
436 } else {
437 break;
438 }
439 }
440
441 if (options.unfold && result.diff() > options.refinePrecision) {
442 // Refine under-approximation
443 underApproxHeuristicPar.gapThreshold *= options.gapThresholdFactor;
444 underApproxHeuristicPar.sizeThreshold = storm::utility::convertNumber<uint64_t, ValueType>(
445 storm::utility::convertNumber<ValueType, uint64_t>(underApproximation->getExploredMdp()->getNumberOfStates()) *
446 options.sizeThresholdFactor);
447 underApproxHeuristicPar.optimalChoiceValueEpsilon *= options.optimalChoiceValueThresholdFactor;
448 underApproxFixPoint = buildUnderApproximation(env, targetObservations, min, rewardModelName.has_value(), true, underApproxHeuristicPar,
449 underApproxBeliefManager, underApproximation, true);
450 if (underApproximation->hasComputedValues() && !storm::utility::resources::isTerminate()) {
451 ValueType const& newValue = underApproximation->getComputedValueAtInitialState();
452 bool betterBound = min ? result.updateUpperBound(newValue) : result.updateLowerBound(newValue);
453 if (betterBound) {
454 STORM_LOG_INFO("Under-approx result for refinement improved after " << statistics.totalTime << " in refinement step #"
455 << (statistics.refinementSteps.value() + 1) << ". New value is '"
456 << newValue << "'.");
457 }
458 } else {
459 break;
460 }
461 }
462
464 break;
465 } else {
466 ++statistics.refinementSteps.value();
467 // Don't make too many outputs (to avoid logfile clutter)
468 if (statistics.refinementSteps.value() <= 1000) {
469 STORM_LOG_INFO("Completed iteration #" << statistics.refinementSteps.value() << ". Current checktime is " << statistics.totalTime << ".");
470 computingLowerBound = false;
471 computingUpperBound = false;
472 if (options.discretize) {
473 STORM_LOG_INFO("\tOver-approx MDP has size " << overApproximation->getExploredMdp()->getNumberOfStates() << ".");
474 (min ? computingLowerBound : computingUpperBound) = true;
475 }
476 if (options.unfold) {
477 STORM_LOG_INFO("\tUnder-approx MDP has size " << underApproximation->getExploredMdp()->getNumberOfStates() << ".");
478 (min ? computingUpperBound : computingLowerBound) = true;
479 }
480 if (computingLowerBound && computingUpperBound) {
481 STORM_LOG_INFO("\tCurrent result is [" << result.lowerBound << ", " << result.upperBound << "].");
482 } else if (computingLowerBound) {
483 STORM_LOG_INFO("\tCurrent result is ≥" << result.lowerBound << ".");
484 } else if (computingUpperBound) {
485 STORM_LOG_INFO("\tCurrent result is ≤" << result.upperBound << ".");
486 }
487 STORM_LOG_WARN_COND(statistics.refinementSteps.value() < 1000, "Refinement requires more than 1000 iterations.");
488 }
489 }
490 if (overApproxFixPoint && underApproxFixPoint) {
491 STORM_LOG_INFO("Refinement fixpoint reached after " << statistics.refinementSteps.value() << " iterations.\n");
492 statistics.refinementFixpointDetected = true;
493 break;
494 }
495 }
496 }
497 // Print model information of final over- / under-approximation MDP
498 if (options.discretize && overApproximation->hasComputedValues()) {
499 auto printOverInfo = [&overApproximation]() {
500 std::stringstream str;
501 str << "Explored and checked Over-Approximation MDP:\n";
502 overApproximation->getExploredMdp()->printModelInformationToStream(str);
503 return str.str();
504 };
505 STORM_LOG_INFO(printOverInfo());
506 }
507 if (options.unfold && underApproximation->hasComputedValues()) {
508 auto printUnderInfo = [&underApproximation]() {
509 std::stringstream str;
510 str << "Explored and checked Under-Approximation MDP:\n";
511 underApproximation->getExploredMdp()->printModelInformationToStream(str);
512 return str.str();
513 };
514 STORM_LOG_INFO(printUnderInfo());
515 std::shared_ptr<storm::models::sparse::Model<ValueType>> scheduledModel = underApproximation->getExploredMdp();
516 if (!options.useStateEliminationCutoff) {
517 storm::models::sparse::StateLabeling newLabeling(scheduledModel->getStateLabeling());
518 auto nrPreprocessingScheds = min ? underApproximation->getNrSchedulersForUpperBounds() : underApproximation->getNrSchedulersForLowerBounds();
519 for (uint64_t i = 0; i < nrPreprocessingScheds; ++i) {
520 newLabeling.addLabel("sched_" + std::to_string(i));
521 }
522 newLabeling.addLabel("cutoff");
523 newLabeling.addLabel("clipping");
524 newLabeling.addLabel("finite_mem");
525
526 auto transMatrix = scheduledModel->getTransitionMatrix();
527 for (uint64_t i = 0; i < scheduledModel->getNumberOfStates(); ++i) {
528 if (newLabeling.getStateHasLabel("truncated", i)) {
529 uint64_t localChosenActionIndex = underApproximation->getSchedulerForExploredMdp()->getChoice(i).getDeterministicChoice();
530 auto rowIndex = scheduledModel->getTransitionMatrix().getRowGroupIndices()[i];
531 if (scheduledModel->getChoiceLabeling().getLabelsOfChoice(rowIndex + localChosenActionIndex).size() > 0) {
532 auto label = *(scheduledModel->getChoiceLabeling().getLabelsOfChoice(rowIndex + localChosenActionIndex).begin());
533 if (label.rfind("clip", 0) == 0) {
534 newLabeling.addLabelToState("clipping", i);
535 auto chosenRow = transMatrix.getRow(i, 0);
536 auto candidateIndex = (chosenRow.end() - 1)->getColumn();
537 transMatrix.makeRowDirac(transMatrix.getRowGroupIndices()[i], candidateIndex);
538 } else if (label.rfind("mem_node", 0) == 0) {
539 if (!newLabeling.containsLabel("finite_mem_" + label.substr(9, 1))) {
540 newLabeling.addLabel("finite_mem_" + label.substr(9, 1));
541 }
542 newLabeling.addLabelToState("finite_mem_" + label.substr(9, 1), i);
543 newLabeling.addLabelToState("cutoff", i);
544 } else {
545 newLabeling.addLabelToState(label, i);
546 newLabeling.addLabelToState("cutoff", i);
547 }
548 }
549 }
550 }
551 newLabeling.removeLabel("truncated");
552
553 transMatrix.dropZeroEntries();
554 storm::storage::sparse::ModelComponents<ValueType> modelComponents(transMatrix, newLabeling);
555 if (scheduledModel->hasChoiceLabeling()) {
556 modelComponents.choiceLabeling = scheduledModel->getChoiceLabeling();
557 }
558 storm::models::sparse::Mdp<ValueType> newMDP(modelComponents);
559 auto inducedMC = newMDP.applyScheduler(*(underApproximation->getSchedulerForExploredMdp()), true);
560 scheduledModel = std::static_pointer_cast<storm::models::sparse::Model<ValueType>>(inducedMC);
561 } else {
562 auto inducedMC = underApproximation->getExploredMdp()->applyScheduler(*(underApproximation->getSchedulerForExploredMdp()), true);
563 scheduledModel = std::static_pointer_cast<storm::models::sparse::Model<ValueType>>(inducedMC);
564 }
565 result.schedulerAsMarkovChain = scheduledModel;
566 if (min) {
567 result.cutoffSchedulers = underApproximation->getUpperValueBoundSchedulers();
568 } else {
569 result.cutoffSchedulers = underApproximation->getLowerValueBoundSchedulers();
570 }
571 }
572}
573
574template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
576 storm::Environment const& env, std::set<uint32_t> const& targetObservations, bool min, std::optional<std::string> rewardModelName,
578 statistics.refinementSteps = 0;
579 interactiveResult = result;
580 unfoldingStatus = Status::Uninitialized;
581 unfoldingControl = UnfoldingControl::Run;
582 auto trivialPOMDPBounds = valueBounds.trivialPomdpValueBounds;
583 // Set up exploration data
584 std::shared_ptr<BeliefManagerType> underApproxBeliefManager;
585 HeuristicParameters underApproxHeuristicPar{};
586 bool firstIteration = true;
587 // Set up belief manager
588 underApproxBeliefManager = std::make_shared<BeliefManagerType>(
589 pomdp(), storm::utility::convertNumber<BeliefValueType>(options.numericPrecision),
590 options.dynamicTriangulation ? BeliefManagerType::TriangulationMode::Dynamic : BeliefManagerType::TriangulationMode::Static);
591 if (rewardModelName) {
592 underApproxBeliefManager->setRewardModel(rewardModelName);
593 }
594
595 // set up belief MDP explorer
596 interactiveUnderApproximationExplorer = std::make_shared<ExplorerType>(underApproxBeliefManager, trivialPOMDPBounds, options.explorationHeuristic);
597 underApproxHeuristicPar.gapThreshold = options.gapThresholdInit;
598 underApproxHeuristicPar.optimalChoiceValueEpsilon = options.optimalChoiceValueThresholdInit;
599 underApproxHeuristicPar.sizeThreshold = std::numeric_limits<uint64_t>::max() - 1; // we don't set a size threshold
600
601 if (options.useClipping && rewardModelName.has_value()) {
602 interactiveUnderApproximationExplorer->setExtremeValueBound(valueBounds.extremePomdpValueBound);
603 }
604
605 if (!valueBounds.fmSchedulerValueList.empty()) {
606 interactiveUnderApproximationExplorer->setFMSchedValueList(valueBounds.fmSchedulerValueList);
607 }
608
609 // Start iteration
610 while (!(unfoldingControl ==
611 storm::pomdp::modelchecker::BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::UnfoldingControl::Terminate)) {
612 bool underApproxFixPoint = true;
613 bool hasTruncatedStates = false;
614 if (unfoldingStatus != Status::Converged) {
615 // Continue unfolding underapproximation
616 underApproxFixPoint = buildUnderApproximation(env, targetObservations, min, rewardModelName.has_value(), false, underApproxHeuristicPar,
617 underApproxBeliefManager, interactiveUnderApproximationExplorer, firstIteration);
618 if (interactiveUnderApproximationExplorer->hasComputedValues() && !storm::utility::resources::isTerminate()) {
619 ValueType const& newValue = interactiveUnderApproximationExplorer->getComputedValueAtInitialState();
620 bool betterBound = min ? interactiveResult.updateUpperBound(newValue) : interactiveResult.updateLowerBound(newValue);
621 if (betterBound) {
622 STORM_LOG_INFO("Under-approximation result improved after " << statistics.totalTime << " in step #"
623 << (statistics.refinementSteps.value() + 1) << ". New value is '" << newValue
624 << "'.");
625 }
626 std::shared_ptr<storm::models::sparse::Model<ValueType>> scheduledModel = interactiveUnderApproximationExplorer->getExploredMdp();
627 if (!options.useStateEliminationCutoff) {
628 storm::models::sparse::StateLabeling newLabeling(scheduledModel->getStateLabeling());
629 auto nrPreprocessingScheds = min ? interactiveUnderApproximationExplorer->getNrSchedulersForUpperBounds()
630 : interactiveUnderApproximationExplorer->getNrSchedulersForLowerBounds();
631 for (uint64_t i = 0; i < nrPreprocessingScheds; ++i) {
632 newLabeling.addLabel("sched_" + std::to_string(i));
633 }
634 newLabeling.addLabel("cutoff");
635 newLabeling.addLabel("clipping");
636 newLabeling.addLabel("finite_mem");
637
638 auto transMatrix = scheduledModel->getTransitionMatrix();
639 for (uint64_t i = 0; i < scheduledModel->getNumberOfStates(); ++i) {
640 if (newLabeling.getStateHasLabel("truncated", i)) {
641 hasTruncatedStates = true;
642 uint64_t localChosenActionIndex =
643 interactiveUnderApproximationExplorer->getSchedulerForExploredMdp()->getChoice(i).getDeterministicChoice();
644 auto rowIndex = scheduledModel->getTransitionMatrix().getRowGroupIndices()[i];
645 if (scheduledModel->getChoiceLabeling().getLabelsOfChoice(rowIndex + localChosenActionIndex).size() > 0) {
646 auto label = *(scheduledModel->getChoiceLabeling().getLabelsOfChoice(rowIndex + localChosenActionIndex).begin());
647 if (label.rfind("clip", 0) == 0) {
648 newLabeling.addLabelToState("clipping", i);
649 auto chosenRow = transMatrix.getRow(i, 0);
650 auto candidateIndex = (chosenRow.end() - 1)->getColumn();
651 transMatrix.makeRowDirac(transMatrix.getRowGroupIndices()[i], candidateIndex);
652 } else if (label.rfind("mem_node", 0) == 0) {
653 if (!newLabeling.containsLabel("finite_mem_" + label.substr(9, 1))) {
654 newLabeling.addLabel("finite_mem_" + label.substr(9, 1));
655 }
656 newLabeling.addLabelToState("finite_mem_" + label.substr(9, 1), i);
657 newLabeling.addLabelToState("cutoff", i);
658 } else {
659 newLabeling.addLabelToState(label, i);
660 newLabeling.addLabelToState("cutoff", i);
661 }
662 }
663 }
664 }
665 newLabeling.removeLabel("truncated");
666
667 transMatrix.dropZeroEntries();
668 storm::storage::sparse::ModelComponents<ValueType> modelComponents(transMatrix, newLabeling);
669 if (scheduledModel->hasChoiceLabeling()) {
670 modelComponents.choiceLabeling = scheduledModel->getChoiceLabeling();
671 }
672 storm::models::sparse::Mdp<ValueType> newMDP(modelComponents);
673 auto inducedMC = newMDP.applyScheduler(*(interactiveUnderApproximationExplorer->getSchedulerForExploredMdp()), true);
674 scheduledModel = std::static_pointer_cast<storm::models::sparse::Model<ValueType>>(inducedMC);
675 }
676 interactiveResult.schedulerAsMarkovChain = scheduledModel;
677 if (min) {
678 interactiveResult.cutoffSchedulers = interactiveUnderApproximationExplorer->getUpperValueBoundSchedulers();
679 } else {
680 interactiveResult.cutoffSchedulers = interactiveUnderApproximationExplorer->getLowerValueBoundSchedulers();
681 }
682 if (firstIteration) {
683 firstIteration = false;
684 }
685 unfoldingStatus = Status::ResultAvailable;
686 } else {
687 break;
688 }
689
691 break;
692 } else {
693 ++statistics.refinementSteps.value();
694 // Don't make too many outputs (to avoid logfile clutter)
695 if (statistics.refinementSteps.value() <= 1000) {
696 STORM_LOG_INFO("Completed iteration #" << statistics.refinementSteps.value() << ". Current checktime is " << statistics.totalTime << ".");
697 bool computingLowerBound = false;
698 bool computingUpperBound = false;
699 if (options.unfold) {
700 STORM_LOG_INFO("\tUnder-approx MDP has size " << interactiveUnderApproximationExplorer->getExploredMdp()->getNumberOfStates() << ".");
701 (min ? computingUpperBound : computingLowerBound) = true;
702 }
703 if (computingLowerBound && computingUpperBound) {
704 STORM_LOG_INFO("\tCurrent result is [" << interactiveResult.lowerBound << ", " << interactiveResult.upperBound << "].");
705 } else if (computingLowerBound) {
706 STORM_LOG_INFO("\tCurrent result is ≥" << interactiveResult.lowerBound << ".");
707 } else if (computingUpperBound) {
708 STORM_LOG_INFO("\tCurrent result is ≤" << interactiveResult.upperBound << ".");
709 }
710 }
711 }
712 if (underApproxFixPoint) {
713 STORM_LOG_INFO("Fixpoint reached after " << statistics.refinementSteps.value() << " iterations.\n");
714 statistics.refinementFixpointDetected = true;
715 unfoldingStatus = Status::Converged;
716 unfoldingControl = UnfoldingControl::Pause;
717 }
718 if (!hasTruncatedStates) {
719 STORM_LOG_INFO("No states have been truncated, so continued iteration does not yield new results.\n");
720 unfoldingStatus = Status::Converged;
721 unfoldingControl = UnfoldingControl::Pause;
722 }
723 }
724 // While we tell the procedure to be paused, idle
725 while (unfoldingControl ==
726 storm::pomdp::modelchecker::BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::UnfoldingControl::Pause &&
728 // Intentionally left empty
729 }
730 }
731 STORM_LOG_INFO("\tInteractive Unfolding terminated.\n");
732}
733
734template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
736 std::set<uint32_t> const& targetObservations, bool min, std::optional<std::string> rewardModelName,
739 unfoldInteractively(env, targetObservations, min, rewardModelName, valueBounds, result);
740}
741
742template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
747
748template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
750 if (unfoldingStatus == Status::Uninitialized) {
751 return 0;
752 }
753 if (unfoldingStatus == Status::Exploring) {
754 return 1;
755 }
756 if (unfoldingStatus == Status::ModelExplorationFinished) {
757 return 2;
758 }
759 if (unfoldingStatus == Status::ResultAvailable) {
760 return 3;
761 }
762 if (unfoldingStatus == Status::Terminated) {
763 return 4;
764 }
765
766 return -1;
767}
768
769template<typename ValueType>
770ValueType getGap(ValueType const& l, ValueType const& u) {
772 "Gap computation currently does not handle negative values.");
776 } else {
777 return u;
778 }
779 } else if (storm::utility::isZero(u)) {
780 STORM_LOG_ASSERT(storm::utility::isZero(l), "Upper bound is zero but lower bound is " << l << ".");
781 return u;
782 } else {
783 STORM_LOG_ASSERT(!storm::utility::isInfinity(l), "Lower bound is infinity, but upper bound is " << u << ".");
784 // get the relative gap
786 }
787}
788
789template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
790bool BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::buildOverApproximation(
791 storm::Environment const& env, std::set<uint32_t> const& targetObservations, bool min, bool computeRewards, bool refine,
792 HeuristicParameters const& heuristicParameters, std::vector<BeliefValueType>& observationResolutionVector,
793 std::shared_ptr<BeliefManagerType>& beliefManager, std::shared_ptr<ExplorerType>& overApproximation) {
794 // Detect whether the refinement reached a fixpoint.
795 bool fixPoint = true;
796
797 statistics.overApproximationBuildTime.start();
798 storm::storage::BitVector refinedObservations;
799 if (!refine) {
800 // If we build the model from scratch, we first have to set up the explorer for the overApproximation.
801 if (computeRewards) {
802 overApproximation->startNewExploration(storm::utility::zero<ValueType>());
803 } else {
804 overApproximation->startNewExploration(storm::utility::one<ValueType>(), storm::utility::zero<ValueType>());
805 }
806 } else {
807 // If we refine the existing overApproximation, our heuristic also wants to know which states are reachable under an optimal policy
808 overApproximation->computeOptimalChoicesAndReachableMdpStates(heuristicParameters.optimalChoiceValueEpsilon, true);
809 // We also need to find out which observation resolutions needs refinement.
810 // current maximal resolution (needed for refinement heuristic)
811 auto obsRatings = getObservationRatings(overApproximation, observationResolutionVector);
812 // If there is a score < 1, we have not reached a fixpoint, yet
813 auto numericPrecision = storm::utility::convertNumber<BeliefValueType>(options.numericPrecision);
814 if (std::any_of(obsRatings.begin(), obsRatings.end(),
815 [&numericPrecision](BeliefValueType const& value) { return value + numericPrecision < storm::utility::one<BeliefValueType>(); })) {
816 STORM_LOG_INFO_COND(!fixPoint, "Not reaching a refinement fixpoint because there are still observations to refine.");
817 fixPoint = false;
818 }
819 refinedObservations = storm::utility::vector::filter<BeliefValueType>(obsRatings, [&heuristicParameters](BeliefValueType const& r) {
820 return r <= storm::utility::convertNumber<BeliefValueType>(heuristicParameters.observationThreshold);
821 });
822 STORM_LOG_DEBUG("Refining the resolution of " << refinedObservations.getNumberOfSetBits() << "/" << refinedObservations.size() << " observations.");
823 for (uint64_t obs : refinedObservations) {
824 // Increment the resolution at the refined observations.
825 // Use storm's rational number to detect overflows properly.
826 storm::RationalNumber newObsResolutionAsRational = storm::utility::convertNumber<storm::RationalNumber>(observationResolutionVector[obs]) *
828 static_assert(storm::NumberTraits<BeliefValueType>::IsExact || std::is_same<BeliefValueType, double>::value, "Unhandled belief value type");
830 newObsResolutionAsRational > storm::utility::convertNumber<storm::RationalNumber>(std::numeric_limits<double>::max())) {
831 observationResolutionVector[obs] = storm::utility::convertNumber<BeliefValueType>(std::numeric_limits<double>::max());
832 } else {
833 observationResolutionVector[obs] = storm::utility::convertNumber<BeliefValueType>(newObsResolutionAsRational);
834 }
835 }
836 overApproximation->restartExploration();
837 }
838 statistics.overApproximationMaxResolution = storm::utility::ceil(*std::max_element(observationResolutionVector.begin(), observationResolutionVector.end()));
839
840 // Start exploration
841 storm::utility::Stopwatch explorationTime;
842 if (options.explorationTimeLimit != 0) {
843 explorationTime.start();
844 }
845 bool timeLimitExceeded = false;
846 std::map<uint32_t, typename ExplorerType::SuccessorObservationInformation> gatheredSuccessorObservations; // Declare here to avoid reallocations
847 uint64_t numRewiredOrExploredStates = 0;
848 while (overApproximation->hasUnexploredState()) {
849 if (!timeLimitExceeded && options.explorationTimeLimit != 0 &&
850 static_cast<uint64_t>(explorationTime.getTimeInSeconds()) > options.explorationTimeLimit) {
851 STORM_LOG_INFO("Exploration time limit exceeded.");
852 timeLimitExceeded = true;
853 STORM_LOG_INFO_COND(!fixPoint, "Not reaching a refinement fixpoint because the exploration time limit is exceeded.");
854 fixPoint = false;
855 }
856
857 uint64_t currId = overApproximation->exploreNextState();
858 bool hasOldBehavior = refine && overApproximation->currentStateHasOldBehavior();
859 if (!hasOldBehavior) {
860 STORM_LOG_INFO_COND(!fixPoint, "Not reaching a refinement fixpoint because a new state is explored");
861 fixPoint = false; // Exploring a new state!
862 }
863 uint32_t currObservation = beliefManager->getBeliefObservation(currId);
864 if (targetObservations.count(currObservation) != 0) {
865 overApproximation->setCurrentStateIsTarget();
866 overApproximation->addSelfloopTransition();
867 } else {
868 // We need to decide how to treat this state (and each individual enabled action). There are the following cases:
869 // 1 The state has no old behavior and
870 // 1.1 we explore all actions or
871 // 1.2 we truncate all actions
872 // 2 The state has old behavior and was truncated in the last iteration and
873 // 2.1 we explore all actions or
874 // 2.2 we truncate all actions (essentially restoring old behavior, but we do the truncation step again to benefit from updated bounds)
875 // 3 The state has old behavior and was not truncated in the last iteration and the current action
876 // 3.1 should be rewired or
877 // 3.2 should get the old behavior but either
878 // 3.2.1 none of the successor observation has been refined since the last rewiring or exploration of this action
879 // 3.2.2 rewiring is only delayed as it could still have an effect in a later refinement step
880
881 // Find out in which case we are
882 bool exploreAllActions = false;
883 bool truncateAllActions = false;
884 bool restoreAllActions = false;
885 bool checkRewireForAllActions = false;
886 // Get the relative gap
887 ValueType gap = getGap(overApproximation->getLowerValueBoundAtCurrentState(), overApproximation->getUpperValueBoundAtCurrentState());
888 if (!hasOldBehavior) {
889 // Case 1
890 // If we explore this state and if it has no old behavior, it is clear that an "old" optimal scheduler can be extended to a scheduler that
891 // reaches this state
892 if (!timeLimitExceeded && gap >= heuristicParameters.gapThreshold && numRewiredOrExploredStates < heuristicParameters.sizeThreshold) {
893 exploreAllActions = true; // Case 1.1
894 } else {
895 truncateAllActions = true; // Case 1.2
896 overApproximation->setCurrentStateIsTruncated();
897 }
898 } else if (overApproximation->getCurrentStateWasTruncated()) {
899 // Case 2
900 if (!timeLimitExceeded && overApproximation->currentStateIsOptimalSchedulerReachable() && gap > heuristicParameters.gapThreshold &&
901 numRewiredOrExploredStates < heuristicParameters.sizeThreshold) {
902 exploreAllActions = true; // Case 2.1
903 STORM_LOG_INFO_COND(!fixPoint, "Not reaching a refinement fixpoint because a previously truncated state is now explored.");
904 fixPoint = false;
905 } else {
906 truncateAllActions = true; // Case 2.2
907 overApproximation->setCurrentStateIsTruncated();
908 if (fixPoint) {
909 // Properly check whether this can still be a fixpoint
910 if (overApproximation->currentStateIsOptimalSchedulerReachable() && !storm::utility::isZero(gap)) {
911 STORM_LOG_INFO_COND(!fixPoint, "Not reaching a refinement fixpoint because we truncate a state with non-zero gap "
912 << gap << " that is reachable via an optimal sched.");
913 fixPoint = false;
914 }
915 // else {}
916 // In this case we truncated a state that is not reachable under optimal schedulers.
917 // If no other state is explored (i.e. fixPoint remains true), these states should still not be reachable in subsequent iterations
918 }
919 }
920 } else {
921 // Case 3
922 // The decision for rewiring also depends on the corresponding action, but we have some criteria that lead to case 3.2 (independent of the
923 // action)
924 if (!timeLimitExceeded && overApproximation->currentStateIsOptimalSchedulerReachable() && gap > heuristicParameters.gapThreshold &&
925 numRewiredOrExploredStates < heuristicParameters.sizeThreshold) {
926 checkRewireForAllActions = true; // Case 3.1 or Case 3.2
927 } else {
928 restoreAllActions = true; // Definitely Case 3.2
929 // We still need to check for each action whether rewiring makes sense later
930 checkRewireForAllActions = true;
931 }
932 }
933 bool expandedAtLeastOneAction = false;
934 for (uint64_t action = 0, numActions = beliefManager->getBeliefNumberOfChoices(currId); action < numActions; ++action) {
935 bool expandCurrentAction = exploreAllActions || truncateAllActions;
936 if (checkRewireForAllActions) {
937 STORM_LOG_ASSERT(refine, "Expected refine to be true.");
938 // In this case, we still need to check whether this action needs to be expanded
939 STORM_LOG_ASSERT(!expandCurrentAction, "Action should not be expanded.");
940 // Check the action dependent conditions for rewiring
941 // First, check whether this action has been rewired since the last refinement of one of the successor observations (i.e. whether rewiring
942 // would actually change the successor states)
943 STORM_LOG_ASSERT(overApproximation->currentStateHasOldBehavior(), "Expected old behavior.");
944 if (overApproximation->getCurrentStateActionExplorationWasDelayed(action) ||
945 overApproximation->currentStateHasSuccessorObservationInObservationSet(action, refinedObservations)) {
946 // Then, check whether the other criteria for rewiring are satisfied
947 if (!restoreAllActions && overApproximation->actionAtCurrentStateWasOptimal(action)) {
948 // Do the rewiring now! (Case 3.1)
949 expandCurrentAction = true;
950 STORM_LOG_INFO_COND(!fixPoint, "Not reaching a refinement fixpoint because we rewire a state.");
951 fixPoint = false;
952 } else {
953 // Delay the rewiring (Case 3.2.2)
954 overApproximation->setCurrentChoiceIsDelayed(action);
955 if (fixPoint) {
956 // Check whether this delay means that a fixpoint has not been reached
957 if (!overApproximation->getCurrentStateActionExplorationWasDelayed(action) ||
958 (overApproximation->currentStateIsOptimalSchedulerReachable() &&
959 overApproximation->actionAtCurrentStateWasOptimal(action) && !storm::utility::isZero(gap))) {
960 STORM_LOG_INFO_COND(!fixPoint,
961 "Not reaching a refinement fixpoint because we delay a rewiring of a state with non-zero gap "
962 << gap << " that is reachable via an optimal scheduler.");
963 fixPoint = false;
964 }
965 }
966 }
967 } // else { Case 3.2.1 }
968 }
969
970 if (expandCurrentAction) {
971 expandedAtLeastOneAction = true;
972 if (!truncateAllActions) {
973 // Cases 1.1, 2.1, or 3.1
974 auto successorGridPoints = beliefManager->expandAndTriangulate(env, currId, action, observationResolutionVector);
975 for (auto const& successor : successorGridPoints) {
976 overApproximation->addTransitionToBelief(action, successor.first, successor.second, false);
977 }
978 if (computeRewards) {
979 overApproximation->computeRewardAtCurrentState(action);
980 }
981 } else {
982 // Cases 1.2 or 2.2
983 auto truncationProbability = storm::utility::zero<ValueType>();
984 auto truncationValueBound = storm::utility::zero<ValueType>();
985 auto successorGridPoints = beliefManager->expandAndTriangulate(env, currId, action, observationResolutionVector);
986 for (auto const& successor : successorGridPoints) {
987 bool added = overApproximation->addTransitionToBelief(action, successor.first, successor.second, true);
988 if (!added) {
989 // We did not explore this successor state. Get a bound on the "missing" value
990 truncationProbability += successor.second;
991 truncationValueBound += successor.second * (min ? overApproximation->computeLowerValueBoundAtBelief(successor.first)
992 : overApproximation->computeUpperValueBoundAtBelief(successor.first));
993 }
994 }
995 if (computeRewards) {
996 // The truncationValueBound will be added on top of the reward introduced by the current belief state.
997 overApproximation->addTransitionsToExtraStates(action, truncationProbability);
998 overApproximation->computeRewardAtCurrentState(action, truncationValueBound);
999 } else {
1000 overApproximation->addTransitionsToExtraStates(action, truncationValueBound, truncationProbability - truncationValueBound);
1001 }
1002 }
1003 } else {
1004 // Case 3.2
1005 overApproximation->restoreOldBehaviorAtCurrentState(action);
1006 }
1007 }
1008 if (expandedAtLeastOneAction) {
1009 ++numRewiredOrExploredStates;
1010 }
1011 }
1012
1013 for (uint64_t action = 0, numActions = beliefManager->getBeliefNumberOfChoices(currId); action < numActions; ++action) {
1014 if (pomdp().hasChoiceLabeling()) {
1015 auto rowIndex = pomdp().getTransitionMatrix().getRowGroupIndices()[beliefManager->getRepresentativeState(currId)];
1016 if (pomdp().getChoiceLabeling().getLabelsOfChoice(rowIndex + action).size() > 0) {
1017 overApproximation->addChoiceLabelToCurrentState(action, *(pomdp().getChoiceLabeling().getLabelsOfChoice(rowIndex + action).begin()));
1018 }
1019 }
1020 }
1021
1023 break;
1024 }
1025 }
1026
1028 // don't overwrite statistics of a previous, successful computation
1029 if (!statistics.overApproximationStates) {
1030 statistics.overApproximationBuildAborted = true;
1031 statistics.overApproximationStates = overApproximation->getCurrentNumberOfMdpStates();
1032 }
1033 statistics.overApproximationBuildTime.stop();
1034 return false;
1035 }
1036
1037 overApproximation->finishExploration();
1038 statistics.overApproximationBuildTime.stop();
1039
1040 statistics.overApproximationCheckTime.start();
1041 overApproximation->computeValuesOfExploredMdp(env, min ? storm::solver::OptimizationDirection::Minimize : storm::solver::OptimizationDirection::Maximize);
1042 statistics.overApproximationCheckTime.stop();
1043
1044 // don't overwrite statistics of a previous, successful computation
1045 if (!storm::utility::resources::isTerminate() || !statistics.overApproximationStates) {
1046 statistics.overApproximationStates = overApproximation->getExploredMdp()->getNumberOfStates();
1047 }
1048 return fixPoint;
1049}
1050
1051template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1052bool BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::buildUnderApproximation(
1053 storm::Environment const& env, std::set<uint32_t> const& targetObservations, bool min, bool computeRewards, bool refine,
1054 HeuristicParameters const& heuristicParameters, std::shared_ptr<BeliefManagerType>& beliefManager, std::shared_ptr<ExplorerType>& underApproximation,
1055 bool firstIteration) {
1056 statistics.underApproximationBuildTime.start();
1057
1058 unfoldingStatus = Status::Exploring;
1059 if (options.useClipping) {
1060 STORM_LOG_INFO("Use Belief Clipping with grid beliefs \n");
1061 statistics.nrClippingAttempts = 0;
1062 statistics.nrClippedStates = 0;
1063 }
1064
1065 uint64_t nrCutoffStrategies = min ? underApproximation->getNrSchedulersForUpperBounds() : underApproximation->getNrSchedulersForLowerBounds();
1066
1067 bool fixPoint = true;
1068 if (heuristicParameters.sizeThreshold != std::numeric_limits<uint64_t>::max()) {
1069 statistics.underApproximationStateLimit = heuristicParameters.sizeThreshold;
1070 }
1071 if (!refine) {
1072 if (options.interactiveUnfolding && !firstIteration) {
1073 underApproximation->restoreExplorationState();
1074 } else if (computeRewards) { // Build a new under approximation
1075 // We use the sink state for infinite cut-off/clipping values
1076 underApproximation->startNewExploration(storm::utility::zero<ValueType>(), storm::utility::infinity<ValueType>());
1077 } else {
1078 underApproximation->startNewExploration(storm::utility::one<ValueType>(), storm::utility::zero<ValueType>());
1079 }
1080 } else {
1081 // Restart the building process
1082 underApproximation->restartExploration();
1083 }
1084
1085 // Expand the beliefs
1086 storm::utility::Stopwatch explorationTime;
1087 storm::utility::Stopwatch printUpdateStopwatch;
1088 printUpdateStopwatch.start();
1089 if (options.explorationTimeLimit != 0) {
1090 explorationTime.start();
1091 }
1092 bool timeLimitExceeded = false;
1093 bool stateStored = false;
1094 while (underApproximation->hasUnexploredState()) {
1095 if (!timeLimitExceeded && options.explorationTimeLimit != 0 &&
1096 static_cast<uint64_t>(explorationTime.getTimeInSeconds()) > options.explorationTimeLimit) {
1097 STORM_LOG_INFO("Exploration time limit exceeded.");
1098 timeLimitExceeded = true;
1099 }
1100 if (printUpdateStopwatch.getTimeInSeconds() >= 60) {
1101 printUpdateStopwatch.restart();
1102 STORM_LOG_INFO("### " << underApproximation->getCurrentNumberOfMdpStates() << " beliefs in underapproximation MDP"
1103 << " ##### " << underApproximation->getUnexploredStates().size() << " beliefs queued\n");
1104 if (underApproximation->getCurrentNumberOfMdpStates() > heuristicParameters.sizeThreshold && options.useClipping) {
1105 STORM_LOG_INFO("##### Clipping Attempts: " << statistics.nrClippingAttempts.value() << " ##### "
1106 << "Clipped States: " << statistics.nrClippedStates.value() << "\n");
1107 }
1108 }
1109 if (unfoldingControl == UnfoldingControl::Pause && !stateStored) {
1110 underApproximation->storeExplorationState();
1111 stateStored = true;
1112 }
1113 uint64_t currId = underApproximation->exploreNextState();
1114 uint32_t currObservation = beliefManager->getBeliefObservation(currId);
1115 uint64_t addedActions = 0;
1116 bool stateAlreadyExplored = refine && underApproximation->currentStateHasOldBehavior() && !underApproximation->getCurrentStateWasTruncated();
1117 if (!stateAlreadyExplored || timeLimitExceeded) {
1118 fixPoint = false;
1119 }
1120 if (targetObservations.count(beliefManager->getBeliefObservation(currId)) != 0) {
1121 underApproximation->setCurrentStateIsTarget();
1122 underApproximation->addSelfloopTransition();
1123 underApproximation->addChoiceLabelToCurrentState(0, "loop");
1124 } else {
1125 bool stopExploration = false;
1126 bool clipBelief = false;
1127 if (timeLimitExceeded || (options.interactiveUnfolding && unfoldingControl != UnfoldingControl::Run)) {
1128 clipBelief = options.useClipping;
1129 stopExploration = !underApproximation->isMarkedAsGridBelief(currId);
1130 } else if (!stateAlreadyExplored) {
1131 // Check whether we want to explore the state now!
1132 ValueType gap = getGap(underApproximation->getLowerValueBoundAtCurrentState(), underApproximation->getUpperValueBoundAtCurrentState());
1133 if ((gap < heuristicParameters.gapThreshold) || (gap == 0 && options.cutZeroGap)) {
1134 stopExploration = true;
1135 } else if (underApproximation->getCurrentNumberOfMdpStates() >=
1136 heuristicParameters.sizeThreshold /*&& !statistics.beliefMdpDetectedToBeFinite*/) {
1137 clipBelief = options.useClipping;
1138 stopExploration = !underApproximation->isMarkedAsGridBelief(currId);
1139 }
1140 }
1141
1142 if (clipBelief && !underApproximation->isMarkedAsGridBelief(currId)) {
1143 // Use a belief grid as clipping candidates
1144 if (!options.useStateEliminationCutoff) {
1145 bool successfulClip = clipToGridExplicitly(env, currId, computeRewards, beliefManager, underApproximation, 0);
1146 // Set again as the current belief might have been detected to be a grid belief
1147 stopExploration = !underApproximation->isMarkedAsGridBelief(currId);
1148 if (successfulClip) {
1149 addedActions += 1;
1150 }
1151 } else {
1152 clipToGrid(env, currId, computeRewards, min, beliefManager, underApproximation);
1153 addedActions += beliefManager->getBeliefNumberOfChoices(currId);
1154 }
1155 } // end Clipping Procedure
1156
1157 if (stopExploration) {
1158 underApproximation->setCurrentStateIsTruncated();
1159 }
1160 if (options.useStateEliminationCutoff || !stopExploration) {
1161 // Add successor transitions or cut-off transitions when exploration is stopped
1162 uint64_t numActions = beliefManager->getBeliefNumberOfChoices(currId);
1163 if (underApproximation->needsActionAdjustment(numActions)) {
1164 underApproximation->adjustActions(numActions);
1165 }
1166 for (uint64_t action = 0; action < numActions; ++action) {
1167 // Always restore old behavior if available
1168 if (pomdp().hasChoiceLabeling()) {
1169 auto rowIndex = pomdp().getTransitionMatrix().getRowGroupIndices()[beliefManager->getRepresentativeState(currId)];
1170 if (pomdp().getChoiceLabeling().getLabelsOfChoice(rowIndex + action).size() > 0) {
1171 underApproximation->addChoiceLabelToCurrentState(addedActions + action,
1172 *(pomdp().getChoiceLabeling().getLabelsOfChoice(rowIndex + action).begin()));
1173 }
1174 }
1175 if (stateAlreadyExplored) {
1176 underApproximation->restoreOldBehaviorAtCurrentState(action);
1177 } else {
1178 auto truncationProbability = storm::utility::zero<ValueType>();
1179 auto truncationValueBound = storm::utility::zero<ValueType>();
1180 auto successors = beliefManager->expand(env, currId, action);
1181 for (auto const& successor : successors) {
1182 bool added = underApproximation->addTransitionToBelief(addedActions + action, successor.first, successor.second, stopExploration);
1183 if (!added) {
1184 STORM_LOG_ASSERT(stopExploration, "Didn't add a transition although exploration shouldn't be stopped.");
1185 // We did not explore this successor state. Get a bound on the "missing" value
1186 truncationProbability += successor.second;
1187 // Some care has to be taken here: Essentially, we are triangulating a value for the under-approximation out of
1188 // other under-approximation values. In general, this does not yield a sound underapproximation anymore as the
1189 // values might be achieved by different schedulers. However, in our case this is still the case as the
1190 // under-approximation values are based on a single memory-less scheduler.
1191 truncationValueBound += successor.second * (min ? underApproximation->computeUpperValueBoundAtBelief(successor.first)
1192 : underApproximation->computeLowerValueBoundAtBelief(successor.first));
1193 }
1194 }
1195 if (stopExploration) {
1196 if (computeRewards) {
1197 if (truncationValueBound == storm::utility::infinity<ValueType>()) {
1198 underApproximation->addTransitionsToExtraStates(addedActions + action, storm::utility::zero<ValueType>(),
1199 truncationProbability);
1200 } else {
1201 underApproximation->addTransitionsToExtraStates(addedActions + action, truncationProbability);
1202 }
1203 } else {
1204 underApproximation->addTransitionsToExtraStates(addedActions + action, truncationValueBound,
1205 truncationProbability - truncationValueBound);
1206 }
1207 }
1208 if (computeRewards) {
1209 // The truncationValueBound will be added on top of the reward introduced by the current belief state.
1210 if (truncationValueBound != storm::utility::infinity<ValueType>()) {
1211 if (!clipBelief) {
1212 underApproximation->computeRewardAtCurrentState(action, truncationValueBound);
1213 } else {
1214 underApproximation->addRewardToCurrentState(addedActions + action,
1215 beliefManager->getBeliefActionReward(currId, action) + truncationValueBound);
1216 }
1217 }
1218 }
1219 }
1220 }
1221 } else {
1222 for (uint64_t i = 0; i < nrCutoffStrategies && !options.skipHeuristicSchedulers; ++i) {
1223 auto cutOffValue = min ? underApproximation->computeUpperValueBoundForScheduler(currId, i)
1224 : underApproximation->computeLowerValueBoundForScheduler(currId, i);
1225 if (computeRewards) {
1226 if (cutOffValue != storm::utility::infinity<ValueType>()) {
1227 underApproximation->addTransitionsToExtraStates(addedActions, storm::utility::one<ValueType>());
1228 underApproximation->addRewardToCurrentState(addedActions, cutOffValue);
1229 } else {
1230 underApproximation->addTransitionsToExtraStates(addedActions, storm::utility::zero<ValueType>(), storm::utility::one<ValueType>());
1231 }
1232 } else {
1233 underApproximation->addTransitionsToExtraStates(addedActions, cutOffValue, storm::utility::one<ValueType>() - cutOffValue);
1234 }
1235 if (pomdp().hasChoiceLabeling()) {
1236 underApproximation->addChoiceLabelToCurrentState(addedActions, "sched_" + std::to_string(i));
1237 }
1238 addedActions++;
1239 }
1240 if (underApproximation->hasFMSchedulerValues()) {
1241 uint64_t transitionNr = 0;
1242 for (uint64_t i = 0; i < underApproximation->getNrOfMemoryNodesForObservation(currObservation); ++i) {
1243 auto resPair = underApproximation->computeFMSchedulerValueForMemoryNode(currId, i);
1244 ValueType cutOffValue;
1245 if (resPair.first) {
1246 cutOffValue = resPair.second;
1247 } else {
1248 STORM_LOG_DEBUG("Skipped cut-off of belief with ID " << currId << " with finite memory scheduler in memory node " << i
1249 << ". Missing values.");
1250 continue;
1251 }
1252 if (computeRewards) {
1253 if (cutOffValue != storm::utility::infinity<ValueType>()) {
1254 underApproximation->addTransitionsToExtraStates(addedActions + transitionNr, storm::utility::one<ValueType>());
1255 underApproximation->addRewardToCurrentState(addedActions + transitionNr, cutOffValue);
1256 } else {
1257 underApproximation->addTransitionsToExtraStates(addedActions + transitionNr, storm::utility::zero<ValueType>(),
1259 }
1260 } else {
1261 underApproximation->addTransitionsToExtraStates(addedActions + transitionNr, cutOffValue,
1262 storm::utility::one<ValueType>() - cutOffValue);
1263 }
1264 if (pomdp().hasChoiceLabeling()) {
1265 underApproximation->addChoiceLabelToCurrentState(addedActions + transitionNr, "mem_node_" + std::to_string(i));
1266 }
1267 ++transitionNr;
1268 }
1269 }
1270 }
1271 }
1273 break;
1274 }
1275 }
1276
1278 // don't overwrite statistics of a previous, successful computation
1279 if (!statistics.underApproximationStates) {
1280 statistics.underApproximationBuildAborted = true;
1281 statistics.underApproximationStates = underApproximation->getCurrentNumberOfMdpStates();
1282 }
1283 statistics.underApproximationBuildTime.stop();
1284 return false;
1285 }
1286
1287 underApproximation->finishExploration();
1288 statistics.underApproximationBuildTime.stop();
1289 printUpdateStopwatch.stop();
1290 STORM_LOG_INFO("Finished exploring under-approximation MDP.\nStart analysis...\n");
1291 unfoldingStatus = Status::ModelExplorationFinished;
1292 statistics.underApproximationCheckTime.start();
1293 underApproximation->computeValuesOfExploredMdp(env, min ? storm::solver::OptimizationDirection::Minimize : storm::solver::OptimizationDirection::Maximize);
1294 statistics.underApproximationCheckTime.stop();
1295 if (underApproximation->getExploredMdp()->getStateLabeling().getStates("truncated").getNumberOfSetBits() > 0) {
1296 statistics.nrTruncatedStates = underApproximation->getExploredMdp()->getStateLabeling().getStates("truncated").getNumberOfSetBits();
1297 }
1298 // don't overwrite statistics of a previous, successful computation
1299 if (!storm::utility::resources::isTerminate() || !statistics.underApproximationStates) {
1300 statistics.underApproximationStates = underApproximation->getExploredMdp()->getNumberOfStates();
1301 }
1302 return fixPoint;
1303}
1304
1305template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1306void BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::clipToGrid(storm::Environment const& env, uint64_t clippingStateId,
1307 bool computeRewards, bool min,
1308 std::shared_ptr<BeliefManagerType>& beliefManager,
1309 std::shared_ptr<ExplorerType>& beliefExplorer) {
1310 // Add all transitions to states which are already in the MDP, clip all others to a grid
1311 // To make the resulting MDP smaller, we eliminate intermediate successor states when clipping is applied
1312 for (uint64_t action = 0, numActions = beliefManager->getBeliefNumberOfChoices(clippingStateId); action < numActions; ++action) {
1313 auto rewardBound = utility::zero<BeliefValueType>();
1314 auto successors = beliefManager->expand(env, clippingStateId, action);
1315 auto absDelta = utility::zero<BeliefValueType>();
1316 for (auto const& successor : successors) {
1317 // Add transition if successor is in explored space.
1318 // We can directly add the transitions as there is at most one successor for each observation
1319 // Therefore no belief can be clipped to an already added successor
1320 bool added = beliefExplorer->addTransitionToBelief(action, successor.first, successor.second, true);
1321 if (!added) {
1322 // The successor is not in the explored space. Clip it
1323 statistics.nrClippingAttempts = statistics.nrClippingAttempts.value() + 1;
1324 auto clipping =
1325 beliefManager->clipBeliefToGrid(env, successor.first, options.clippingGridRes,
1326 computeRewards ? beliefExplorer->getStateExtremeBoundIsInfinite() : storm::storage::BitVector());
1327 if (clipping.isClippable) {
1328 // The belief is not on the grid and there is a candidate with finite reward
1329 statistics.nrClippedStates = statistics.nrClippedStates.value() + 1;
1330 // Transition probability to candidate is (probability to successor) * (clipping transition probability)
1331 BeliefValueType transitionProb =
1332 (utility::one<BeliefValueType>() - clipping.delta) * utility::convertNumber<BeliefValueType>(successor.second);
1333 beliefExplorer->addTransitionToBelief(action, clipping.targetBelief, utility::convertNumber<BeliefMDPType>(transitionProb), false);
1334 // Collect weighted clipping values
1335 absDelta += clipping.delta * utility::convertNumber<BeliefValueType>(successor.second);
1336 if (computeRewards) {
1337 // collect cumulative reward bounds
1338 auto localRew = utility::zero<BeliefValueType>();
1339 for (auto const& deltaValue : clipping.deltaValues) {
1340 localRew += deltaValue.second *
1341 utility::convertNumber<BeliefValueType>((beliefExplorer->getExtremeValueBoundAtPOMDPState(deltaValue.first)));
1342 }
1343 if (localRew == utility::infinity<BeliefValueType>()) {
1344 STORM_LOG_WARN("Infinite reward in clipping!");
1345 }
1346 rewardBound += localRew * utility::convertNumber<BeliefValueType>(successor.second);
1347 }
1348 } else if (clipping.onGrid) {
1349 // If the belief is not clippable, but on the grid, it may need to be explored, too
1350 beliefExplorer->addTransitionToBelief(action, successor.first, successor.second, false);
1351 } else {
1352 // Otherwise, the reward for all candidates is infinite, clipping does not make sense. Cut it off instead
1353 absDelta += utility::convertNumber<BeliefValueType>(successor.second);
1354 rewardBound += utility::convertNumber<BeliefValueType>(successor.second) *
1355 utility::convertNumber<BeliefValueType>(min ? beliefExplorer->computeUpperValueBoundAtBelief(successor.first)
1356 : beliefExplorer->computeLowerValueBoundAtBelief(successor.first));
1357 }
1358 }
1359 }
1360 // Add the collected clipping transition if necessary
1361 if (absDelta != utility::zero<BeliefValueType>()) {
1362 if (computeRewards) {
1363 if (rewardBound == utility::infinity<BeliefValueType>()) {
1364 // If the reward is infinite, add a transition to the sink state to collect infinite reward
1365 beliefExplorer->addTransitionsToExtraStates(action, utility::zero<BeliefMDPType>(), utility::convertNumber<BeliefMDPType>(absDelta));
1366 } else {
1367 beliefExplorer->addTransitionsToExtraStates(action, utility::convertNumber<BeliefMDPType>(absDelta));
1368 BeliefValueType totalRewardVal = rewardBound / absDelta;
1369 beliefExplorer->addClippingRewardToCurrentState(action, utility::convertNumber<BeliefMDPType>(totalRewardVal));
1370 }
1371 } else {
1372 beliefExplorer->addTransitionsToExtraStates(action, utility::zero<BeliefMDPType>(), utility::convertNumber<BeliefMDPType>(absDelta));
1373 }
1374 }
1375 if (computeRewards) {
1376 beliefExplorer->computeRewardAtCurrentState(action);
1377 }
1378 }
1379}
1380
1381template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1382bool BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::clipToGridExplicitly(storm::Environment const& env,
1383 uint64_t clippingStateId, bool computeRewards,
1384 std::shared_ptr<BeliefManagerType>& beliefManager,
1385 std::shared_ptr<ExplorerType>& beliefExplorer,
1386 uint64_t localActionIndex) {
1387 statistics.nrClippingAttempts = statistics.nrClippingAttempts.value() + 1;
1388 auto clipping = beliefManager->clipBeliefToGrid(env, clippingStateId, options.clippingGridRes,
1389 computeRewards ? beliefExplorer->getStateExtremeBoundIsInfinite() : storm::storage::BitVector());
1390 if (clipping.isClippable) {
1391 // The belief is not on the grid and there is a candidate with finite reward
1392 statistics.nrClippedStates = statistics.nrClippedStates.value() + 1;
1393 // Transition probability to candidate is clipping value
1394 BeliefValueType transitionProb = (utility::one<BeliefValueType>() - clipping.delta);
1395 beliefExplorer->addTransitionToBelief(localActionIndex, clipping.targetBelief, utility::convertNumber<BeliefMDPType>(transitionProb), false);
1396 beliefExplorer->markAsGridBelief(clipping.targetBelief);
1397 if (computeRewards) {
1398 // collect cumulative reward bounds
1399 auto reward = utility::zero<BeliefValueType>();
1400 for (auto const& deltaValue : clipping.deltaValues) {
1401 reward += deltaValue.second * utility::convertNumber<BeliefValueType>((beliefExplorer->getExtremeValueBoundAtPOMDPState(deltaValue.first)));
1402 }
1403 if (reward == utility::infinity<BeliefValueType>()) {
1404 STORM_LOG_WARN("Infinite reward in clipping!");
1405 // If the reward is infinite, add a transition to the sink state to collect infinite reward in our semantics
1406 beliefExplorer->addTransitionsToExtraStates(localActionIndex, utility::zero<BeliefMDPType>(),
1408 } else {
1409 beliefExplorer->addTransitionsToExtraStates(localActionIndex, utility::convertNumber<BeliefMDPType>(clipping.delta));
1410 BeliefValueType totalRewardVal = reward / clipping.delta;
1411 beliefExplorer->addClippingRewardToCurrentState(localActionIndex, utility::convertNumber<BeliefMDPType>(totalRewardVal));
1412 }
1413 } else {
1414 beliefExplorer->addTransitionsToExtraStates(localActionIndex, utility::zero<BeliefMDPType>(),
1416 }
1417 beliefExplorer->addChoiceLabelToCurrentState(localActionIndex, "clip");
1418 return true;
1419 } else {
1420 if (clipping.onGrid) {
1421 // If the belief is not clippable, but on the grid, it may need to be explored, too
1422 beliefExplorer->markAsGridBelief(clippingStateId);
1423 }
1424 }
1425 return false;
1426}
1427
1428template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1429void BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::setUnfoldingControl(
1430 storm::pomdp::modelchecker::BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::UnfoldingControl newUnfoldingControl) {
1431 unfoldingControl = newUnfoldingControl;
1432}
1433
1434template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1436 STORM_LOG_TRACE("PAUSE COMMAND ISSUED");
1437 setUnfoldingControl(UnfoldingControl::Pause);
1438}
1439
1440template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1442 STORM_LOG_TRACE("CONTINUATION COMMAND ISSUED");
1443 setUnfoldingControl(UnfoldingControl::Run);
1444}
1445
1446template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1448 STORM_LOG_TRACE("TERMINATION COMMAND ISSUED");
1449 setUnfoldingControl(UnfoldingControl::Terminate);
1450}
1451
1452template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1456
1457template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1461
1462template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1466
1467template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1468std::shared_ptr<storm::builder::BeliefMdpExplorer<PomdpModelType, BeliefValueType>>
1472
1473template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1475 std::vector<std::vector<std::unordered_map<uint64_t, ValueType>>> valueList) {
1476 interactiveUnderApproximationExplorer->setFMSchedValueList(valueList);
1477}
1478
1479template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1480BeliefValueType BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::rateObservation(
1481 typename ExplorerType::SuccessorObservationInformation const& info, BeliefValueType const& observationResolution, BeliefValueType const& maxResolution) {
1484 if (storm::utility::isOne(n)) {
1485 // If the belief is Dirac, it has to be approximated precisely.
1486 // In this case, we return the best possible rating
1487 return one;
1488 } else {
1489 // Create the rating for this observation at this choice from the given info
1490 auto obsChoiceRating = storm::utility::convertNumber<BeliefValueType, ValueType>(info.maxProbabilityToSuccessorWithObs / info.observationProbability);
1491 // At this point, obsRating is the largest triangulation weight (which ranges from 1/n to 1)
1492 // Normalize the rating so that it ranges from 0 to 1, where
1493 // 0 means that the actual belief lies in the middle of the triangulating simplex (i.e. a "bad" approximation) and 1 means that the belief is precisely
1494 // approximated.
1495 obsChoiceRating = (obsChoiceRating * n - one) / (n - one);
1496 // Scale the ratings with the resolutions, so that low resolutions get a lower rating (and are thus more likely to be refined)
1497 obsChoiceRating *= observationResolution / maxResolution;
1498 return obsChoiceRating;
1499 }
1500}
1501
1502template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1503std::vector<BeliefValueType> BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::getObservationRatings(
1504 std::shared_ptr<ExplorerType> const& overApproximation, std::vector<BeliefValueType> const& observationResolutionVector) {
1505 uint64_t numMdpStates = overApproximation->getExploredMdp()->getNumberOfStates();
1506 auto const& choiceIndices = overApproximation->getExploredMdp()->getNondeterministicChoiceIndices();
1507 BeliefValueType maxResolution = *std::max_element(observationResolutionVector.begin(), observationResolutionVector.end());
1508
1509 std::vector<BeliefValueType> resultingRatings(pomdp().getNrObservations(), storm::utility::one<BeliefValueType>());
1510
1511 std::map<uint32_t, typename ExplorerType::SuccessorObservationInformation> gatheredSuccessorObservations; // Declare here to avoid reallocations
1512 for (uint64_t mdpState = 0; mdpState < numMdpStates; ++mdpState) {
1513 // Check whether this state is reached under an optimal scheduler.
1514 // The heuristic assumes that the remaining states are not relevant for the observation score.
1515 if (overApproximation->stateIsOptimalSchedulerReachable(mdpState)) {
1516 for (uint64_t mdpChoice = choiceIndices[mdpState]; mdpChoice < choiceIndices[mdpState + 1]; ++mdpChoice) {
1517 // Similarly, only optimal actions are relevant
1518 if (overApproximation->actionIsOptimal(mdpChoice)) {
1519 // score the observations for this choice
1520 gatheredSuccessorObservations.clear();
1521 overApproximation->gatherSuccessorObservationInformationAtMdpChoice(mdpChoice, gatheredSuccessorObservations);
1522 for (auto const& obsInfo : gatheredSuccessorObservations) {
1523 auto const& obs = obsInfo.first;
1524 BeliefValueType obsChoiceRating = rateObservation(obsInfo.second, observationResolutionVector[obs], maxResolution);
1525
1526 // The rating of the observation will be the minimum over all choice-based observation ratings
1527 resultingRatings[obs] = std::min(resultingRatings[obs], obsChoiceRating);
1528 }
1529 }
1530 }
1531 }
1532 }
1533 return resultingRatings;
1534}
1535
1536template<typename PomdpModelType, typename BeliefValueType, typename BeliefMDPType>
1537typename PomdpModelType::ValueType BeliefExplorationPomdpModelChecker<PomdpModelType, BeliefValueType, BeliefMDPType>::getGap(
1538 typename PomdpModelType::ValueType const& l, typename PomdpModelType::ValueType const& u) {
1540 "Gap computation currently does not handle negative values.");
1544 } else {
1545 return u;
1546 }
1547 } else if (storm::utility::isZero(u)) {
1548 STORM_LOG_ASSERT(storm::utility::isZero(l), "Upper bound is zero but lower bound is " << l << ".");
1549 return u;
1550 } else {
1551 STORM_LOG_ASSERT(!storm::utility::isInfinity(l), "Lower bound is infinity, but upper bound is " << u << ".");
1552 // get the relative gap
1554 (l + u);
1555 }
1556}
1557
1558/* Template Instantiations */
1559
1561
1563
1565
1567
1568} // namespace modelchecker
1569} // namespace pomdp
1570} // namespace storm
virtual bool isRewardOperatorFormula() const
Definition Formula.cpp:184
void addLabel(std::string const &label)
Adds a new label to the labelings.
bool containsLabel(std::string const &label) const
Checks whether a label is registered within this labeling.
void removeLabel(std::string const &label)
Removes a label from the labelings.
This class represents a (discrete-time) Markov decision process.
Definition Mdp.h:13
std::shared_ptr< storm::models::sparse::Model< ValueType, RewardModelType > > applyScheduler(storm::storage::Scheduler< ValueType > const &scheduler, bool dropUnreachableStates=true, bool preserveModelType=false) const
Applies the given scheduler to this model.
This class manages the labeling of the state space with a number of (atomic) labels.
bool getStateHasLabel(std::string const &label, storm::storage::sparse::state_type state) const
Checks whether a given state is labeled with the given label.
void addLabelToState(std::string const &label, storm::storage::sparse::state_type state)
Adds a label to a given state.
Model checker for checking reachability queries on POMDPs using approximations based on exploration o...
BeliefExplorationPomdpModelChecker(std::shared_ptr< PomdpModelType > pomdp, Options options=Options())
Constructor.
int64_t getStatus()
Get the current status of the interactive unfolding.
bool hasConverged()
Indicates whether the interactive unfolding has coonverged, i.e.
bool isExploring()
Indicates whether the interactive unfolding is currently in the process of exploring the belief MDP.
std::shared_ptr< ExplorerType > getInteractiveBeliefExplorer()
Get a pointer to the belief explorer used in the interactive unfolding.
void setFMSchedValueList(std::vector< std::vector< std::unordered_map< uint64_t, ValueType > > > valueList)
void unfoldInteractively(storm::Environment const &env, std::set< uint32_t > const &targetObservations, bool min, std::optional< std::string > rewardModelName, storm::pomdp::modelchecker::POMDPValueBounds< ValueType > const &valueBounds, Result &result)
Allows to generate an under-approximation using a controllable unfolding.
bool isResultReady()
Indicates whether there is a result after an interactive unfolding was paused.
Result check(storm::Environment const &env, storm::logic::Formula const &formula, storm::Environment const &preProcEnv, std::vector< std::vector< std::unordered_map< uint64_t, ValueType > > > const &additionalUnderApproximationBounds=std::vector< std::vector< std::unordered_map< uint64_t, ValueType > > >())
Performs model checking of the given POMDP with regards to a formula using the previously specified o...
Result getInteractiveResult()
Get the latest saved result obtained by the interactive unfolding.
void printStatisticsToStream(std::ostream &stream) const
Prints statistics of the process to a given output stream.
void precomputeValueBounds(const logic::Formula &formula, storm::Environment const &preProcEnv)
Uses model checking on the underlying MDP to generate values used for cut-offs and for clipping compe...
void continueUnfolding()
Continues a previously paused interactive unfolding.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
uint64_t getNumberOfSetBits() const
Returns the number of bits that are set to true in this bit vector.
size_t size() const
Retrieves the number of bits this bit vector can store.
std::pair< std::shared_ptr< storm::models::sparse::Pomdp< ValueType > >, std::set< uint32_t > > transform(storm::storage::BitVector const &stateSet) const
Ensures that the given set of states is observation closed, potentially, adding new observation(s) A ...
void start()
Start stopwatch (again) and start measuring time.
Definition Stopwatch.cpp:48
void restart()
Reset the stopwatch and immediately start it.
Definition Stopwatch.cpp:59
SecondType getTimeInSeconds() const
Gets the measured time in seconds.
Definition Stopwatch.cpp:13
void stop()
Stop stopwatch and add measured time to total time.
Definition Stopwatch.cpp:42
#define STORM_LOG_INFO(message)
Definition logging.h:27
#define STORM_LOG_WARN(message)
Definition logging.h:28
#define STORM_LOG_DEBUG(message)
Definition logging.h:21
#define STORM_LOG_TRACE(message)
Definition logging.h:15
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:36
#define STORM_LOG_ERROR_COND(cond, message)
Definition macros.h:50
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:28
#define STORM_LOG_INFO_COND(cond, message)
Definition macros.h:43
SFTBDDChecker::ValueType ValueType
FormulaInformation getFormulaInformation(PomdpType const &pomdp, storm::logic::ProbabilityOperatorFormula const &formula)
ValueType getGap(ValueType const &l, ValueType const &u)
bool detectFiniteBeliefMdp(storm::models::sparse::Pomdp< ValueType > const &pomdp, std::optional< storm::storage::BitVector > const &targetStates)
This method tries to detect that the beliefmdp is finite.
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
bool isTerminate()
Check whether the program should terminate (due to some abort signal).
storm::storage::BitVector filter(std::vector< T > const &values, std::function< bool(T const &value)> const &function)
Retrieves a bit vector containing all the indices for which the value at this position makes the give...
Definition vector.h:486
bool isOne(ValueType const &a)
Definition constants.cpp:37
ValueType min(ValueType const &first, ValueType const &second)
bool isZero(ValueType const &a)
Definition constants.cpp:42
ValueType ceil(ValueType const &number)
ValueType abs(ValueType const &number)
ValueType zero()
Definition constants.cpp:24
ValueType infinity()
Definition constants.cpp:29
ValueType one()
Definition constants.cpp:19
bool isInfinity(ValueType const &a)
TargetType convertNumber(SourceType const &number)
static const bool IsExact
Structure for storing values on the POMDP used for cut-offs and clipping.
std::vector< std::vector< std::unordered_map< uint64_t, ValueType > > > fmSchedulerValueList
storm::pomdp::storage::ExtremePOMDPValueBound< ValueType > extremePomdpValueBound
storm::pomdp::storage::PreprocessingPomdpValueBounds< ValueType > trivialPomdpValueBounds
std::unordered_map< std::string, RewardModelType > rewardModels
storm::storage::SparseMatrix< ValueType > transitionMatrix
std::optional< storm::storage::sparse::Valuations > observationValuations
std::optional< storm::models::sparse::ChoiceLabeling > choiceLabeling
storm::models::sparse::StateLabeling stateLabeling
std::optional< std::vector< uint32_t > > observabilityClasses