Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
BeliefManager.cpp
Go to the documentation of this file.
2
9
10namespace storm {
11namespace storage {
12
13template<typename PomdpType, typename BeliefValueType, typename StateType>
17
18template<typename PomdpType, typename BeliefValueType, typename StateType>
20 : dimension(dimension), diff(std::move(diff)) {
21 // Intentionally left empty
22}
23
24template<typename PomdpType, typename BeliefValueType, typename StateType>
26 if (diff != other.diff) {
27 return diff > other.diff;
28 } else {
29 return dimension < other.dimension;
30 }
31}
32
33template<typename PomdpType, typename BeliefValueType, typename StateType>
34bool BeliefManager<PomdpType, BeliefValueType, StateType>::Belief_equal_to::operator()(const BeliefType &lhBelief, const BeliefType &rhBelief) const {
35 return lhBelief == rhBelief;
36}
37
38template<>
39bool BeliefManager<storm::models::sparse::Pomdp<double>, double, uint64_t>::Belief_equal_to::operator()(const BeliefType &lhBelief,
40 const BeliefType &rhBelief) const {
41 // If the sizes are different, we don't have to look inside the belief
42 if (lhBelief.size() != rhBelief.size()) {
43 return false;
44 }
45 // Assumes that beliefs are ordered
46 auto lhIt = lhBelief.begin();
47 auto rhIt = rhBelief.begin();
48 while (lhIt != lhBelief.end() || rhIt != rhBelief.end()) {
49 // Iterate over the entries simultaneously, beliefs not equal if they contain either different states or different values for the same state
50 if (lhIt->first != rhIt->first || std::fabs(lhIt->second - rhIt->second) > 1e-15) {
51 return false;
52 }
53 ++lhIt;
54 ++rhIt;
55 }
56 return lhIt == lhBelief.end() && rhIt == rhBelief.end();
57}
58
59template<typename PomdpType, typename BeliefValueType, typename StateType>
60std::size_t BeliefManager<PomdpType, BeliefValueType, StateType>::BeliefHash::operator()(const BeliefType &belief) const {
61 std::size_t seed = 0;
62 // Assumes that beliefs are ordered
63 for (auto const &entry : belief) {
64 boost::hash_combine(seed, entry.first);
65 boost::hash_combine(seed, entry.second);
66 }
67 return seed;
68}
69
70template<>
71std::size_t BeliefManager<storm::models::sparse::Pomdp<double>, double, uint64_t>::BeliefHash::operator()(const BeliefType &belief) const {
72 std::size_t seed = 0;
73 // Assumes that beliefs are ordered
74 for (auto const &entry : belief) {
75 boost::hash_combine(seed, entry.first);
76 boost::hash_combine(seed, round(storm::utility::convertNumber<double>(entry.second) * 1e15));
77 }
78 return seed;
79}
80
81template<typename PomdpType, typename BeliefValueType, typename StateType>
82BeliefManager<PomdpType, BeliefValueType, StateType>::BeliefManager(PomdpType const &pomdp, BeliefValueType const &precision,
83 TriangulationMode const &triangulationMode)
84 : pomdp(pomdp), cc(precision, false), triangulationMode(triangulationMode) {
85 beliefToIdMap.resize(pomdp.getNrObservations());
86 initialBeliefId = computeInitialBelief();
87}
88
89template<typename PomdpType, typename BeliefValueType, typename StateType>
90void BeliefManager<PomdpType, BeliefValueType, StateType>::setRewardModel(std::optional<std::string> rewardModelName) {
91 if (rewardModelName) {
92 auto const &rewardModel = pomdp.getRewardModel(rewardModelName.value());
93 pomdpActionRewardVector = rewardModel.getTotalRewardVector(pomdp.getTransitionMatrix());
94 } else {
95 setRewardModel(pomdp.getUniqueRewardModelName());
96 }
97}
98
99template<typename PomdpType, typename BeliefValueType, typename StateType>
101 pomdpActionRewardVector.clear();
102}
103
104template<typename PomdpType, typename BeliefValueType, typename StateType>
108
109template<typename PomdpType, typename BeliefValueType, typename StateType>
111 return isEqual(getBelief(first), getBelief(second));
112}
113
114template<typename PomdpType, typename BeliefValueType, typename StateType>
116 return toString(getBelief(beliefId));
117}
118
119template<typename PomdpType, typename BeliefValueType, typename StateType>
121 std::stringstream str;
122 str << "(\n";
123 for (uint64_t i = 0; i < t.size(); ++i) {
124 str << "\t" << t.weights[i] << " * \t" << toString(getBelief(t.gridPoints[i])) << "\n";
125 }
126 str << ")\n";
127 return str.str();
128}
129
130template<typename PomdpType, typename BeliefValueType, typename StateType>
132 BeliefId const &beliefId, std::vector<ValueType> const &summands) {
133 auto result = storm::utility::zero<ValueType>();
134 for (auto const &entry : getBelief(beliefId)) {
135 result += storm::utility::convertNumber<ValueType>(entry.second) * storm::utility::convertNumber<ValueType>(summands.at(entry.first));
136 }
137 return result;
138}
139
140template<typename PomdpType, typename BeliefValueType, typename StateType>
141std::pair<bool, typename BeliefManager<PomdpType, BeliefValueType, StateType>::ValueType> BeliefManager<PomdpType, BeliefValueType, StateType>::getWeightedSum(
142 BeliefId const &beliefId, std::unordered_map<StateType, ValueType> const &summands) {
143 bool successful = true;
144 auto result = storm::utility::zero<ValueType>();
145 for (auto const &entry : getBelief(beliefId)) {
146 auto probIter = summands.find(entry.first);
147 if (probIter != summands.end()) {
148 result += storm::utility::convertNumber<ValueType>(entry.second) * storm::utility::convertNumber<ValueType>(summands.at(entry.first));
149 } else {
150 successful = false;
151 break;
152 }
153 }
154 return {successful, result};
155}
156
157template<typename PomdpType, typename BeliefValueType, typename StateType>
161
162template<typename PomdpType, typename BeliefValueType, typename StateType>
164 BeliefId const &beliefId, uint64_t const &localActionIndex) const {
165 auto const &belief = getBelief(beliefId);
166 STORM_LOG_ASSERT(!pomdpActionRewardVector.empty(), "Requested a reward although no reward model was specified.");
167 auto result = storm::utility::zero<ValueType>();
168 auto const &choiceIndices = pomdp.getTransitionMatrix().getRowGroupIndices();
169 for (auto const &entry : belief) {
170 uint64_t choiceIndex = choiceIndices[entry.first] + localActionIndex;
171 STORM_LOG_ASSERT(choiceIndex < choiceIndices[entry.first + 1], "Invalid local action index.");
172 STORM_LOG_ASSERT(choiceIndex < pomdpActionRewardVector.size(), "Invalid choice index.");
173 result += storm::utility::convertNumber<ValueType>(entry.second) * pomdpActionRewardVector[choiceIndex];
174 }
175 return result;
176}
177
178template<typename PomdpType, typename BeliefValueType, typename StateType>
180 return getBeliefObservation(getBelief(beliefId));
181}
182
183template<typename PomdpType, typename BeliefValueType, typename StateType>
185 auto const &belief = getBelief(beliefId);
186 return pomdp.getNumberOfChoices(belief.begin()->first);
187}
188
189template<typename PomdpType, typename BeliefValueType, typename StateType>
191 BeliefId beliefId, BeliefValueType resolution) {
192 return triangulateBelief(getBelief(beliefId), resolution);
193}
194
195template<typename PomdpType, typename BeliefValueType, typename StateType>
196template<typename DistributionType>
197void BeliefManager<PomdpType, BeliefValueType, StateType>::addToDistribution(DistributionType &distr, StateType const &state, BeliefValueType const &value) {
198 auto insertionRes = distr.emplace(state, value);
199 if (!insertionRes.second) {
200 insertionRes.first->second += value;
201 }
202}
203
204template<typename PomdpType, typename BeliefValueType, typename StateType>
205template<typename DistributionType>
207 if (distr.size() == 1 && cc.isEqual(distr.begin()->second, storm::utility::one<BeliefValueType>())) {
208 // If the distribution consists of only one entry and its value is sufficiently close to 1, make it exactly 1 to avoid numerical problems
209 distr.begin()->second = storm::utility::one<BeliefValueType>();
210 }
211}
212
213template<typename PomdpType, typename BeliefValueType, typename StateType>
215 auto const &belief = getBelief(beliefId);
216 for (auto const &entry : belief) {
217 support.insert(entry.first);
218 }
219}
220
221template<typename PomdpType, typename BeliefValueType, typename StateType>
225
226template<typename PomdpType, typename BeliefValueType, typename StateType>
227std::vector<std::pair<typename BeliefManager<PomdpType, BeliefValueType, StateType>::BeliefId,
230 std::vector<BeliefValueType> const &observationResolutions) {
231 return expandInternal(beliefId, actionIndex, observationResolutions);
232}
233
234template<typename PomdpType, typename BeliefValueType, typename StateType>
235std::vector<std::pair<typename BeliefManager<PomdpType, BeliefValueType, StateType>::BeliefId,
238 std::vector<uint64_t> const &observationResolutions) {
239 return expandInternal(beliefId, actionIndex, std::nullopt, observationResolutions);
240}
241
242template<typename PomdpType, typename BeliefValueType, typename StateType>
243std::vector<std::pair<typename BeliefManager<PomdpType, BeliefValueType, StateType>::BeliefId,
246 return expandInternal(beliefId, actionIndex);
247}
248
249template<typename PomdpType, typename BeliefValueType, typename StateType>
251 BeliefId const &id) const {
252 STORM_LOG_ASSERT(id != noId(), "Tried to get a non-existent belief.");
253 STORM_LOG_ASSERT(id < getNumberOfBeliefIds(), "Belief index " << id << " is out of range.");
254 return beliefs[id];
255}
256
257template<typename PomdpType, typename BeliefValueType, typename StateType>
258typename BeliefManager<PomdpType, BeliefValueType, StateType>::BeliefId BeliefManager<PomdpType, BeliefValueType, StateType>::getId(
259 BeliefType const &belief) const {
260 uint32_t obs = getBeliefObservation(belief);
261 STORM_LOG_ASSERT(obs < beliefToIdMap.size(), "Belief has unknown observation.");
262 auto idIt = beliefToIdMap[obs].find(belief);
263 STORM_LOG_ASSERT(idIt != beliefToIdMap[obs].end(), "Unknown Belief.");
264 return idIt->second;
265}
266
267template<typename PomdpType, typename BeliefValueType, typename StateType>
268std::string BeliefManager<PomdpType, BeliefValueType, StateType>::toString(BeliefType const &belief) const {
269 std::stringstream str;
270 str << "{ ";
271 bool first = true;
272 for (auto const &entry : belief) {
273 if (first) {
274 first = false;
275 } else {
276 str << ", ";
277 }
278 str << entry.first << ": " << std::setprecision(std::numeric_limits<double>::max_digits10 + 1) << entry.second;
279 }
280 str << " }";
281 return str.str();
282}
283
284template<typename PomdpType, typename BeliefValueType, typename StateType>
285bool BeliefManager<PomdpType, BeliefValueType, StateType>::isEqual(BeliefType const &first, BeliefType const &second) const {
286 if (first.size() != second.size()) {
287 return false;
288 }
289 auto secondIt = second.begin();
290 for (auto const &firstEntry : first) {
291 if (firstEntry.first != secondIt->first) {
292 return false;
293 }
294 if (!cc.isEqual(firstEntry.second, secondIt->second)) {
295 return false;
296 }
297 ++secondIt;
298 }
299 return true;
300}
301
302template<typename PomdpType, typename BeliefValueType, typename StateType>
303bool BeliefManager<PomdpType, BeliefValueType, StateType>::assertBelief(BeliefType const &belief) const {
304 auto sum = storm::utility::zero<BeliefValueType>();
305 std::optional<uint32_t> observation;
306 for (auto const &entry : belief) {
307 if (entry.first >= pomdp.getNumberOfStates()) {
308 STORM_LOG_ERROR("Belief does refer to non-existing pomdp state " << entry.first << ".");
309 return false;
310 }
311 uint64_t entryObservation = pomdp.getObservation(entry.first);
312 if (observation) {
313 if (observation.value() != entryObservation) {
314 STORM_LOG_ERROR("Beliefsupport contains different observations.");
315 return false;
316 }
317 } else {
318 observation = entryObservation;
319 }
320 // Don't use cc for these checks, because computations with zero are usually fine
321 if (storm::utility::isZero(entry.second)) {
322 // We assume that beliefs only consider their support.
323 STORM_LOG_ERROR("Zero belief probability.");
324 return false;
325 }
326 if (entry.second < storm::utility::zero<BeliefValueType>()) {
327 STORM_LOG_ERROR("Negative belief probability.");
328 return false;
329 }
330 if (cc.isLess(storm::utility::one<BeliefValueType>(), entry.second)) {
331 STORM_LOG_ERROR("Belief probability greater than one.");
332 return false;
333 }
334 sum += entry.second;
335 }
336 if (!cc.isOne(sum)) {
337 STORM_LOG_ERROR("Belief does not sum up to one. (" << sum << " instead).");
338 return false;
339 }
340 return true;
341}
342
343template<typename PomdpType, typename BeliefValueType, typename StateType>
344bool BeliefManager<PomdpType, BeliefValueType, StateType>::assertTriangulation(BeliefType const &belief, Triangulation const &triangulation) const {
345 if (triangulation.weights.size() != triangulation.gridPoints.size()) {
346 STORM_LOG_ERROR("Number of weights and points in triangulation does not match.");
347 return false;
348 }
349 if (triangulation.size() == 0) {
350 STORM_LOG_ERROR("Empty triangulation.");
351 return false;
352 }
353 BeliefType triangulatedBelief;
354 auto weightSum = storm::utility::zero<BeliefValueType>();
355 for (uint64_t i = 0; i < triangulation.weights.size(); ++i) {
356 if (cc.isZero(triangulation.weights[i])) {
357 STORM_LOG_ERROR("Zero weight in triangulation.");
358 return false;
359 }
360 if (cc.isLess(triangulation.weights[i], storm::utility::zero<BeliefValueType>())) {
361 STORM_LOG_ERROR("Negative weight in triangulation.");
362 return false;
363 }
364 if (cc.isLess(storm::utility::one<BeliefValueType>(), triangulation.weights[i])) {
365 STORM_LOG_ERROR("Weight greater than one in triangulation.");
366 }
367 weightSum += triangulation.weights[i];
368 BeliefType const &gridPoint = getBelief(triangulation.gridPoints[i]);
369 for (auto const &pointEntry : gridPoint) {
370 BeliefValueType &triangulatedValue = triangulatedBelief.emplace(pointEntry.first, storm::utility::zero<BeliefValueType>()).first->second;
371 triangulatedValue += triangulation.weights[i] * pointEntry.second;
372 }
373 }
374 if (!cc.isOne(weightSum)) {
375 STORM_LOG_ERROR("Triangulation weights do not sum up to one.");
376 return false;
377 }
378 if (!assertBelief(triangulatedBelief)) {
379 STORM_LOG_ERROR("Triangulated belief is not a belief.");
380 }
381 if (!isEqual(belief, triangulatedBelief)) {
382 STORM_LOG_ERROR("Belief:\n\t" << toString(belief) << "\ndoes not match triangulated belief:\n\t" << toString(triangulatedBelief) << ".");
383 return false;
384 }
385 return true;
386}
387
388template<typename PomdpType, typename BeliefValueType, typename StateType>
390 STORM_LOG_ASSERT(assertBelief(belief), "Invalid belief.");
391 return pomdp.getObservation(belief.begin()->first);
392}
393
394template<typename PomdpType, typename BeliefValueType, typename StateType>
395void BeliefManager<PomdpType, BeliefValueType, StateType>::triangulateBeliefFreudenthal(BeliefType const &belief, BeliefValueType const &resolution,
396 Triangulation &result) {
397 STORM_LOG_ASSERT(resolution != 0, "Invalid resolution: 0");
398 STORM_LOG_ASSERT(storm::utility::isInteger(resolution), "Expected an integer resolution");
399 StateType numEntries = belief.size();
400 // This is the Freudenthal Triangulation as described in Lovejoy (a whole lotta math)
401 // Probabilities will be triangulated to values in 0/N, 1/N, 2/N, ..., N/N
402 // Variable names are mostly based on the paper
403 // However, we speed this up a little by exploiting that belief states usually have sparse support (i.e. numEntries is much smaller than
404 // pomdp.getNumberOfStates()). Initialize diffs and the first row of the 'qs' matrix (aka v)
405 std::set<FreudenthalDiff, std::greater<>> sorted_diffs; // d (and p?) in the paper
406 std::vector<BeliefValueType> qsRow; // Row of the 'qs' matrix from the paper (initially corresponds to v
407 qsRow.reserve(numEntries);
408 std::vector<StateType> toOriginalIndicesMap; // Maps 'local' indices to the original pomdp state indices
409 toOriginalIndicesMap.reserve(numEntries);
410 BeliefValueType x = resolution;
411 for (auto const &entry : belief) {
412 qsRow.push_back(storm::utility::floor(x)); // v
413 sorted_diffs.emplace(toOriginalIndicesMap.size(), x - qsRow.back()); // x-v
414 toOriginalIndicesMap.push_back(entry.first);
415 x -= entry.second * resolution;
416 }
417 // Insert a dummy 0 column in the qs matrix so the loops below are a bit simpler
418 qsRow.push_back(storm::utility::zero<BeliefValueType>());
419
420 result.weights.reserve(numEntries);
421 result.gridPoints.reserve(numEntries);
422 auto currentSortedDiff = sorted_diffs.begin();
423 auto previousSortedDiff = sorted_diffs.end();
424 --previousSortedDiff;
425 for (StateType i = 0; i < numEntries; ++i) {
426 // Compute the weight for the grid points
427 BeliefValueType weight = previousSortedDiff->diff - currentSortedDiff->diff;
428 if (i == 0) {
429 // The first weight is a bit different
430 weight += storm::utility::one<BeliefValueType>();
431 } else {
432 // 'compute' the next row of the qs matrix
433 qsRow[previousSortedDiff->dimension] += storm::utility::one<BeliefValueType>();
434 }
435 if (!cc.isZero(weight)) {
436 result.weights.push_back(weight);
437 // Compute the grid point
438 BeliefType gridPoint;
439 for (StateType j = 0; j < numEntries; ++j) {
440 BeliefValueType gridPointEntry = qsRow[j] - qsRow[j + 1];
441 if (!cc.isZero(gridPointEntry)) {
442 gridPoint[toOriginalIndicesMap[j]] = gridPointEntry / resolution;
443 }
444 }
445 result.gridPoints.push_back(getOrAddBeliefId(gridPoint));
446 }
447 previousSortedDiff = currentSortedDiff++;
448 }
449}
450
451template<typename PomdpType, typename BeliefValueType, typename StateType>
452void BeliefManager<PomdpType, BeliefValueType, StateType>::triangulateBeliefDynamic(BeliefType const &belief, BeliefValueType const &resolution,
453 Triangulation &result) {
454 // Find the best resolution for this belief, i.e., N such that the largest distance between one of the belief values to a value in {i/N | 0 ≤ i ≤ N} is
455 // minimal
456 STORM_LOG_ASSERT(storm::utility::isInteger(resolution), "Expected an integer resolution");
457 BeliefValueType finalResolution = resolution;
458 uint64_t finalResolutionMisses = belief.size() + 1;
459 // We don't need to check resolutions that are smaller than the maximal resolution divided by 2 (as we already checked multiples of these)
460 for (BeliefValueType currResolution = resolution; currResolution > resolution / 2; --currResolution) {
461 uint64_t currResMisses = 0;
462 bool continueWithNextResolution = false;
463 for (auto const &belEntry : belief) {
464 BeliefValueType product = belEntry.second * currResolution;
465 if (!cc.isZero(product - storm::utility::round(product))) {
466 ++currResMisses;
467 if (currResMisses >= finalResolutionMisses) {
468 // This resolution is not better than a previous resolution
469 continueWithNextResolution = true;
470 break;
471 }
472 }
473 }
474 if (!continueWithNextResolution) {
475 STORM_LOG_ASSERT(currResMisses < finalResolutionMisses, "Distance for this resolution should not be larger than a previously checked one.");
476 finalResolution = currResolution;
477 finalResolutionMisses = currResMisses;
478 if (currResMisses == 0) {
479 break;
480 }
481 }
482 }
483
484 STORM_LOG_TRACE("Picking resolution " << finalResolution << " for belief " << toString(belief));
485
486 // do standard freudenthal with the found resolution
487 triangulateBeliefFreudenthal(belief, finalResolution, result);
488}
489
490template<typename PomdpType, typename BeliefValueType, typename StateType>
491typename BeliefManager<PomdpType, BeliefValueType, StateType>::Triangulation BeliefManager<PomdpType, BeliefValueType, StateType>::triangulateBelief(
492 BeliefType const &belief, BeliefValueType const &resolution) {
493 STORM_LOG_ASSERT(assertBelief(belief), "Input belief for triangulation is not valid.");
494 Triangulation result;
495 // Quickly triangulate Dirac beliefs
496 if (belief.size() == 1u) {
497 result.weights.push_back(storm::utility::one<BeliefValueType>());
498 result.gridPoints.push_back(getOrAddBeliefId(belief));
499 } else {
500 auto ceiledResolution = storm::utility::ceil<BeliefValueType>(resolution);
501 switch (triangulationMode) {
502 case TriangulationMode::Static:
503 triangulateBeliefFreudenthal(belief, ceiledResolution, result);
504 break;
505 case TriangulationMode::Dynamic:
506 triangulateBeliefDynamic(belief, ceiledResolution, result);
507 break;
508 default:
509 STORM_LOG_ASSERT(false, "Invalid triangulation mode.");
510 }
511 }
512 STORM_LOG_ASSERT(assertTriangulation(belief, result), "Incorrect triangulation: " << toString(result));
513 return result;
514}
515
516template<typename PomdpType, typename BeliefValueType, typename StateType>
517std::vector<std::pair<typename BeliefManager<PomdpType, BeliefValueType, StateType>::BeliefId,
519BeliefManager<PomdpType, BeliefValueType, StateType>::expandInternal(BeliefId const &beliefId, uint64_t actionIndex,
520 std::optional<std::vector<BeliefValueType>> const &observationTriangulationResolutions,
521 std::optional<std::vector<uint64_t>> const &observationGridClippingResolutions) {
522 std::vector<std::pair<BeliefId, ValueType>> destinations;
523
524 BeliefType belief = getBelief(beliefId);
525
526 // Find the probability we go to each observation
527 BeliefType successorObs; // This is actually not a belief but has the same type
528 for (auto const &pointEntry : belief) {
529 uint64_t state = pointEntry.first;
530 for (auto const &pomdpTransition : pomdp.getTransitionMatrix().getRow(state, actionIndex)) {
531 if (!storm::utility::isZero(pomdpTransition.getValue())) {
532 auto obs = pomdp.getObservation(pomdpTransition.getColumn());
533 addToDistribution(successorObs, obs, pointEntry.second * storm::utility::convertNumber<BeliefValueType>(pomdpTransition.getValue()));
534 }
535 }
536 }
537 adjustDistribution(successorObs);
538
539 // Now for each successor observation we find and potentially triangulate the successor belief
540 for (auto const &successor : successorObs) {
541 BeliefType successorBelief;
542 for (auto const &pointEntry : belief) {
543 uint64_t state = pointEntry.first;
544 for (auto const &pomdpTransition : pomdp.getTransitionMatrix().getRow(state, actionIndex)) {
545 if (pomdp.getObservation(pomdpTransition.getColumn()) == successor.first) {
546 BeliefValueType prob = pointEntry.second * storm::utility::convertNumber<BeliefValueType>(pomdpTransition.getValue()) / successor.second;
547 addToDistribution(successorBelief, pomdpTransition.getColumn(), prob);
548 }
549 }
550 }
551 adjustDistribution(successorBelief);
552 STORM_LOG_ASSERT(assertBelief(successorBelief), "Invalid successor belief.");
553
554 // Insert the destination. We know that destinations have to be disjoint since they have different observations
555 if (observationTriangulationResolutions) {
556 Triangulation triangulation = triangulateBelief(successorBelief, observationTriangulationResolutions.value()[successor.first]);
557 for (size_t j = 0; j < triangulation.size(); ++j) {
558 // Here we additionally assume that triangulation.gridPoints does not contain the same point multiple times
559 BeliefValueType a = triangulation.weights[j] * successor.second;
560 destinations.emplace_back(triangulation.gridPoints[j], storm::utility::convertNumber<ValueType>(a));
561 }
562 } else if (observationGridClippingResolutions) {
563 BeliefClipping clipping = clipBeliefToGrid(successorBelief, observationGridClippingResolutions.value()[successor.first],
564 storm::storage::BitVector(pomdp.getNumberOfStates()));
565 if (clipping.isClippable) {
566 BeliefValueType a = (storm::utility::one<BeliefValueType>() - clipping.delta) * successor.second;
567 destinations.emplace_back(clipping.targetBelief, storm::utility::convertNumber<ValueType>(a));
568 } else {
569 // Belief on Grid
570 destinations.emplace_back(getOrAddBeliefId(successorBelief), storm::utility::convertNumber<ValueType>(successor.second));
571 }
572 } else {
573 destinations.emplace_back(getOrAddBeliefId(successorBelief), storm::utility::convertNumber<ValueType>(successor.second));
574 }
575 }
576
577 return destinations;
578}
579
580template<typename PomdpType, typename BeliefValueType, typename StateType>
582 BeliefId const &beliefId, uint64_t resolution, storm::storage::BitVector isInfinite) {
583 auto res = clipBeliefToGrid(getBelief(beliefId), resolution, isInfinite);
584 res.startingBelief = beliefId;
585 return res;
586}
587
588template<typename PomdpType, typename BeliefValueType, typename StateType>
590 BeliefType const &belief, uint64_t resolution, const storm::storage::BitVector &isInfinite) {
591 uint32_t obs = getBeliefObservation(belief);
592 STORM_LOG_ASSERT(obs < beliefToIdMap.size(), "Belief has unknown observation.");
593 if (!lpSolver) {
594 lpSolver = storm::utility::solver::getLpSolver<BeliefValueType>("POMDP LP Solver");
595 } else {
596 lpSolver->pop();
597 }
598 lpSolver->push();
599
600 std::vector<BeliefValueType> helper(belief.size(), storm::utility::zero<BeliefValueType>());
601 helper[0] = storm::utility::convertNumber<BeliefValueType>(resolution);
602 bool done = false;
603 // Set-up Variables
604 std::vector<storm::expressions::Expression> decisionVariables;
605 // Add variable for the clipping value, it is to be minimized
606 auto bigDelta = lpSolver->addBoundedContinuousVariable("D", storm::utility::zero<BeliefValueType>(), storm::utility::one<BeliefValueType>(),
607 storm::utility::one<BeliefValueType>());
608 // State clipping values
609 std::vector<storm::expressions::Expression> deltas;
610 uint64_t i = 0;
611 for (auto const &state : belief) {
612 // This is a quite dirty fix to enable GLPK for the TACAS '22 implementation without substantially changing the implementation for Gurobi.
613 if (typeid(*lpSolver) == typeid(storm::solver::GlpkLpSolver<ValueType>) && !isInfinite.empty()) {
614 if (isInfinite[state.first]) {
615 auto localDelta = lpSolver->addBoundedContinuousVariable("d_" + std::to_string(i), storm::utility::zero<BeliefValueType>(), state.second);
616 auto deltaExpr = storm::expressions::Expression(localDelta);
617 deltas.push_back(deltaExpr);
618 lpSolver->addConstraint("state_val_inf_" + std::to_string(i), deltaExpr == lpSolver->getConstant(storm::utility::zero<BeliefValueType>()));
619 }
620 } else {
621 BeliefValueType bound = state.second;
622 if (!isInfinite.empty()) {
623 bound = isInfinite[state.first] ? storm::utility::zero<BeliefValueType>() : state.second;
624 }
625 auto localDelta = lpSolver->addBoundedContinuousVariable("d_" + std::to_string(i), storm::utility::zero<BeliefValueType>(), bound);
626 deltas.push_back(storm::expressions::Expression(localDelta));
627 }
628 ++i;
629 }
630 lpSolver->update();
631 std::vector<BeliefType> gridCandidates;
632 while (!done) {
633 BeliefType candidate;
634 auto belIter = belief.begin();
635 for (uint64_t j = 0; j < belief.size() - 1; ++j) {
636 if (!cc.isEqual(helper[j] - helper[j + 1], storm::utility::zero<BeliefValueType>())) {
637 candidate[belIter->first] = (helper[j] - helper[j + 1]) / storm::utility::convertNumber<BeliefValueType>(resolution);
638 }
639 belIter++;
640 }
641 if (!cc.isEqual(helper[belief.size() - 1], storm::utility::zero<BeliefValueType>())) {
642 candidate[belIter->first] = helper[belief.size() - 1] / storm::utility::convertNumber<BeliefValueType>(resolution);
643 }
644 if (isEqual(candidate, belief)) {
645 // TODO Improve handling of successors which are already on the grid
646 return BeliefClipping{false, noId(), noId(), storm::utility::zero<BeliefValueType>(), {}, true};
647 } else {
648 gridCandidates.push_back(candidate);
649
650 // Add variables a_j
651 auto decisionVar = lpSolver->addBinaryVariable("a_" + std::to_string(gridCandidates.size() - 1));
652 decisionVariables.push_back(storm::expressions::Expression(decisionVar));
653 lpSolver->update();
654
655 i = 0;
656 for (auto const &state : belief) {
657 // Add the constraint to describe the transformation between the state values in the beliefs
658 // d_i
659 storm::expressions::Expression leftSide = deltas[i];
660 storm::expressions::Expression targetValue = lpSolver->getConstant(candidate[i]);
661 if (candidate.find(state.first) != candidate.end()) {
662 targetValue = lpSolver->getConstant(candidate.at(state.first));
663 } else {
664 targetValue = lpSolver->getConstant(storm::utility::zero<BeliefValueType>());
665 }
666
667 // b(s_i) - b_j(s_i) + D * b_j(s_i) - 1 + a_j
669 lpSolver->getConstant(state.second) - targetValue + storm::expressions::Expression(bigDelta) * targetValue -
670 lpSolver->getConstant(storm::utility::one<BeliefValueType>()) + storm::expressions::Expression(decisionVar);
671
672 // Add left >= right
673 lpSolver->addConstraint("state_eq_" + std::to_string(i) + "_" + std::to_string(gridCandidates.size() - 1), leftSide >= rightSide);
674 ++i;
675 lpSolver->update();
676 }
677 }
678 if (helper.back() == storm::utility::convertNumber<BeliefValueType>(resolution)) {
679 // If the last entry of helper is the gridResolution, we have enumerated all necessary distributions
680 done = true;
681 } else {
682 // Update helper by finding the index to increment
683 auto helperIt = helper.end() - 1;
684 while (*helperIt == *(helperIt - 1)) {
685 --helperIt;
686 }
687 STORM_LOG_ASSERT(helperIt != helper.begin(), "Error in grid clipping - index wrong");
688 // Increment the value at the index
689 *helperIt += 1;
690 // Reset all indices greater than the changed one to 0
691 ++helperIt;
692 while (helperIt != helper.end()) {
693 *helperIt = 0;
694 ++helperIt;
695 }
696 }
697 }
698
699 // Only one target belief should be chosen
700 lpSolver->addConstraint("choice", storm::expressions::sum(decisionVariables) == lpSolver->getConstant(storm::utility::one<BeliefValueType>()));
701 // Link D and d_i
702 lpSolver->addConstraint("delta", storm::expressions::Expression(bigDelta) == storm::expressions::sum(deltas));
703 // Exclude D = 0 (self-loop)
704 lpSolver->addConstraint("not_zero", storm::expressions::Expression(bigDelta) > lpSolver->getConstant(storm::utility::zero<BeliefValueType>()));
705
706 lpSolver->update();
707
708 lpSolver->optimize();
709 // Get the optimal belief for clipping
710 BeliefId targetBelief = noId();
711 // Not a belief but has the same type
712 BeliefType deltaValues;
713 auto optDelta = storm::utility::zero<BeliefValueType>();
714 auto deltaSum = storm::utility::zero<BeliefValueType>();
715 if (lpSolver->isOptimal()) {
716 optDelta = lpSolver->getObjectiveValue();
717 for (uint64_t dist = 0; dist < gridCandidates.size(); ++dist) {
718 if (lpSolver->getBinaryValue(lpSolver->getManager().getVariable("a_" + std::to_string(dist)))) {
719 targetBelief = getOrAddBeliefId(gridCandidates[dist]);
720 break;
721 }
722 }
723 i = 0;
724 for (auto const &state : belief) {
725 auto val = lpSolver->getContinuousValue(lpSolver->getManager().getVariable("d_" + std::to_string(i)));
726 if (cc.isLess(storm::utility::zero<BeliefValueType>(), val)) {
727 deltaValues.emplace(state.first, val);
728 deltaSum += val;
729 }
730 ++i;
731 }
732
733 if (cc.isEqual(optDelta, storm::utility::zero<BeliefValueType>())) {
734 // If we get an optimal value of 0, the LP solver considers two beliefs to be equal, possibly due to numerical instability
735 // For a sound result, we consider the state to not be clippable
736 STORM_LOG_WARN("LP solver returned an optimal value of 0. This should definitely not happen when using a grid");
737 STORM_LOG_WARN("Origin" << toString(belief));
738 STORM_LOG_WARN("Target [Bel " << targetBelief << "] " << toString(targetBelief));
739 return BeliefClipping{false, noId(), noId(), storm::utility::zero<BeliefValueType>(), {}, false};
740 }
741
742 if (optDelta == storm::utility::one<BeliefValueType>()) {
743 STORM_LOG_WARN("LP solver returned an optimal value of 1. Sum of state clipping values is " << deltaSum);
744 // If we get an optimal value of 1, we cannot clip the belief as by definition this would correspond to a division by 0.
745 STORM_LOG_DEBUG("Origin" << toString(belief));
746 STORM_LOG_DEBUG("Target [Bel " << targetBelief << "] " << toString(targetBelief));
747
748 if (deltaSum == storm::utility::one<BeliefValueType>()) {
749 return BeliefClipping{false, noId(), noId(), storm::utility::zero<BeliefValueType>(), {}, false};
750 }
751 optDelta = deltaSum;
752 }
753 }
754 return BeliefClipping{lpSolver->isOptimal(), noId(), targetBelief, optDelta, deltaValues, false};
755}
756
757template<typename PomdpType, typename BeliefValueType, typename StateType>
758typename BeliefManager<PomdpType, BeliefValueType, StateType>::BeliefId BeliefManager<PomdpType, BeliefValueType, StateType>::computeInitialBelief() {
759 STORM_LOG_ASSERT(pomdp.getInitialStates().getNumberOfSetBits() < 2, "POMDP contains more than one initial state");
760 STORM_LOG_ASSERT(pomdp.getInitialStates().getNumberOfSetBits() == 1, "POMDP does not contain an initial state");
761 BeliefType belief;
762 belief[*pomdp.getInitialStates().begin()] = storm::utility::one<BeliefValueType>();
763
764 STORM_LOG_ASSERT(assertBelief(belief), "Invalid initial belief.");
765 return getOrAddBeliefId(belief);
766}
767
768template<typename PomdpType, typename BeliefValueType, typename StateType>
769typename BeliefManager<PomdpType, BeliefValueType, StateType>::BeliefId BeliefManager<PomdpType, BeliefValueType, StateType>::getOrAddBeliefId(
770 BeliefType const &belief) {
771 uint32_t obs = getBeliefObservation(belief);
772 STORM_LOG_ASSERT(obs < beliefToIdMap.size(), "Belief has unknown observation.");
773 auto insertioRes = beliefToIdMap[obs].emplace(belief, beliefs.size());
774 if (insertioRes.second) {
775 // There actually was an insertion, so add the new belief
776 STORM_LOG_TRACE("Add Belief " << beliefs.size() << " " << toString(belief));
777 beliefs.push_back(belief);
778 }
779 // Return the id
780 return insertioRes.first->second;
781}
782template<typename PomdpType, typename BeliefValueType, typename StateType>
784 return getBelief(beliefId).begin()->first;
785}
786
787template<typename PomdpType, typename BeliefValueType, typename StateType>
789 if (pomdp.hasObservationValuations()) {
790 return pomdp.getObservationValuations().getStateInfo(getBeliefObservation(beliefId));
791 } else {
792 STORM_LOG_TRACE("Cannot get observation labels as no observation valuation has been defined for the POMDP. Return empty label instead.");
793 return "";
794 }
795}
796
797template<typename PomdpType, typename BeliefValueType, typename StateType>
798std::vector<BeliefValueType> BeliefManager<PomdpType, BeliefValueType, StateType>::getBeliefAsVector(BeliefId const &beliefId) {
799 return getBeliefAsVector(getBelief(beliefId));
800}
801
802template<typename PomdpType, typename BeliefValueType, typename StateType>
803std::vector<BeliefValueType> BeliefManager<PomdpType, BeliefValueType, StateType>::getBeliefAsVector(const BeliefType &belief) {
804 std::vector<BeliefValueType> res(pomdp.getNumberOfStates(), storm::utility::zero<BeliefValueType>());
805 for (auto const &stateprob : belief) {
806 res[stateprob.first] = stateprob.second;
807 }
808 return res;
809}
810
811template<typename PomdpType, typename BeliefValueType, typename StateType>
814 std::vector<BeliefValueType> beliefAsVector = getBeliefAsVector(beliefId);
815 std::vector<BeliefValueType> res(matrix.getRowCount());
816 matrix.multiplyWithVector(beliefAsVector, res);
817 return res;
818}
819
821
822template class BeliefManager<storm::models::sparse::Pomdp<double>, storm::RationalNumber>;
823
825
827} // namespace storage
828} // namespace storm
double round(double val, int precision)
A class that implements the LpSolver interface using glpk as the background solver.
std::vector< BeliefValueType > computeMatrixBeliefProduct(BeliefId const &beliefId, storm::storage::SparseMatrix< BeliefValueType > &matrix)
void joinSupport(BeliefId const &beliefId, BeliefSupportType &support)
boost::container::flat_set< StateType > BeliefSupportType
PomdpType::ValueType ValueType
BeliefManager(PomdpType const &pomdp, BeliefValueType const &precision, TriangulationMode const &triangulationMode)
std::vector< std::pair< BeliefId, ValueType > > expandAndTriangulate(BeliefId const &beliefId, uint64_t actionIndex, std::vector< BeliefValueType > const &observationResolutions)
boost::container::flat_map< StateType, BeliefValueType > BeliefType
uint32_t getBeliefObservation(BeliefId beliefId)
void setRewardModel(std::optional< std::string > rewardModelName=std::nullopt)
std::string toString(BeliefId const &beliefId) const
uint64_t getBeliefNumberOfChoices(BeliefId beliefId)
uint64_t getRepresentativeState(BeliefId const &beliefId)
Returns the first state in the belief as a representative.
std::vector< std::pair< BeliefId, ValueType > > expand(BeliefId const &beliefId, uint64_t actionIndex)
BeliefId const & getInitialBelief() const
std::vector< std::pair< BeliefId, ValueType > > expandAndClip(BeliefId const &beliefId, uint64_t actionIndex, std::vector< uint64_t > const &observationResolutions)
ValueType getBeliefActionReward(BeliefId const &beliefId, uint64_t const &localActionIndex) const
BeliefId getNumberOfBeliefIds() const
void addToDistribution(DistributionType &distr, StateType const &state, BeliefValueType const &value)
std::string getObservationLabel(BeliefId const &beliefId)
ValueType getWeightedSum(BeliefId const &beliefId, std::vector< ValueType > const &summands)
BeliefClipping clipBeliefToGrid(BeliefId const &beliefId, uint64_t resolution, storm::storage::BitVector isInfinite=storm::storage::BitVector())
bool isEqual(BeliefId const &first, BeliefId const &second) const
Triangulation triangulateBelief(BeliefId beliefId, BeliefValueType resolution)
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
bool empty() const
Retrieves whether no bits are set to true in this bit vector.
A class that holds a possibly non-square matrix in the compressed row storage format.
void multiplyWithVector(std::vector< value_type > const &vector, std::vector< value_type > &result, std::vector< value_type > const *summand=nullptr) const
Multiplies the matrix with the given vector and writes the result to the given result vector.
index_type getRowCount() const
Returns the number of rows of the matrix.
#define STORM_LOG_WARN(message)
Definition logging.h:25
#define STORM_LOG_DEBUG(message)
Definition logging.h:18
#define STORM_LOG_TRACE(message)
Definition logging.h:12
#define STORM_LOG_ERROR(message)
Definition logging.h:26
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
Expression sum(std::vector< storm::expressions::Expression > const &expressions)
std::string toString(PomdpMemoryPattern const &pattern)
ValueType floor(ValueType const &number)
bool isZero(ValueType const &a)
Definition constants.cpp:38
bool isInteger(ValueType const &number)
ValueType round(ValueType const &number)
std::vector< BeliefValueType > weights