Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
RewardBoundUnfolderTest.cpp
Go to the documentation of this file.
1#include "storm-config.h"
2#include "test/storm_gtest.h"
3
4#include <limits>
5#include <memory>
6#include <optional>
7#include <set>
8#include <string>
9#include <utility>
10#include <vector>
11
12#include <boost/optional.hpp>
13
18#include "storm/api/storm.h"
33
34namespace {
35
36using ValueType = storm::RationalNumber;
41
42std::shared_ptr<storm::logic::Formula const> makeRewardBoundedFormula(
43 std::optional<int64_t> lowerBound, std::optional<int64_t> upperBound,
44 storm::solver::OptimizationDirection direction = storm::solver::OptimizationDirection::Maximize) {
45 static auto const expressionManager = std::make_shared<storm::expressions::ExpressionManager>();
46 std::optional<storm::logic::TimeBound> lowerTimeBound;
47 std::optional<storm::logic::TimeBound> upperTimeBound;
48 if (lowerBound) {
49 lowerTimeBound.emplace(false, expressionManager->integer(*lowerBound));
50 }
51 if (upperBound) {
52 upperTimeBound.emplace(false, expressionManager->integer(*upperBound));
53 }
54
55 auto pathFormula = std::make_shared<storm::logic::BoundedUntilFormula>(std::make_shared<storm::logic::BooleanLiteralFormula>(true),
56 std::make_shared<storm::logic::AtomicLabelFormula>("goal"), lowerTimeBound,
57 upperTimeBound, storm::logic::TimeBoundReference(boost::optional<std::string>{"r"}));
58 return std::make_shared<storm::logic::ProbabilityOperatorFormula>(
59 std::move(pathFormula), storm::logic::OperatorInformation(boost::optional<storm::solver::OptimizationDirection>{direction}));
60}
61
64 labeling.addLabel("init");
65 labeling.addLabelToState("init", 0);
66 labeling.addLabel("goal");
67 labeling.addLabelToState("goal", 1);
68 return labeling;
69}
70
72 std::optional<std::vector<ValueType>> stateRewards{
74 return storm::models::sparse::StandardRewardModel<ValueType>(std::move(stateRewards));
75}
76
77std::shared_ptr<DtmcType> buildDtmc() {
78 storm::storage::SparseMatrixBuilder<ValueType> matrixBuilder(3, 3, 4, false, false);
79 auto const half = storm::utility::convertNumber<ValueType>(std::string("1/2"));
80 matrixBuilder.addNextValue(0, 1, half);
81 matrixBuilder.addNextValue(0, 2, half);
82 matrixBuilder.addNextValue(1, 1, storm::utility::one<ValueType>());
83 matrixBuilder.addNextValue(2, 2, storm::utility::one<ValueType>());
84
85 storm::storage::sparse::ModelComponents<ValueType> components(matrixBuilder.build(), makeLabeling());
86 components.rewardModels.emplace("r", makeRewardModel());
87 return std::make_shared<DtmcType>(std::move(components));
88}
89
90std::shared_ptr<MdpType> buildMdp() {
91 storm::storage::SparseMatrixBuilder<ValueType> matrixBuilder(0, 0, 0, false, true);
92 matrixBuilder.newRowGroup(0);
93 matrixBuilder.addNextValue(0, 1, storm::utility::one<ValueType>());
94 matrixBuilder.addNextValue(1, 1, storm::utility::one<ValueType>());
95 matrixBuilder.newRowGroup(2);
96 matrixBuilder.addNextValue(2, 1, storm::utility::one<ValueType>());
97 matrixBuilder.newRowGroup(3);
98 matrixBuilder.addNextValue(3, 2, storm::utility::one<ValueType>());
99
100 storm::storage::sparse::ModelComponents<ValueType> components(matrixBuilder.build(), makeLabeling());
101 components.rewardModels.emplace("r", storm::models::sparse::StandardRewardModel<ValueType>(std::nullopt, std::vector<ValueType>{1, 2, 0, 0}));
102 return std::make_shared<MdpType>(std::move(components));
103}
104
105std::shared_ptr<PomdpType> buildPomdp() {
106 storm::storage::SparseMatrixBuilder<ValueType> matrixBuilder(0, 0, 0, false, true);
107 auto const half = storm::utility::convertNumber<ValueType>(std::string("1/2"));
108 matrixBuilder.newRowGroup(0);
109 matrixBuilder.addNextValue(0, 1, half);
110 matrixBuilder.addNextValue(0, 2, half);
111 matrixBuilder.newRowGroup(1);
112 matrixBuilder.addNextValue(1, 1, storm::utility::one<ValueType>());
113 matrixBuilder.newRowGroup(2);
114 matrixBuilder.addNextValue(2, 2, storm::utility::one<ValueType>());
115
116 storm::storage::sparse::ModelComponents<ValueType> components(matrixBuilder.build(), makeLabeling());
117 components.rewardModels.emplace("r", makeRewardModel());
118 components.observabilityClasses = std::vector<uint32_t>{0, 1, 2};
119 return std::make_shared<PomdpType>(std::move(components), true);
120}
121
122ResultValueType getInitialValue(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model,
123 std::shared_ptr<storm::logic::Formula const> const& formula) {
125 EXPECT_TRUE(result->isExplicitQuantitativeCheckResult());
126 return result->asExplicitQuantitativeCheckResult<ValueType>()[*model->getInitialStates().begin()];
127}
128
129} // namespace
130
131TEST(RewardBoundUnfolder, FullyUnfoldedDtmcPreservesRewardBoundedProbability) {
132 auto const dtmc = buildDtmc();
133 auto const formula = makeRewardBoundedFormula(std::nullopt, 1);
134 auto const expectedValue = getInitialValue(dtmc, formula);
135
137
138 EXPECT_EQ(storm::models::ModelType::Dtmc, result.model->getType());
139 EXPECT_TRUE(result.formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula());
140 EXPECT_EQ(expectedValue, getInitialValue(result.model, result.formula));
141}
142
143TEST(RewardBoundUnfolder, FullyUnfoldedMdpPreservesChoicesAndOptimalProbabilities) {
144 auto const mdp = buildMdp();
145 for (auto const direction : {storm::solver::OptimizationDirection::Maximize, storm::solver::OptimizationDirection::Minimize}) {
146 auto const formula = makeRewardBoundedFormula(std::nullopt, 1, direction);
147 auto const expectedValue = getInitialValue(mdp, formula);
148 EXPECT_EQ(direction == storm::solver::OptimizationDirection::Maximize ? storm::utility::one<ValueType>() : storm::utility::zero<ValueType>(),
149 expectedValue);
150
152
153 ASSERT_EQ(storm::models::ModelType::Mdp, result.model->getType());
154 EXPECT_EQ(2u, result.model->getTransitionMatrix().getRowGroupSize(*result.model->getInitialStates().begin()));
155 EXPECT_TRUE(result.formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula());
156 EXPECT_EQ(expectedValue, getInitialValue(result.model, result.formula));
157 }
158}
159
160TEST(RewardBoundUnfolder, FullyUnfoldedPomdpPreservesObservationsAndActiveBound) {
161 auto pomdp = buildPomdp();
162 pomdp->getRewardModel("r").setStateReward(1, storm::utility::one<ValueType>());
163 auto const formula = makeRewardBoundedFormula(std::nullopt, 1);
164
166 auto const unfoldedPomdp = result.model->as<PomdpType>();
167
168 ASSERT_NE(nullptr, unfoldedPomdp);
169 ASSERT_EQ(4u, unfoldedPomdp->getNumberOfStates());
170 EXPECT_TRUE(unfoldedPomdp->isCanonic());
171 EXPECT_EQ(pomdp->getNrObservations(), unfoldedPomdp->getNrObservations());
172 EXPECT_EQ(pomdp->getObservation(0), unfoldedPomdp->getObservation(0));
173 EXPECT_EQ(pomdp->getObservation(1), unfoldedPomdp->getObservation(1));
174 EXPECT_EQ(pomdp->getObservation(2), unfoldedPomdp->getObservation(2));
175 EXPECT_EQ(pomdp->getObservation(1), unfoldedPomdp->getObservation(3));
176 EXPECT_TRUE(unfoldedPomdp->getStateLabeling().getStates("dim0_active").get(0));
177 EXPECT_TRUE(unfoldedPomdp->getStateLabeling().getStates("dim0_active").get(1));
178 EXPECT_TRUE(unfoldedPomdp->getStateLabeling().getStates("dim0_active").get(2));
179 EXPECT_FALSE(unfoldedPomdp->getStateLabeling().getStates("dim0_active").get(3));
180 EXPECT_TRUE(unfoldedPomdp->getStateLabeling().getStates("goal").get(1));
181 EXPECT_TRUE(unfoldedPomdp->getStateLabeling().getStates("goal").get(3));
182 EXPECT_TRUE(result.formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula());
183}
184
185TEST(RewardBoundUnfolder, LevelAbstractionPreservesPomdpSpecificComponents) {
186 auto const pomdp = buildPomdp();
187 auto const formula = makeRewardBoundedFormula(std::nullopt, 1);
188
190 options.levelWidths = {2};
191 options.preservedRewardModels = {"r"};
192 auto const result = storm::pomdp::transformer::RewardBoundUnfolder<ValueType>::transform(*pomdp, *formula, options);
193 auto const unfoldedPomdp = result.model->as<PomdpType>();
194
195 ASSERT_NE(nullptr, unfoldedPomdp);
196 EXPECT_TRUE(unfoldedPomdp->isCanonic());
197 EXPECT_EQ(pomdp->getNrObservations(), unfoldedPomdp->getNrObservations());
198 EXPECT_TRUE(unfoldedPomdp->hasRewardModel("r"));
199 EXPECT_TRUE(unfoldedPomdp->hasRewardModel("dim0_levelReward"));
200 EXPECT_TRUE(result.formula->asProbabilityOperatorFormula().getSubformula().isBoundedUntilFormula());
201}
202
203TEST(RewardBoundUnfolder, RejectsLevelWidthOutsideSignedRange) {
204 auto const pomdp = buildPomdp();
205 auto const formula = makeRewardBoundedFormula(std::nullopt, 1);
207 options.levelWidths = {static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1};
208
210 storm::exceptions::InvalidArgumentException);
211}
212
213TEST(RewardBoundUnfolder, AcceptsLargestSignedLevelWidth) {
214 auto const dtmc = buildDtmc();
215 auto const formula = makeRewardBoundedFormula(std::nullopt, 1);
217 options.levelWidths = {static_cast<uint64_t>(std::numeric_limits<int64_t>::max())};
218
219 auto const result = storm::pomdp::transformer::RewardBoundUnfolder<ValueType>::transform(*dtmc, *formula, options);
220 EXPECT_EQ(getInitialValue(dtmc, formula), getInitialValue(result.model, result.formula));
221}
222
223TEST(RewardBoundUnfolder, TrivialLowerBoundCanBeFollowedByRewardAwareObservationTransformation) {
224 auto const pomdp = buildPomdp();
225 auto const formula = makeRewardBoundedFormula(0, std::nullopt);
226 auto const& inputBoundedUntilFormula = formula->asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula();
227 ASSERT_TRUE(inputBoundedUntilFormula.hasLowerBound());
228 EXPECT_FALSE(inputBoundedUntilFormula.isLowerBoundStrict());
229
231 options.levelWidths = {1};
232 auto const unfoldingResult = storm::pomdp::transformer::RewardBoundUnfolder<ValueType>::transform(*pomdp, *formula, options);
233 auto const unfoldedPomdp = unfoldingResult.model->as<PomdpType>();
234 std::set<std::string> levelRewardModels;
235 unfoldingResult.formula->gatherReferencedRewardModels(levelRewardModels);
236
237 ASSERT_NE(nullptr, unfoldedPomdp);
238 EXPECT_TRUE(unfoldingResult.formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula());
239 EXPECT_TRUE(levelRewardModels.empty());
240
241 auto const rewardAwarePomdp =
243
244 EXPECT_TRUE(rewardAwarePomdp->isCanonic());
245 EXPECT_EQ(5u, rewardAwarePomdp->getNrObservations());
246 EXPECT_EQ(unfoldedPomdp->getNumberOfStates(), rewardAwarePomdp->getNumberOfStates());
247}
248
249TEST(RewardBoundUnfolder, LevelAbstractionTurnsNonStrictLowerBoundIntoStrictBound) {
250 auto const dtmc = buildDtmc();
251 auto const formula = makeRewardBoundedFormula(1, std::nullopt);
252 auto const& inputBoundedUntilFormula = formula->asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula();
253 ASSERT_TRUE(inputBoundedUntilFormula.hasLowerBound());
254 EXPECT_FALSE(inputBoundedUntilFormula.isLowerBoundStrict());
255 auto const expectedValue = getInitialValue(dtmc, formula);
256
258 options.levelWidths = {2};
259 auto const result = storm::pomdp::transformer::RewardBoundUnfolder<ValueType>::transform(*dtmc, *formula, options);
260 auto const& unfoldedBoundedUntilFormula = result.formula->asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula();
261
262 ASSERT_TRUE(unfoldedBoundedUntilFormula.hasLowerBound());
263 EXPECT_TRUE(unfoldedBoundedUntilFormula.isLowerBoundStrict());
264 EXPECT_EQ(0, unfoldedBoundedUntilFormula.getLowerBound().evaluateAsInt());
265 EXPECT_FALSE(unfoldedBoundedUntilFormula.hasUpperBound());
266 EXPECT_EQ("dim0_levelReward", unfoldedBoundedUntilFormula.getTimeBoundReference().getRewardModelName());
267 EXPECT_EQ(expectedValue, getInitialValue(result.model, result.formula));
268}
269
270TEST(RewardBoundUnfolder, DropsTriviallySatisfiedLowerBoundForDtmc) {
271 auto const dtmc = buildDtmc();
272 auto const formula = makeRewardBoundedFormula(0, std::nullopt);
273 auto const expectedValue = getInitialValue(dtmc, formula);
274
276
277 EXPECT_TRUE(result.formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula());
278 EXPECT_EQ(expectedValue, getInitialValue(result.model, result.formula));
279}
280
281TEST(RewardBoundUnfolder, RejectsDimensionSpecificStateSubformulas) {
282 auto const pomdp = buildPomdp();
283 auto const expressionManager = std::make_shared<storm::expressions::ExpressionManager>();
284 auto const trueFormula = std::make_shared<storm::logic::BooleanLiteralFormula>(true);
285 std::vector<std::shared_ptr<storm::logic::Formula const>> leftSubformulas{trueFormula, trueFormula};
286 std::vector<std::shared_ptr<storm::logic::Formula const>> rightSubformulas{std::make_shared<storm::logic::AtomicLabelFormula>("goal"),
287 std::make_shared<storm::logic::AtomicLabelFormula>("init")};
288 std::vector<std::optional<storm::logic::TimeBound>> lowerBounds(2);
289 std::vector<std::optional<storm::logic::TimeBound>> upperBounds{storm::logic::TimeBound{false, expressionManager->integer(1)},
290 storm::logic::TimeBound{false, expressionManager->integer(2)}};
291 std::vector<storm::logic::TimeBoundReference> references(2, storm::logic::TimeBoundReference(boost::optional<std::string>{"r"}));
292 auto const pathFormula = std::make_shared<storm::logic::BoundedUntilFormula>(leftSubformulas, rightSubformulas, lowerBounds, upperBounds, references);
293 auto const formula = std::make_shared<storm::logic::ProbabilityOperatorFormula>(
294 pathFormula, storm::logic::OperatorInformation(boost::optional<storm::solver::OptimizationDirection>{storm::solver::OptimizationDirection::Maximize}));
295
296 STORM_SILENT_EXPECT_THROW(storm::pomdp::analysis::getFormulaInformation(*pomdp, *formula), storm::exceptions::NotSupportedException);
297 STORM_SILENT_EXPECT_THROW(storm::pomdp::transformer::RewardBoundUnfolder<ValueType>::transform(*pomdp, *formula), storm::exceptions::NotSupportedException);
298}
TEST(RewardBoundUnfolder, FullyUnfoldedDtmcPreservesRewardBoundedProbability)
This class represents a discrete-time Markov chain.
Definition Dtmc.h:16
This class represents a (discrete-time) Markov decision process.
Definition Mdp.h:16
Base class for all sparse models.
Definition Model.h:34
This class represents a partially observable Markov decision process.
Definition Pomdp.h:13
This class manages the labeling of the state space with a number of (atomic) labels.
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.
static std::shared_ptr< storm::models::sparse::Pomdp< ValueType > > transformRewardAware(storm::models::sparse::Pomdp< ValueType > const &pomdp, std::set< std::string > const &observableRewardModels, bool dropUnreachableStates=true)
Makes rewards observable by extending observations with the state and state-action reward values of t...
A class that can be used to build a sparse matrix by adding value by value.
storm::modelchecker::CheckTask< storm::logic::Formula, ValueType > createTask(std::shared_ptr< const storm::logic::Formula > const &formula, bool onlyInitialStatesRelevant=false)
std::unique_ptr< storm::modelchecker::CheckResult > verifyWithSparseEngine(storm::Environment const &env, std::shared_ptr< storm::models::sparse::Dtmc< ValueType > > const &dtmc, storm::modelchecker::CheckTask< storm::logic::Formula, ValueType > const &task, bool preferEliminationChecker=false)
SFTBDDChecker::ValueType ValueType
FormulaInformation getFormulaInformation(PomdpType const &pomdp, storm::logic::ProbabilityOperatorFormula const &formula)
ValueType zero()
Definition constants.cpp:26
std::conditional_t< storm::NumberTraits< ValueType >::HasInfinity, ValueType, ExtendedNumber< ValueType > > ExtendedValueType
The type to use for values that may be infinite.
ValueType one()
Definition constants.cpp:21
TargetType convertNumber(SourceType const &number)
#define STORM_SILENT_EXPECT_THROW(statement, expected_exception)
Definition storm_gtest.h:21
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.