Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
UmbTest.cpp
Go to the documentation of this file.
1#include <filesystem>
2#include <limits>
3#include <random>
4#include <span>
5#include <vector>
6
7#include "storm-config.h"
21#include "test/storm_gtest.h"
25namespace {
26class UmbRoundTripTest : public ::testing::Test {
27 protected:
28 std::filesystem::path umbFile;
29 void removeUmbFile() {
30 if (!umbFile.empty() && std::filesystem::exists(umbFile)) {
31 std::error_code ec;
32 std::filesystem::remove(umbFile);
33 ASSERT_EQ(ec.value(), 0) << "Unable to remove temporary file " << umbFile << " for UMB round trip test: " << ec.message();
34 }
35 }
36
37 void setUpUmbFileName() {
38 std::error_code ec;
39 auto tmp = std::filesystem::temp_directory_path(ec);
40 ASSERT_EQ(ec.value(), 0) << "Unable to get temporary directory for UMB round trip test: " << ec.message();
41 std::random_device rd;
42 do {
43 umbFile = tmp / std::filesystem::path("storm_umb_round_trip_test_" + std::to_string(rd()) + ".umb");
44 } while (std::filesystem::exists(umbFile));
45 }
46
47 template<typename ValueType>
48 void run(std::filesystem::path const& prismfile, std::string const& constants = "", storm::umb::ExportOptions const& exportOptions = {}) {
49 setUpUmbFileName();
50 // Set-up options structs
51 storm::umb::ImportOptions importOptions;
52 importOptions.buildChoiceLabeling = exportOptions.allowChoiceLabelingAsActions || exportOptions.allowChoiceOriginsAsActions;
54 generatorOptions.setBuildChoiceLabels(exportOptions.allowChoiceLabelingAsActions);
55 generatorOptions.setBuildChoiceOrigins(exportOptions.allowChoiceOriginsAsActions);
56 generatorOptions.setBuildAllRewardModels();
57 generatorOptions.setBuildAllLabels();
58 generatorOptions.setBuildStateValuations(true);
59 generatorOptions.setBuildObservationValuations(true);
60
61 // build model from prism file
62 storm::prism::Program program = storm::parser::PrismParser::parse(prismfile, true);
63 program = program.preprocess(constants);
64 auto builder = storm::builder::ExplicitModelBuilder<ValueType>(program, generatorOptions);
65 auto model = builder.build();
66
67 auto assertEqualModel = [&model](auto const& otherModelPtr) {
68 ASSERT_TRUE(otherModelPtr) << "No model.";
69 EXPECT_EQ(model->getType(), otherModelPtr->getType());
70 EXPECT_EQ(model->getNumberOfStates(), otherModelPtr->getNumberOfStates());
71 EXPECT_EQ(model->getNumberOfChoices(), otherModelPtr->getNumberOfChoices());
72 EXPECT_EQ(model->getNumberOfTransitions(), otherModelPtr->getNumberOfTransitions());
73 if (!model->isOfType(storm::models::ModelType::Ctmc) || std::is_same_v<ValueType, storm::RationalNumber>) {
74 // Round trip in CTMC case may yield slightly different probabilities because we translate between rates and probabilities
75 EXPECT_EQ(model->getTransitionMatrix(), otherModelPtr->getTransitionMatrix());
76 }
77 EXPECT_EQ(model->getStateLabeling(), otherModelPtr->getStateLabeling());
78 if (model->isNondeterministicModel() && model->hasChoiceLabeling()) {
79 // This test does not work for deterministic models as we might fuse multiple (overlapping) choice labels together
80 EXPECT_EQ(model->getChoiceLabeling(), otherModelPtr->getChoiceLabeling());
81 }
82 EXPECT_EQ(model->getNumberOfRewardModels(), otherModelPtr->getNumberOfRewardModels());
83 for (auto const& [name, rewardModel] : model->getRewardModels()) {
84 ASSERT_TRUE(otherModelPtr->hasRewardModel(name) || model->hasUniqueRewardModel()) << "Other model does not have reward model '" << name << "'.";
85 auto const& otherRewardModel = model->hasUniqueRewardModel() ? otherModelPtr->getUniqueRewardModel() : otherModelPtr->getRewardModel(name);
86 if (rewardModel.hasStateRewards()) {
87 ASSERT_TRUE(otherRewardModel.hasStateRewards());
88 EXPECT_EQ(rewardModel.getStateRewardVector(), otherRewardModel.getStateRewardVector());
89 }
90 if (rewardModel.hasStateActionRewards()) {
91 ASSERT_TRUE(otherRewardModel.hasStateActionRewards());
92 EXPECT_EQ(rewardModel.getStateActionRewardVector(), otherRewardModel.getStateActionRewardVector());
93 }
94 if (rewardModel.hasTransitionRewards()) {
95 ASSERT_TRUE(otherRewardModel.hasTransitionRewards());
96 EXPECT_EQ(rewardModel.getTransitionRewardMatrix(), otherRewardModel.getTransitionRewardMatrix());
97 }
98 }
99 auto assertEqualValuations = [](auto const& v, auto const& other_v) {
100 ASSERT_EQ(v.size(), other_v.size());
101 EXPECT_EQ(0, v.numStrings());
102 EXPECT_EQ(0, other_v.numStrings());
103 ASSERT_EQ(1, v.numClasses());
104 ASSERT_EQ(1, other_v.numClasses());
105 ASSERT_EQ(v.getClassDescription().sizeInBits(), other_v.getClassDescription().sizeInBits());
106 storm::umb::UmbModel::Valuation data = v.getRawUmbData();
107 storm::umb::UmbModel::Valuation other_data = other_v.getRawUmbData();
108 EXPECT_EQ(data.valuations, other_data.valuations);
109 EXPECT_EQ(data.stringMapping, other_data.stringMapping);
110 EXPECT_EQ(data.strings == other_data.strings, true);
111 EXPECT_EQ(data.valuationToClass, other_data.valuationToClass);
112 };
113 ASSERT_TRUE(model->hasStateValuations());
114 ASSERT_TRUE(otherModelPtr->hasStateValuations());
115 assertEqualValuations(model->getStateValuations().getStorage(), otherModelPtr->getStateValuations().getStorage());
116 // POMDP specific things
117 if (model->isPartiallyObservable()) {
118 ASSERT_TRUE(model->isOfType(storm::models::ModelType::Pomdp));
119 auto pomdp = model->template as<storm::models::sparse::Pomdp<ValueType>>();
120 auto otherPomdp = otherModelPtr->template as<storm::models::sparse::Pomdp<ValueType>>();
121 EXPECT_EQ(pomdp->getObservations(), otherPomdp->getObservations());
122 ASSERT_TRUE(pomdp->hasObservationValuations());
123 ASSERT_TRUE(otherPomdp->hasObservationValuations());
124 assertEqualValuations(pomdp->getObservationValuations().getStorage(), otherPomdp->getObservationValuations().getStorage());
125 }
126 };
127
128 // Short round trip: model -> umb -> model
129 auto umb1 = storm::umb::sparseModelToUmb(*model, exportOptions);
130 umb1.encodeRationals();
131 std::stringstream validationErrors;
132 ASSERT_TRUE(umb1.validate(validationErrors)) << validationErrors.str();
133 validationErrors.clear();
134 auto model1 = storm::umb::sparseModelFromUmb<ValueType>(umb1, importOptions);
135 assertEqualModel(model1);
136
137 // long round trip: model -> umb -> file -> umb -> model
138 storm::umb::toArchive(umb1, umbFile, exportOptions);
139 auto umb2 = storm::umb::importUmb(umbFile, importOptions);
140 ASSERT_TRUE(umb2.validate(validationErrors)) << validationErrors.str();
141 validationErrors.clear();
142 auto model2 = storm::umb::sparseModelFromUmb<ValueType>(umb2, importOptions);
143 assertEqualModel(model2);
144 removeUmbFile();
145 }
146
147 virtual void SetUp() override {
148#ifndef STORM_HAVE_Z3
149 GTEST_SKIP() << "Z3 not available.";
150#endif
151#ifndef STORM_HAVE_LIBARCHIVE
152 GTEST_SKIP() << "LibArchive not available.";
153#endif
154 }
155
156 virtual void TearDown() override {
157 removeUmbFile();
158 }
159};
160
161TEST_F(UmbRoundTripTest, brp_dtmc) {
163 run<double>(STORM_TEST_RESOURCES_DIR "/dtmc/brp-16-2.pm", "", options);
164 run<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR "/dtmc/brp-16-2.pm", "", options);
165 run<storm::Interval>(STORM_TEST_RESOURCES_DIR "/dtmc/brp-16-2.pm", "", options);
166 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR "/dtmc/brp-16-2.pm", "", options);
168 run<double>(STORM_TEST_RESOURCES_DIR "/dtmc/brp-16-2.pm", "", options);
170 run<double>(STORM_TEST_RESOURCES_DIR "/dtmc/brp-16-2.pm", "", options);
172 run<double>(STORM_TEST_RESOURCES_DIR "/dtmc/brp-16-2.pm", "", options);
173}
174
175TEST_F(UmbRoundTripTest, embedded_ctmc) {
177 run<double>(STORM_TEST_RESOURCES_DIR "/ctmc/embedded2.sm", "", options);
178 run<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR "/ma/polling.ma", "N=3,Q=3", options);
179 run<storm::Interval>(STORM_TEST_RESOURCES_DIR "/ma/polling.ma", "N=3,Q=3", options);
180 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR "/ma/polling.ma", "N=3,Q=3", options);
181}
182
183TEST_F(UmbRoundTripTest, firewire_mdp) {
185 run<double>(STORM_TEST_RESOURCES_DIR "/mdp/firewire3-0.5.nm", "", options);
186 run<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR "/mdp/firewire3-0.5.nm", "", options);
187 run<storm::Interval>(STORM_TEST_RESOURCES_DIR "/mdp/firewire3-0.5.nm", "", options);
188 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR "/mdp/firewire3-0.5.nm", "", options);
189 options.allowChoiceOriginsAsActions = true;
190 options.allowChoiceLabelingAsActions = false;
191 run<double>(STORM_TEST_RESOURCES_DIR "/mdp/firewire3-0.5.nm", "", options);
192}
193
194TEST_F(UmbRoundTripTest, polling_ma) {
196 run<double>(STORM_TEST_RESOURCES_DIR "/ma/polling.ma", "N=3,Q=3", options);
197 run<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR "/ma/polling.ma", "N=3,Q=3", options);
198 run<storm::Interval>(STORM_TEST_RESOURCES_DIR "/ma/polling.ma", "N=3,Q=3", options);
199 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR "/ma/polling.ma", "N=3,Q=3", options);
200}
201
202TEST_F(UmbRoundTripTest, robot_imdp) {
204 run<storm::Interval>(STORM_TEST_RESOURCES_DIR "/imdp/robot.prism", "delta=0.5", options);
205 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR "/imdp/robot.prism", "delta=0.5", options);
206}
207
208TEST_F(UmbRoundTripTest, maze_pomdp) {
210 run<double>(STORM_TEST_RESOURCES_DIR "/pomdp/maze2.prism", "sl=0.5", options);
211 run<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR "/pomdp/maze2.prism", "sl=0.5", options);
212 run<storm::Interval>(STORM_TEST_RESOURCES_DIR "/pomdp/maze2.prism", "sl=0.5", options);
213 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR "/pomdp/maze2.prism", "sl=0.5", options);
214}
215
216} // namespace
217
218TEST(UmbTest, RationalEncoding) {
220 auto const int64max = storm::utility::convertNumber<storm::RationalNumber, int64_t>(std::numeric_limits<int64_t>::max());
221 auto const int64min = storm::utility::convertNumber<storm::RationalNumber, int64_t>(std::numeric_limits<int64_t>::min());
222 auto const uint64max = storm::utility::convertNumber<storm::RationalNumber, uint64_t>(std::numeric_limits<uint64_t>::max());
223
224 std::vector<storm::RationalNumber> values(17);
225 // The first 8 values are chosen such that they can be represented with two 64-bit numbers.
228 values[2] = one;
229 values[3] = -one;
232 values[6] = int64max / uint64max;
233 values[7] = int64min / uint64max;
234
235 auto const simpleRationals = std::span<storm::RationalNumber>(values.data(), 8);
236 ASSERT_EQ(128ull, storm::umb::ValueEncoding::getMinimalRationalSize(simpleRationals, false));
237 ASSERT_EQ(128ull, storm::umb::ValueEncoding::getMinimalRationalSize(simpleRationals, true));
238 auto encoded1 = storm::umb::ValueEncoding::createUint64FromRationalRange(simpleRationals, 128ull);
239 auto decoded1 = storm::umb::ValueEncoding::uint64ToRationalRangeView(encoded1, 128ull);
240 ASSERT_EQ(simpleRationals.size(), decoded1.size());
241 for (size_t i = 0; i < simpleRationals.size(); ++i) {
242 EXPECT_EQ(simpleRationals[i], decoded1[i]) << " at index " << i;
243 }
244
245 // The following values are chosen such that they are not representable with two 64-bit numbers.
246 values[8] = int64max + one;
247 values[9] = one / (uint64max + one);
248 values[10] = int64min - one;
249 values[11] = one / (int64min - one);
250 values[12] = (int64min - one) / (uint64max + one);
252 "949667607787274453086419753000949667607787274453086419753000949667607787274453086419753000949667607787274453086419753000949667607787274453086419753000"
253 "9496676077872744530864197530009496676077872744530864197530009496676077872744530864197530/"
254 "780116505469339517040847240228739241622101546262265311616467711470010820006007800398204693387501962318501358930877102188539546463329577703105788853954"
255 "134811616465520508472358467546262155762385699576193087775947700108638258539546825539241654967");
256 values[14] = one / values[13];
257 values[15] = -values[13];
258 values[16] = -values[14];
259
260 ASSERT_EQ(1616ull, storm::umb::ValueEncoding::getMinimalRationalSize(values, false));
261 ASSERT_EQ(1664ull, storm::umb::ValueEncoding::getMinimalRationalSize(values, true));
262 auto encoded2 = storm::umb::ValueEncoding::createUint64FromRationalRange(values, 1664ull);
263 auto decoded2 = storm::umb::ValueEncoding::uint64ToRationalRangeView(encoded2, 1664ull);
264
265 ASSERT_EQ(values.size(), decoded2.size());
266 for (size_t i = 0; i < values.size(); ++i) {
267 EXPECT_EQ(values[i], decoded2[i]) << " at index " << i;
268 }
269}
TEST_F(AssumptionCheckerTest, Brp_no_bisimulation)
TEST(UmbTest, RationalEncoding)
Definition UmbTest.cpp:218
BuilderOptions & setBuildAllLabels(bool newValue=true)
Should all reward models be built?
BuilderOptions & setBuildChoiceLabels(bool newValue=true)
Should the choice labels be built?
BuilderOptions & setBuildAllRewardModels(bool newValue=true)
Should all reward models be built?
BuilderOptions & setBuildChoiceOrigins(bool newValue=true)
Should the origins the different choices be built?
BuilderOptions & setBuildStateValuations(bool newValue=true)
Should the state valuation mapping be built?
BuilderOptions & setBuildObservationValuations(bool newValue=true)
Should a observation valuation mapping be built?
static storm::prism::Program parse(std::string const &filename, bool prismCompatability=false)
Parses the given file into the PRISM storage classes assuming it complies with the PRISM syntax.
Program preprocess(std::map< storm::expressions::Variable, storm::expressions::Expression > const &constantDefinitions) const
Preprocesses the program by defining the given constant definitions, substituting constants and formu...
Definition Program.cpp:1170
static uint64_t getMinimalRationalSize(InputRange &&input, bool multiplesOf64)
static std::vector< uint64_t > createUint64FromRationalRange(InputRange &&input, uint64_t const numberSize)
static auto uint64ToRationalRangeView(InputRange &&input, uint64_t const numberSize)
storm::builder::BuilderOptions NextStateGeneratorOptions
storm::umb::UmbModel importUmb(std::filesystem::path const &umbLocation, ImportOptions const &options)
void toArchive(storm::umb::UmbModel const &umbModel, std::filesystem::path const &archivePath, ExportOptions const &options)
std::shared_ptr< storm::models::sparse::Model< ValueType > > sparseModelFromUmb(storm::umb::UmbModel const &umbModel, ImportOptions const &options)
Constructs a sparse model from the given UMB model.
storm::umb::UmbModel sparseModelToUmb(storm::models::sparse::Model< ValueType > const &model, ExportOptions const &options)
ValueType zero()
Definition constants.cpp:24
ValueType one()
Definition constants.cpp:19
TargetType convertNumber(SourceType const &number)
bool allowChoiceLabelingAsActions
Whether export of choice origins is enabled.
bool allowChoiceOriginsAsActions
Whether export of choice origins is enabled.
storm::io::CompressionMode compression
The type of compression used for the exported UMB model.
bool buildChoiceLabeling
Controls building of choice labelings.
TO1< uint32_t > valuationToClass
Definition UmbModel.h:70