Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
RewardBoundUnfolder.cpp
Go to the documentation of this file.
2
3#include <limits>
4#include <queue>
5
19
23
25namespace detail {
26// Typedefs for readability
27using StateIdType = uint64_t;
28using ChoiceIdType = uint64_t; // global choices, i.e., each (state-action) pair has a unique id
29using EpochType = std::vector<int64_t>;
30using StateEpochPair = std::pair<StateIdType, EpochType>;
31
35template<typename ValueType>
36struct Dimension {
37 enum class Relation { greater, lessEqual } const relation{Relation::lessEqual};
38 int64_t const threshold;
39 int64_t const levelWidth;
40 std::string const freshLevelRewardOrActiveLabelName; // A name for the newly introduced level reward or active label
43};
44
48template<typename ValueType>
50 // Use an exclusive upper limit because INT64_MAX rounds up to 2^63 when converted to double.
51 ValueType const rewardLimit = -storm::utility::convertNumber<ValueType>(std::numeric_limits<int64_t>::min());
52 auto const rewardFitsInt64 = [&rewardLimit](ValueType const& reward) { return reward < rewardLimit; };
53 if (rewardModel.hasStateRewards() && !std::all_of(rewardModel.getStateRewardVector().begin(), rewardModel.getStateRewardVector().end(), rewardFitsInt64)) {
54 return false;
55 }
56 if (rewardModel.hasStateActionRewards() &&
57 !std::all_of(rewardModel.getStateActionRewardVector().begin(), rewardModel.getStateActionRewardVector().end(), rewardFitsInt64)) {
58 return false;
59 }
60 // Individual rewards may fit into int64_t even though their sum does not. Check all original choices, including unreachable ones.
61 if (rewardModel.hasStateRewards() && rewardModel.hasStateActionRewards()) {
62 for (StateIdType state = 0; state < model.getNumberOfStates(); ++state) {
63 for (auto const choice : model.getTransitionMatrix().getRowGroupIndices(state)) {
64 ValueType const reward = rewardModel.getStateReward(state) + rewardModel.getStateActionReward(choice);
65 if (!rewardFitsInt64(reward)) {
66 return false;
67 }
68 }
69 }
70 }
71 return true;
72}
73
78template<typename ValueType>
79std::vector<Dimension<ValueType>> extractDimensions(storm::models::sparse::Model<ValueType> const& model,
80 storm::logic::BoundedUntilFormula const& boundedUntilFormula, std::vector<uint64_t> const& levelWidths) {
81 STORM_LOG_THROW(!boundedUntilFormula.hasMultiDimensionalSubformulas(), storm::exceptions::NotSupportedException,
82 "Reward bound unfolding requires shared left and right state subformulas across all dimensions.");
83 STORM_LOG_THROW(boundedUntilFormula.getRightSubformula().isInFragment(storm::logic::propositional()), storm::exceptions::NotSupportedException,
84 "Only propositional right subformulas are supported."); // Temporal sub-formulas are potentially not preserved by the construction
85 STORM_LOG_THROW(boundedUntilFormula.getLeftSubformula().isInFragment(storm::logic::propositional()), storm::exceptions::NotSupportedException,
86 "Only propositional left subformulas are supported."); // Temporal sub-formulas are potentially not preserved by the construction
87 std::vector<Dimension<ValueType>> dimensions;
88 for (uint64_t formulaDim = 0; formulaDim < boundedUntilFormula.getDimension(); ++formulaDim) {
89 STORM_LOG_THROW(formulaDim >= levelWidths.size() || levelWidths[formulaDim] <= static_cast<uint64_t>(std::numeric_limits<int64_t>::max()),
90 storm::exceptions::InvalidArgumentException, "Level width in dimension " << formulaDim << " exceeds INT64_MAX.");
91 int64_t const levelWidth = formulaDim < levelWidths.size() ? static_cast<int64_t>(levelWidths[formulaDim]) : 0;
92 auto const& tbr = boundedUntilFormula.getTimeBoundReference(formulaDim);
93 STORM_LOG_THROW(tbr.isRewardBound(), storm::exceptions::NotSupportedException, "The bounded formula is not a reward-bounded formula.");
95 !tbr.hasRewardAccumulation(), storm::exceptions::NotSupportedException,
96 "The reward model for bound reference " << formulaDim << " has non-trivial reward accumulation which is not supported in this context.");
97 // An unnamed reference selects the explicit default model, or the unique model when no explicit default exists.
98 std::string const rewardModelName = tbr.getOptionalRewardModelName().get_value_or("");
99 auto const& rewardModel = model.getRewardModel(rewardModelName);
100 // Note: computation of successor epoch is slightly more involved with transition rewards which is why we do not support them for now
101 STORM_LOG_THROW(!rewardModel.hasTransitionRewards(), storm::exceptions::NotSupportedException,
102 "The reward model for bound reference " << formulaDim << " uses transition rewards. These are currently unsupported.");
103 // All assigned rewards need to be integer (might support rational via scaling, but its unclear how to scale levelWidth)
104 STORM_LOG_THROW(!rewardModel.hasStateRewards() || std::all_of(rewardModel.getStateRewardVector().begin(), rewardModel.getStateRewardVector().end(),
106 storm::exceptions::NotSupportedException, "State rewards in reward model " << rewardModelName << " are not integers.");
108 !rewardModel.hasStateActionRewards() || std::all_of(rewardModel.getStateActionRewardVector().begin(),
109 rewardModel.getStateActionRewardVector().end(), storm::utility::isInteger<ValueType>),
110 storm::exceptions::NotSupportedException, "State action rewards in reward model " << rewardModelName << " are not integers.");
111 // The finite epoch abstraction assumes that accumulated rewards never decrease. In particular, a negative-reward cycle would
112 // increase an epoch indefinitely and therefore make the unfolding infinite.
113 STORM_LOG_THROW(!rewardModel.hasNegativeRewards(), storm::exceptions::NotSupportedException,
114 "Reward model " << rewardModelName << " contains negative rewards. These are currently unsupported.");
115 STORM_LOG_ASSERT(rewardsFitInt64(model, rewardModel),
116 "Rewards in reward model " << rewardModelName << " for bound reference " << formulaDim << " exceed INT64_MAX.");
117
118 // Helper function to generate fresh identifiers (either for level reward or active label)
119 auto getFreshIdentifier = [&]() {
120 std::string prefix = "dim" + std::to_string(dimensions.size()) + (levelWidth == 0 ? "_active" : "_levelReward");
121 auto identifier = prefix;
122 for (uint64_t i = 0; (levelWidth == 0 ? model.hasLabel(identifier) : model.hasRewardModel(identifier)); ++i) {
123 identifier = prefix + "_" + std::to_string(i);
124 }
125 return identifier;
126 };
127
128 if (boundedUntilFormula.hasUpperBound(formulaDim)) {
129 STORM_LOG_THROW(boundedUntilFormula.hasIntegerUpperBound(formulaDim), storm::exceptions::NotSupportedException,
130 "Bound " << formulaDim << " is not an integer"); // might support rational via scaling (how to scale levelWidth?)
131 int64_t const bound = boundedUntilFormula.getUpperBound(formulaDim).evaluateAsInt();
132 int64_t const adjustment = boundedUntilFormula.isUpperBoundStrict(formulaDim) ? int64_t{1} : int64_t{0};
133 STORM_LOG_THROW(bound >= adjustment, storm::exceptions::NotSupportedException,
134 "Upper reward bound in dimension " << formulaDim << " is not satisfiable.");
135 int64_t const threshold = bound - adjustment;
136 dimensions.push_back(
137 Dimension<ValueType>{Dimension<ValueType>::Relation::lessEqual, threshold, levelWidth, getFreshIdentifier(), formulaDim, rewardModel});
138 }
139 if (boundedUntilFormula.hasLowerBound(formulaDim)) {
140 STORM_LOG_THROW(boundedUntilFormula.hasIntegerLowerBound(formulaDim), storm::exceptions::NotSupportedException,
141 "Bound " << formulaDim << " is not an integer"); // might support rational via scaling (how to scale levelWidth?)
142 int64_t const bound = boundedUntilFormula.getLowerBound(formulaDim).evaluateAsInt();
143 int64_t const adjustment = boundedUntilFormula.isLowerBoundStrict(formulaDim) ? int64_t{0} : int64_t{1};
144 if (bound >= adjustment) {
145 int64_t const threshold = bound - adjustment;
146 dimensions.push_back(
147 Dimension<ValueType>{Dimension<ValueType>::Relation::greater, threshold, levelWidth, getFreshIdentifier(), formulaDim, rewardModel});
148 } else {
149 STORM_LOG_WARN("Lower reward bound in dimension " << formulaDim << " is trivially satisfied and therefore dropped.");
150 }
151 }
152 }
153 return dimensions;
154}
155
160int64_t mod(int64_t a, int64_t b) {
161 int64_t const remainder = a % b;
162 return remainder < 0 ? remainder + b : remainder;
163}
164
165template<typename ValueType>
166EpochType computeInitialEpoch(std::vector<Dimension<ValueType>> const& dimensions) {
167 EpochType epoch;
168 epoch.reserve(dimensions.size());
169 for (auto const& dim : dimensions) {
170 if (dim.levelWidth == 0) {
171 epoch.push_back(dim.threshold);
172 } else {
173 epoch.push_back(mod(dim.threshold, dim.levelWidth));
174 }
175 }
176 return epoch;
177}
178
179template<typename ValueType>
180EpochType computeSuccessorEpoch(StateIdType currentState, EpochType const& currentEpoch, ChoiceIdType choice,
181 std::vector<Dimension<ValueType>> const& dimensions) {
182 EpochType successorEpoch = currentEpoch;
183 for (auto eIt = successorEpoch.begin(); auto const& dim : dimensions) {
184 auto const& rew = dim.rewardModel;
185 ValueType const reward = (rew.hasStateRewards() ? rew.getStateReward(currentState) : storm::utility::zero<ValueType>()) +
186 (rew.hasStateActionRewards() ? rew.getStateActionReward(choice) : storm::utility::zero<ValueType>());
187 int64_t const integerReward = storm::utility::convertNumber<int64_t>(reward);
188 // Initial epochs are nonnegative and abstracted epochs are at least -1. Combined rewards are assumed to be
189 // in [0, INT64_MAX] (asserted in extractDimensions), so subtraction cannot produce a value below INT64_MIN.
190 *eIt -= integerReward;
191 ++eIt;
192 }
193 return successorEpoch;
194}
195
196template<typename ValueType, typename LevelRewardSetterType>
197void computeLevelReward(EpochType const& successorEpoch, std::vector<Dimension<ValueType>> const& dimensions, LevelRewardSetterType const& setLevelReward) {
198 for (uint64_t dimIndex = 0; dimIndex < dimensions.size(); ++dimIndex) {
199 auto const lvlWidth = dimensions[dimIndex].levelWidth;
200 if (lvlWidth != 0) {
201 auto const reward = storm::utility::ceil<ValueType>(-storm::utility::convertNumber<ValueType>(successorEpoch[dimIndex]) /
203 STORM_LOG_ASSERT(reward >= storm::utility::zero<ValueType>(), "Expected non-negative level reward, got "
204 << reward << ". Succ epoch is " << successorEpoch[dimIndex] << " and lvlWidth is "
205 << lvlWidth);
206 // The level reward is the smallest number of times we can add lvlWidth to the epoch entry to get a non-negative value
207 setLevelReward(dimIndex, reward);
208 }
209 }
210}
211
212template<typename ValueType>
213void applyEpochAbstraction(EpochType& epoch, std::vector<Dimension<ValueType>> const& dimensions) {
214 for (auto eIt = epoch.begin(); auto const& dim : dimensions) {
215 if (*eIt < 0) {
216 if (dim.levelWidth == 0) {
217 // bottom epoch. If this is an upper bound, we can set all epoch entries to -1
218 if (dim.relation == Dimension<ValueType>::Relation::lessEqual) {
219 epoch.assign(epoch.size(), -1);
220 return;
221 }
222 *eIt = -1; // bottom epoch
223 } else {
224 *eIt = mod(*eIt, dim.levelWidth); // level abstraction
225 }
226 }
227 ++eIt;
228 }
229}
230
231template<typename ValueType, typename ActiveDimensionSetterType>
232void computeActiveDimensions(EpochType const& epoch, std::vector<Dimension<ValueType>> const& dimensions, ActiveDimensionSetterType const& setActiveDimension) {
233 for (uint64_t dimIndex = 0; dimIndex < dimensions.size(); ++dimIndex) {
234 auto const& dim = dimensions[dimIndex];
235 if (dim.levelWidth == 0) {
236 bool const inBottomEpoch = epoch[dimIndex] < 0;
237 if ((dim.relation == Dimension<ValueType>::Relation::greater) ? inBottomEpoch : !inBottomEpoch) {
238 setActiveDimension(dimIndex);
239 }
240 }
241 }
242}
243
245 std::pair<StateIdType, bool> getOrAddStateEpoch(StateIdType state, EpochType epoch) {
246 auto [idIt, isNewEntry] = stateEpochPairToId.try_emplace(std::make_pair(state, std::forward<EpochType>(epoch)), idToStateEpochPair.size());
247 if (isNewEntry) {
248 idToStateEpochPair.push_back(idIt->first);
249 }
250 return {idIt->second, isNewEntry};
251 }
252 std::map<StateEpochPair, StateIdType> stateEpochPairToId;
253 std::vector<StateEpochPair> idToStateEpochPair;
254};
255
256template<typename ValueType>
260 std::vector<std::vector<ValueType>> levelRewardsForDimensions;
261 std::vector<storm::storage::BitVector> activeDimensions;
262 std::vector<StateEpochPair> stateIdToOgStateEpochPair;
263};
264
265template<typename ValueType>
267 auto const& ogMatrix = model.getTransitionMatrix();
268 bool const hasRowGroups = !ogMatrix.hasTrivialRowGrouping();
269
270 // Initialize data objects that we are going to fill
271 storm::storage::SparseMatrixBuilder<ValueType> matrixBuilder(0, 0, 0, 0, hasRowGroups);
272 storm::storage::BitVector initialStateIds;
273 std::vector<std::vector<ValueType>> levelRewardsForDimensions(dimensions.size());
274 std::vector<storm::storage::BitVector> activeDimensions(dimensions.size());
275
276 // Exploration data
277 std::queue<StateIdType> queue;
278 StateEpochCollector stateEpochCollector;
279
280 // Auxiliary function that is called whenever a (state-epoch) pair is found
281 auto processNewStateEpoch = [&queue, &stateEpochCollector](StateIdType stateId, EpochType epoch) {
282 auto [newId, isNewEntry] = stateEpochCollector.getOrAddStateEpoch(stateId, epoch);
283 if (isNewEntry) {
284 queue.push(newId);
285 }
286 return newId;
287 };
288
289 // Fill queue with initial state(s)
290 auto initEpoch = computeInitialEpoch(dimensions);
291 for (auto const& initState : model.getInitialStates()) {
292 auto initId = processNewStateEpoch(initState, initEpoch);
293 initialStateIds.grow(initId + 1);
294 initialStateIds.set(initId);
295 }
296
297 // Start BFS exploration
298 uint64_t numChoicesInUnfolding = 0;
299 while (!queue.empty()) {
300 // Pop from queue
301 auto const currentStateEpochId = queue.front();
302 queue.pop();
303 auto [currentState, currentEpoch] = stateEpochCollector.idToStateEpochPair[currentStateEpochId];
304 // Can't take currentEpoch by reference because it might be invalidated when finding new epochs
305
306 // Compute active dimensions
307 computeActiveDimensions(currentEpoch, dimensions, [&activeDimensions, &currentStateEpochId](uint64_t dimIndex) {
308 activeDimensions[dimIndex].grow(currentStateEpochId + 1);
309 activeDimensions[dimIndex].set(currentStateEpochId);
310 });
311
312 // Explore successors
313 if (hasRowGroups) {
314 matrixBuilder.newRowGroup(numChoicesInUnfolding);
315 }
316 for (auto const choice : ogMatrix.getRowGroupIndices(currentState)) {
317 // Since we (for now) assume models without transition branch rewards, the successor epoch does not depend on the state that we reach
318 auto successorEpoch = computeSuccessorEpoch(currentState, currentEpoch, choice, dimensions);
319 // Compute the level rewards for the dimensions with level widths (needs to be done before abstracting away that information!)
320 computeLevelReward(successorEpoch, dimensions, [&levelRewardsForDimensions](uint64_t dimIndex, ValueType levelReward) {
321 levelRewardsForDimensions[dimIndex].push_back(levelReward);
322 });
323 // Abstract the successor epoch. Ensures that we only reach a finite set of epochs.
324 applyEpochAbstraction(successorEpoch, dimensions);
325 for (auto const& entry : ogMatrix.getRow(choice)) {
326 if (storm::utility::isZero(entry.getValue())) {
327 continue;
328 }
329 STORM_LOG_ASSERT(entry.getValue() > storm::utility::zero<ValueType>(), "Transition probabilities must be positive.");
330 auto successorInUnfolding = processNewStateEpoch(entry.getColumn(), successorEpoch);
331 matrixBuilder.addNextValue(numChoicesInUnfolding, successorInUnfolding, entry.getValue());
332 }
333 ++numChoicesInUnfolding;
334 }
335 }
336 auto matrix = matrixBuilder.build(numChoicesInUnfolding, stateEpochCollector.idToStateEpochPair.size(), stateEpochCollector.idToStateEpochPair.size());
337 STORM_LOG_ASSERT(matrix.isProbabilistic(storm::utility::zero<ValueType>()), "Resulting transition matrix is not a probability matrix.");
338 STORM_LOG_ASSERT(matrix.hasOnlyPositiveEntries(), "Resulting transition matrix has non-positive entries.");
339
340 return ExplorationResult<ValueType>{std::move(matrix), std::move(initialStateIds), std::move(levelRewardsForDimensions), std::move(activeDimensions),
341 std::move(stateEpochCollector.idToStateEpochPair)};
342}
343
344template<typename ValueType>
346 std::vector<Dimension<ValueType>> const& dimensions,
347 std::set<std::string> const& preservedRewardModels,
348 ExplorationResult<ValueType>&& explorationResult) {
349 STORM_LOG_WARN_COND(!originalModel.hasStateValuations(), "State valuations are not preserved in bound unfolding.");
350 STORM_LOG_WARN_COND(!originalModel.hasChoiceOrigins(), "Choice origins are not preserved in bound unfolding.");
351 uint64_t const numStates = explorationResult.matrix.getColumnCount();
352 uint64_t const numChoices = explorationResult.matrix.getRowCount();
353
354 storm::storage::sparse::ModelComponents<ValueType> components(std::move(explorationResult.matrix), storm::models::sparse::StateLabeling(numStates));
355
356 // Helper functions to iterate over all states/choices. Unfolding states/choices are called in ascending order of their ids.
357 auto forEachState = [&explorationResult, &numStates](auto const& f) {
358 for (uint64_t unfoldingState = 0; unfoldingState < numStates; ++unfoldingState) {
359 f(unfoldingState, explorationResult.stateIdToOgStateEpochPair[unfoldingState].first);
360 }
361 };
362 auto forEachChoice = [&originalModel, &components, &explorationResult, &numStates](auto const& f) {
363 for (uint64_t unfoldingState = 0; unfoldingState < numStates; ++unfoldingState) {
364 uint64_t const originalState = explorationResult.stateIdToOgStateEpochPair[unfoldingState].first;
365 STORM_LOG_ASSERT(components.transitionMatrix.getRowGroupSize(unfoldingState) == originalModel.getTransitionMatrix().getRowGroupSize(originalState),
366 "Number of choices in unfolding and original model differ.");
367 auto origChoice = originalModel.getTransitionMatrix().getRowGroupIndices()[originalState];
368 for (auto unfoldingChoice : components.transitionMatrix.getRowGroupIndices(unfoldingState)) {
369 f(unfoldingChoice, origChoice);
370 ++origChoice;
371 }
372 }
373 };
374
375 // Create the state labeling
376 explorationResult.initialStates.resize(numStates);
377 components.stateLabeling.addLabel("init", std::move(explorationResult.initialStates));
378 for (auto label : originalModel.getStateLabeling().getLabels()) {
379 if (label == "init") {
380 continue;
381 }
382 auto const& originalLabel = originalModel.getStateLabeling().getStates(label);
383 storm::storage::BitVector newLabel(numStates, false);
384 forEachState([&newLabel, &originalLabel](uint64_t unfoldingState, StateIdType originalState) {
385 if (originalLabel.get(originalState)) {
386 newLabel.set(unfoldingState);
387 }
388 });
389 components.stateLabeling.addLabel(label, std::move(newLabel));
390 }
391 for (uint64_t dimIndex = 0; dimIndex < dimensions.size(); ++dimIndex) {
392 if (dimensions[dimIndex].levelWidth == 0) {
393 explorationResult.activeDimensions[dimIndex].resize(numStates);
394 components.stateLabeling.addLabel(dimensions[dimIndex].freshLevelRewardOrActiveLabelName, std::move(explorationResult.activeDimensions[dimIndex]));
395 }
396 }
397
398 // Create the (optional) choice labeling
399 if (originalModel.hasChoiceLabeling()) {
400 components.choiceLabeling.emplace(numChoices);
401 for (auto label : originalModel.getChoiceLabeling().getLabels()) {
402 auto const& originalLabel = originalModel.getChoiceLabeling().getChoices(label);
403 storm::storage::BitVector newLabel(numChoices, false);
404 forEachChoice([&newLabel, &originalLabel](uint64_t unfoldingChoice, uint64_t originalChoice) {
405 if (originalLabel.get(originalChoice)) {
406 newLabel.set(unfoldingChoice);
407 }
408 });
409 components.choiceLabeling->addLabel(label, std::move(newLabel));
410 }
411 }
412 // Create the reward models
413 for (auto const& [name, rewmodel] : originalModel.getRewardModels()) {
414 if (!preservedRewardModels.contains(name)) {
415 continue;
416 }
417 STORM_LOG_THROW(!rewmodel.hasTransitionRewards(), storm::exceptions::NotSupportedException,
418 "Transition rewards are currently not supported in this context.");
419 std::optional<std::vector<ValueType>> stateRewards, stateActionRewards;
420 if (rewmodel.hasStateRewards()) {
421 stateRewards.emplace();
422 stateRewards->reserve(numStates);
423 forEachState(
424 [&stateRewards, &rewmodel](uint64_t _, StateIdType originalState) { stateRewards->push_back(rewmodel.getStateReward(originalState)); });
425 }
426 if (rewmodel.hasStateActionRewards()) {
427 stateActionRewards.emplace();
428 stateActionRewards->reserve(numChoices);
429 forEachChoice([&stateActionRewards, &rewmodel](uint64_t _, StateIdType originalChoice) {
430 stateActionRewards->push_back(rewmodel.getStateActionReward(originalChoice));
431 });
432 }
433 components.rewardModels.emplace(name, storm::models::sparse::StandardRewardModel<ValueType>{std::move(stateRewards), std::move(stateActionRewards)});
434 }
435 for (uint64_t dimIndex = 0; dimIndex < dimensions.size(); ++dimIndex) {
436 if (dimensions[dimIndex].levelWidth != 0) {
437 explorationResult.levelRewardsForDimensions[dimIndex].shrink_to_fit();
438 components.rewardModels.emplace(
439 dimensions[dimIndex].freshLevelRewardOrActiveLabelName,
440 storm::models::sparse::StandardRewardModel<ValueType>{std::nullopt, std::move(explorationResult.levelRewardsForDimensions[dimIndex])});
441 }
442 }
443
444 // Create POMDP-specific components
445 if (originalModel.isOfType(storm::models::ModelType::Pomdp)) {
446 auto const& pomdp = static_cast<storm::models::sparse::Pomdp<ValueType> const&>(originalModel);
447 std::vector<uint32_t> unfoldingStateObservations;
448 unfoldingStateObservations.reserve(numStates);
449 forEachState([&unfoldingStateObservations, &pomdp](auto _, StateIdType originalState) {
450 unfoldingStateObservations.push_back(pomdp.getObservation(originalState));
451 });
452 components.observabilityClasses = std::move(unfoldingStateObservations);
453 STORM_LOG_WARN_COND(!pomdp.hasObservationValuations(), "Observation valuations are dropped.");
454
455 } else {
457 storm::exceptions::NotSupportedException, "Unfolding is only supported for POMDPs, MDPs and DTMCs.");
458 }
459 return components;
460}
461
462template<typename ValueType>
463std::shared_ptr<storm::logic::Formula> constructFormula(storm::logic::BoundedUntilFormula boundedUntilFormula,
464 std::vector<Dimension<ValueType>> const& dimensions) {
465 // Construct a new (bounded or unbounded) until formula
466 auto lhs = boundedUntilFormula.getLeftSubformula().clone();
467 auto rhs = boundedUntilFormula.getRightSubformula().clone();
468 // At this point, we may have removed all bound dimensions as they were trivial. We return an unbounded until-formula.
469 if (dimensions.empty()) {
470 return std::make_shared<storm::logic::UntilFormula>(std::move(lhs), std::move(rhs));
471 }
472 std::vector<std::optional<storm::logic::TimeBound>> lowerBounds, upperBounds;
473 std::vector<storm::logic::TimeBoundReference> timeBoundReferences;
474 STORM_LOG_ASSERT(boundedUntilFormula.getDimension() > 0, "did not expect a 0-dimensional formula.");
475 auto const& exprManager =
476 boundedUntilFormula.hasLowerBound(0) ? boundedUntilFormula.getLowerBound(0).getManager() : boundedUntilFormula.getUpperBound(0).getManager();
477 for (auto const& dim : dimensions) {
478 if (dim.levelWidth == 0) {
479 // Dimension is unfolded up to the threshold. Inject the label where the bound is active
480 auto activeFormula = std::make_shared<storm::logic::AtomicLabelFormula>(dim.freshLevelRewardOrActiveLabelName);
481 rhs = std::make_shared<storm::logic::BinaryBooleanStateFormula>(storm::logic::BinaryBooleanOperatorType::And, std::move(rhs),
482 std::move(activeFormula));
483 if (dim.relation == Dimension<ValueType>::Relation::lessEqual) {
484 auto activeFormula = std::make_shared<storm::logic::AtomicLabelFormula>(dim.freshLevelRewardOrActiveLabelName);
485 lhs = std::make_shared<storm::logic::BinaryBooleanStateFormula>(storm::logic::BinaryBooleanOperatorType::And, std::move(lhs),
486 std::move(activeFormula));
487 }
488 } else {
489 // Dimension is unfolded up to level width. Add a reward bound addressing the level jump reward
490 auto const lvlthreshold = exprManager.integer(storm::utility::convertNumber<int64_t>(storm::utility::floor<ValueType>(
492 if (dim.relation == Dimension<ValueType>::Relation::lessEqual) {
493 upperBounds.push_back(storm::logic::TimeBound{false, lvlthreshold});
494 lowerBounds.emplace_back();
495 } else {
496 upperBounds.emplace_back();
497 lowerBounds.push_back(storm::logic::TimeBound{true, lvlthreshold});
498 }
499 timeBoundReferences.emplace_back(dim.freshLevelRewardOrActiveLabelName);
500 }
501 }
502
503 // Construct the new formula
504
505 if (timeBoundReferences.empty()) {
506 return std::make_shared<storm::logic::UntilFormula>(lhs, rhs);
507 } else {
508 return std::make_shared<storm::logic::BoundedUntilFormula>(lhs, rhs, std::move(lowerBounds), std::move(upperBounds), std::move(timeBoundReferences));
509 }
510}
511
512} // namespace detail
513
514template<typename ValueType>
516 storm::logic::Formula const& formula, UnfoldingOptions const& options) {
519 storm::exceptions::NotSupportedException, "Reward bound unfolding is only supported for POMDPs, MDPs and DTMCs.");
520 if (formula.isProbabilityOperatorFormula()) {
521 // Recursive call with subformula
522 auto const& opFormula = formula.asProbabilityOperatorFormula();
523 auto result = transform(model, opFormula.getSubformula(), options);
524 result.formula = std::make_shared<storm::logic::ProbabilityOperatorFormula>(std::move(result.formula), opFormula.getOperatorInformation());
525 return result;
526 }
527
528 // Process with boundedUntilFormula
529 STORM_LOG_THROW(formula.isBoundedUntilFormula(), storm::exceptions::InvalidPropertyException, "Unexpected formula type." << formula << ".");
530 auto const& boundedUntilFormula = formula.asBoundedUntilFormula();
531 auto const dimensions = detail::extractDimensions(model, boundedUntilFormula, options.levelWidths);
532 auto explorationResult = detail::exploreUnfolding(model, dimensions);
533 auto components = detail::constructComponents(model, dimensions, options.preservedRewardModels, std::move(explorationResult));
534 auto unfoldedModel = storm::utility::builder::buildModelFromComponents(model.getType(), std::move(components));
535
536 if (unfoldedModel->isOfType(storm::models::ModelType::Pomdp)) {
537 auto& unfoldedPomdp = static_cast<storm::models::sparse::Pomdp<ValueType>&>(*unfoldedModel);
538 auto const& originalPomdp = static_cast<storm::models::sparse::Pomdp<ValueType> const&>(model);
539 unfoldedPomdp.setIsCanonic(originalPomdp.isCanonic());
540 }
541
542 return {std::move(unfoldedModel), std::move(detail::constructFormula(boundedUntilFormula, dimensions))};
543}
544
545template class RewardBoundUnfolder<double>;
547
548} // namespace storm::pomdp::transformer
int_fast64_t evaluateAsInt(Valuation const *valuation=nullptr) const
Evaluates the expression under the valuation of variables given by the valuation and returns the resu...
ExpressionManager const & getManager() const
Retrieves the manager responsible for this expression.
TimeBoundReference const & getTimeBoundReference(unsigned i=0) const
bool isLowerBoundStrict(unsigned i=0) const
storm::expressions::Expression const & getUpperBound(unsigned i=0) const
storm::expressions::Expression const & getLowerBound(unsigned i=0) const
bool hasIntegerUpperBound(unsigned i=0) const
bool hasIntegerLowerBound(unsigned i=0) const
bool isUpperBoundStrict(unsigned i=0) const
BoundedUntilFormula & asBoundedUntilFormula()
Definition Formula.cpp:363
virtual bool isProbabilityOperatorFormula() const
Definition Formula.cpp:194
ProbabilityOperatorFormula & asProbabilityOperatorFormula()
Definition Formula.cpp:506
bool isInFragment(FragmentSpecification const &fragment) const
Definition Formula.cpp:218
virtual bool isBoundedUntilFormula() const
Definition Formula.cpp:90
std::shared_ptr< Formula > clone() const
Definition Formula.cpp:554
virtual ModelType getType() const
Return the actual type of the model.
Definition ModelBase.cpp:7
bool isOfType(storm::models::ModelType const &modelType) const
Checks whether the model is of the given type.
Definition ModelBase.cpp:27
storm::storage::BitVector const & getChoices(std::string const &label) const
Returns the labeling of choices associated with the given label.
std::set< std::string > getLabels() const
Retrieves the set of labels contained in this labeling.
void addLabel(std::string const &label)
Adds a new label to the labelings.
Base class for all sparse models.
Definition Model.h:34
storm::models::sparse::ChoiceLabeling const & getChoiceLabeling() const
Retrieves the labels for the choices of the model.
Definition Model.cpp:363
storm::storage::SparseMatrix< ValueType > const & getTransitionMatrix() const
Retrieves the matrix representing the transitions of the model.
Definition Model.cpp:226
std::unordered_map< std::string, RewardModelType > const & getRewardModels() const
Retrieves the reward models.
Definition Model.cpp:728
bool hasStateValuations() const
Retrieves whether this model was build with state valuations.
Definition Model.cpp:378
bool hasChoiceLabeling() const
Retrieves whether this model has a labeling of the choices.
Definition Model.cpp:358
virtual bool hasRewardModel(std::string const &rewardModelName) const override
Retrieves whether the model has a reward model with the given name.
Definition Model.cpp:236
storm::models::sparse::StateLabeling const & getStateLabeling() const
Returns the state labeling associated with this model.
Definition Model.cpp:348
bool hasChoiceOrigins() const
Retrieves whether this model was build with choice origins.
Definition Model.cpp:398
virtual uint_fast64_t getNumberOfStates() const override
Returns the number of states of the model.
Definition Model.cpp:191
bool hasLabel(std::string const &label) const
Retrieves whether the given label is a valid label in this model.
Definition Model.cpp:221
RewardModelType const & getRewardModel(std::string const &rewardModelName) const
Retrieves the reward model with the given name, if one exists.
Definition Model.cpp:247
storm::storage::BitVector const & getInitialStates() const
Retrieves the initial states of the model.
Definition Model.cpp:206
This class represents a partially observable Markov decision process.
Definition Pomdp.h:13
void setIsCanonic(bool newValue=true)
Definition Pomdp.cpp:139
ValueType const & getStateReward(uint_fast64_t state) const
std::vector< ValueType > const & getStateActionRewardVector() const
Retrieves the state-action rewards of the reward model.
std::vector< ValueType > const & getStateRewardVector() const
Retrieves the state rewards of the reward model.
ValueType const & getStateActionReward(uint_fast64_t choiceIndex) const
Retrieves the state-action reward for the given choice.
bool hasStateRewards() const
Retrieves whether the reward model has state rewards.
bool hasStateActionRewards() const
Retrieves whether the reward model has state-action rewards.
This class manages the labeling of the state space with a number of (atomic) labels.
storm::storage::BitVector const & getStates(std::string const &label) const
Returns the labeling of states associated with the given label.
static ReturnType transform(storm::models::sparse::Model< ValueType > const &model, storm::logic::Formula const &formula, UnfoldingOptions const &options={})
Unfolds reward-bounded dimensions into the model state space and returns an equivalent formula.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
void grow(uint64_t minimumLength, bool init=false)
Enlarges the bit vector such that it holds at least the given number of bits (but possibly more).
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
A class that can be used to build a sparse matrix by adding value by value.
void addNextValue(index_type row, index_type column, value_type const &value)
Sets the matrix entry at the given row and column to the given value.
void newRowGroup(index_type startingRow)
Starts a new row group in the matrix.
SparseMatrix< value_type > build(index_type overriddenRowCount=0, index_type overriddenColumnCount=0, index_type overriddenRowGroupCount=0)
A class that holds a possibly non-square matrix in the compressed row storage format.
#define STORM_LOG_WARN(message)
Definition logging.h:35
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:54
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:39
FragmentSpecification propositional()
ExplorationResult< ValueType > exploreUnfolding(storm::models::sparse::Model< ValueType > const &model, std::vector< Dimension< ValueType > > const &dimensions)
EpochType computeInitialEpoch(std::vector< Dimension< ValueType > > const &dimensions)
std::pair< StateIdType, EpochType > StateEpochPair
bool rewardsFitInt64(storm::models::sparse::Model< ValueType > const &model, storm::models::sparse::StandardRewardModel< ValueType > const &rewardModel)
Checks whether all individual and combined rewards fit into int64_t, assuming nonnegative integer rew...
int64_t mod(int64_t a, int64_t b)
Helper function to compute the modulos.
EpochType computeSuccessorEpoch(StateIdType currentState, EpochType const &currentEpoch, ChoiceIdType choice, std::vector< Dimension< ValueType > > const &dimensions)
storm::storage::sparse::ModelComponents< ValueType > constructComponents(storm::models::sparse::Model< ValueType > const &originalModel, std::vector< Dimension< ValueType > > const &dimensions, std::set< std::string > const &preservedRewardModels, ExplorationResult< ValueType > &&explorationResult)
std::vector< Dimension< ValueType > > extractDimensions(storm::models::sparse::Model< ValueType > const &model, storm::logic::BoundedUntilFormula const &boundedUntilFormula, std::vector< uint64_t > const &levelWidths)
Extracts the dimension information from the input formula.
void applyEpochAbstraction(EpochType &epoch, std::vector< Dimension< ValueType > > const &dimensions)
void computeLevelReward(EpochType const &successorEpoch, std::vector< Dimension< ValueType > > const &dimensions, LevelRewardSetterType const &setLevelReward)
std::shared_ptr< storm::logic::Formula > constructFormula(storm::logic::BoundedUntilFormula boundedUntilFormula, std::vector< Dimension< ValueType > > const &dimensions)
void computeActiveDimensions(EpochType const &epoch, std::vector< Dimension< ValueType > > const &dimensions, ActiveDimensionSetterType const &setActiveDimension)
std::shared_ptr< storm::models::sparse::Model< ValueType, RewardModelType > > buildModelFromComponents(storm::models::ModelType modelType, storm::storage::sparse::ModelComponents< ValueType, RewardModelType > &&components)
Definition builder.cpp:20
ValueType floor(ValueType const &number)
bool isZero(ValueType const &a)
Definition constants.cpp:44
ValueType ceil(ValueType const &number)
bool isInteger(ValueType const &number)
ValueType zero()
Definition constants.cpp:26
TargetType convertNumber(SourceType const &number)
std::vector< uint64_t > levelWidths
Allows to define a levelWidth for each dimension.
std::set< std::string > preservedRewardModels
The reward models that will be preserved in the unfolding.
Contains information for a single dimension of the unfolding.
enum storm::pomdp::transformer::detail::Dimension::Relation lessEqual
storm::models::sparse::StandardRewardModel< ValueType > const & rewardModel
std::vector< storm::storage::BitVector > activeDimensions
std::vector< std::vector< ValueType > > levelRewardsForDimensions
storm::storage::SparseMatrix< ValueType > matrix
std::map< StateEpochPair, StateIdType > stateEpochPairToId
std::pair< StateIdType, bool > getOrAddStateEpoch(StateIdType state, EpochType epoch)
std::unordered_map< std::string, RewardModelType > rewardModels
storm::storage::SparseMatrix< ValueType > transitionMatrix
std::optional< storm::models::sparse::ChoiceLabeling > choiceLabeling
storm::models::sparse::StateLabeling stateLabeling
std::optional< std::vector< uint32_t > > observabilityClasses