7#include "storm-config.h"
37class UmbRoundTripTest :
public ::testing::Test {
39 std::filesystem::path umbFile;
40 void removeUmbFile() {
41 if (!umbFile.empty() && std::filesystem::exists(umbFile)) {
43 std::filesystem::remove(umbFile);
44 ASSERT_EQ(ec.value(), 0) <<
"Unable to remove temporary file " << umbFile <<
" for UMB round trip test: " << ec.message();
48 void setUpUmbFileName() {
50 auto tmp = std::filesystem::temp_directory_path(ec);
51 ASSERT_EQ(ec.value(), 0) <<
"Unable to get temporary directory for UMB round trip test: " << ec.message();
52 std::random_device rd;
54 umbFile = tmp / std::filesystem::path(
"storm_umb_round_trip_test_" + std::to_string(rd()) +
".umb");
55 }
while (std::filesystem::exists(umbFile));
58 template<
typename ValueType>
59 void run(std::filesystem::path
const& prismfile, std::string
const& constants =
"", storm::umb::ExportOptions
const& exportOptions = {}) {
62 storm::umb::ImportOptions importOptions;
63 importOptions.
buildChoiceLabeling = exportOptions.allowChoiceLabelingAsActions || exportOptions.allowChoiceOriginsAsActions;
75 auto builder = storm::builder::ExplicitModelBuilder<ValueType>(program, generatorOptions);
76 auto model = builder.build();
78 auto assertEqualModel = [&model](
auto const& otherModelPtr) {
79 ASSERT_TRUE(otherModelPtr) <<
"No model.";
80 EXPECT_EQ(model->getType(), otherModelPtr->getType());
81 EXPECT_EQ(model->getNumberOfStates(), otherModelPtr->getNumberOfStates());
82 EXPECT_EQ(model->getNumberOfChoices(), otherModelPtr->getNumberOfChoices());
83 EXPECT_EQ(model->getNumberOfTransitions(), otherModelPtr->getNumberOfTransitions());
86 EXPECT_EQ(model->getTransitionMatrix(), otherModelPtr->getTransitionMatrix());
88 EXPECT_EQ(model->getStateLabeling(), otherModelPtr->getStateLabeling());
89 if (model->isNondeterministicModel() && model->hasChoiceLabeling()) {
91 EXPECT_EQ(model->getChoiceLabeling(), otherModelPtr->getChoiceLabeling());
93 EXPECT_EQ(model->getNumberOfRewardModels(), otherModelPtr->getNumberOfRewardModels());
94 for (
auto const& [name, rewardModel] : model->getRewardModels()) {
95 ASSERT_TRUE(otherModelPtr->hasRewardModel(name) || model->hasUniqueRewardModel()) <<
"Other model does not have reward model '" << name <<
"'.";
96 auto const& otherRewardModel = model->hasUniqueRewardModel() ? otherModelPtr->getUniqueRewardModel() : otherModelPtr->getRewardModel(name);
97 if (rewardModel.hasStateRewards()) {
98 ASSERT_TRUE(otherRewardModel.hasStateRewards());
99 EXPECT_EQ(rewardModel.getStateRewardVector(), otherRewardModel.getStateRewardVector());
101 if (rewardModel.hasStateActionRewards()) {
102 ASSERT_TRUE(otherRewardModel.hasStateActionRewards());
103 EXPECT_EQ(rewardModel.getStateActionRewardVector(), otherRewardModel.getStateActionRewardVector());
105 if (rewardModel.hasTransitionRewards()) {
106 ASSERT_TRUE(otherRewardModel.hasTransitionRewards());
107 EXPECT_EQ(rewardModel.getTransitionRewardMatrix(), otherRewardModel.getTransitionRewardMatrix());
110 auto assertEqualValuations = [](
auto const& v,
auto const& other_v) {
111 ASSERT_EQ(v.size(), other_v.size());
112 EXPECT_EQ(0, v.numStrings());
113 EXPECT_EQ(0, other_v.numStrings());
114 ASSERT_EQ(1, v.numClasses());
115 ASSERT_EQ(1, other_v.numClasses());
116 ASSERT_EQ(v.getClassDescription().sizeInBits(), other_v.getClassDescription().sizeInBits());
117 storm::umb::UmbModel::Valuation data = v.getRawUmbData();
118 storm::umb::UmbModel::Valuation other_data = other_v.getRawUmbData();
124 ASSERT_TRUE(model->hasStateValuations());
125 ASSERT_TRUE(otherModelPtr->hasStateValuations());
126 assertEqualValuations(model->getStateValuations().getStorage(), otherModelPtr->getStateValuations().getStorage());
128 if (model->isPartiallyObservable()) {
130 auto pomdp = model->template as<storm::models::sparse::Pomdp<ValueType>>();
131 auto otherPomdp = otherModelPtr->template as<storm::models::sparse::Pomdp<ValueType>>();
132 EXPECT_EQ(pomdp->getObservations(), otherPomdp->getObservations());
133 ASSERT_TRUE(pomdp->hasObservationValuations());
134 ASSERT_TRUE(otherPomdp->hasObservationValuations());
135 assertEqualValuations(pomdp->getObservationValuations().getStorage(), otherPomdp->getObservationValuations().getStorage());
141 umb1.encodeRationals();
142 std::stringstream validationErrors;
143 ASSERT_TRUE(umb1.validate(validationErrors)) << validationErrors.str();
144 validationErrors.clear();
146 assertEqualModel(model1);
151 ASSERT_TRUE(umb2.validate(validationErrors)) << validationErrors.str();
152 validationErrors.clear();
154 assertEqualModel(model2);
158 virtual void SetUp()
override {
160 GTEST_SKIP() <<
"Z3 not available.";
162#ifndef STORM_HAVE_LIBARCHIVE
163 GTEST_SKIP() <<
"LibArchive not available.";
167 virtual void TearDown()
override {
172TEST_F(UmbRoundTripTest, brp_dtmc) {
174 run<double>(STORM_TEST_RESOURCES_DIR
"/dtmc/brp-16-2.pm",
"", options);
175 run<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR
"/dtmc/brp-16-2.pm",
"", options);
176 run<storm::Interval>(STORM_TEST_RESOURCES_DIR
"/dtmc/brp-16-2.pm",
"", options);
177 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR
"/dtmc/brp-16-2.pm",
"", options);
179 run<double>(STORM_TEST_RESOURCES_DIR
"/dtmc/brp-16-2.pm",
"", options);
181 run<double>(STORM_TEST_RESOURCES_DIR
"/dtmc/brp-16-2.pm",
"", options);
183 run<double>(STORM_TEST_RESOURCES_DIR
"/dtmc/brp-16-2.pm",
"", options);
186TEST_F(UmbRoundTripTest, embedded_ctmc) {
188 run<double>(STORM_TEST_RESOURCES_DIR
"/ctmc/embedded2.sm",
"", options);
189 run<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR
"/ma/polling.ma",
"N=3,Q=3", options);
190 run<storm::Interval>(STORM_TEST_RESOURCES_DIR
"/ma/polling.ma",
"N=3,Q=3", options);
191 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR
"/ma/polling.ma",
"N=3,Q=3", options);
194TEST_F(UmbRoundTripTest, firewire_mdp) {
196 run<double>(STORM_TEST_RESOURCES_DIR
"/mdp/firewire3-0.5.nm",
"", options);
197 run<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR
"/mdp/firewire3-0.5.nm",
"", options);
198 run<storm::Interval>(STORM_TEST_RESOURCES_DIR
"/mdp/firewire3-0.5.nm",
"", options);
199 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR
"/mdp/firewire3-0.5.nm",
"", options);
202 run<double>(STORM_TEST_RESOURCES_DIR
"/mdp/firewire3-0.5.nm",
"", options);
205TEST_F(UmbRoundTripTest, polling_ma) {
207 run<double>(STORM_TEST_RESOURCES_DIR
"/ma/polling.ma",
"N=3,Q=3", options);
208 run<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR
"/ma/polling.ma",
"N=3,Q=3", options);
209 run<storm::Interval>(STORM_TEST_RESOURCES_DIR
"/ma/polling.ma",
"N=3,Q=3", options);
210 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR
"/ma/polling.ma",
"N=3,Q=3", options);
213TEST_F(UmbRoundTripTest, robot_imdp) {
215 run<storm::Interval>(STORM_TEST_RESOURCES_DIR
"/imdp/robot.prism",
"delta=0.5", options);
216 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR
"/imdp/robot.prism",
"delta=0.5", options);
219TEST_F(UmbRoundTripTest, maze_pomdp) {
221 run<double>(STORM_TEST_RESOURCES_DIR
"/pomdp/maze2.prism",
"sl=0.5", options);
222 run<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR
"/pomdp/maze2.prism",
"sl=0.5", options);
223 run<storm::Interval>(STORM_TEST_RESOURCES_DIR
"/pomdp/maze2.prism",
"sl=0.5", options);
224 run<storm::RationalInterval>(STORM_TEST_RESOURCES_DIR
"/pomdp/maze2.prism",
"sl=0.5", options);
229TEST(UmbTest, RationalEncoding) {
235 std::vector<storm::RationalNumber> values(17);
243 values[6] = int64max / uint64max;
244 values[7] = int64min / uint64max;
246 auto const simpleRationals = std::span<storm::RationalNumber>(values.data(), 8);
251 ASSERT_EQ(simpleRationals.size(), decoded1.size());
252 for (
size_t i = 0; i < simpleRationals.size(); ++i) {
253 EXPECT_EQ(simpleRationals[i], decoded1[i]) <<
" at index " << i;
257 values[8] = int64max + one;
258 values[9] = one / (uint64max + one);
259 values[10] = int64min - one;
260 values[11] = one / (int64min - one);
261 values[12] = (int64min - one) / (uint64max + one);
263 "949667607787274453086419753000949667607787274453086419753000949667607787274453086419753000949667607787274453086419753000949667607787274453086419753000"
264 "9496676077872744530864197530009496676077872744530864197530009496676077872744530864197530/"
265 "780116505469339517040847240228739241622101546262265311616467711470010820006007800398204693387501962318501358930877102188539546463329577703105788853954"
266 "134811616465520508472358467546262155762385699576193087775947700108638258539546825539241654967");
267 values[14] = one / values[13];
268 values[15] = -values[13];
269 values[16] = -values[14];
276 ASSERT_EQ(values.size(), decoded2.size());
277 for (
size_t i = 0; i < values.size(); ++i) {
278 EXPECT_EQ(values[i], decoded2[i]) <<
" at index " << i;
286TEST(UmbTest, DuplicateBranches) {
298 initialStates.
set(0);
300 labeling.
addLabel(
"init", std::move(initialStates));
301 std::optional<std::vector<double>> noStateRewards, noStateActionRewards;
302 std::optional<storm::storage::SparseMatrix<double>> transitionRewards = rewardMatrixBuilder.
build();
303 std::unordered_map<std::string, storm::models::sparse::StandardRewardModel<double>> rewardModels;
304 rewardModels.emplace(
312 auto createUmbWithDuplicateBranch = [&model](
double rewardOfSecondPart) {
315 auto& probabilities = umb.branchToProbability.get<
double>();
316 auto& branchRewards = umb.rewards().value().begin()->second.branches.value().values.get<
double>();
317 EXPECT_EQ((std::vector<uint64_t>{1, 2, 1, 2}), targets);
318 EXPECT_EQ((std::vector<double>{0.5, 0.5, 1.0, 1.0}), probabilities);
319 EXPECT_EQ((std::vector<double>{3.0, 5.0, 0.0, 0.0}), branchRewards);
320 targets.insert(targets.begin(), 1);
321 probabilities[0] = 0.25;
322 probabilities.insert(probabilities.begin(), 0.25);
323 branchRewards.insert(branchRewards.begin(), 3.0);
324 branchRewards[1] = rewardOfSecondPart;
325 for (
auto& branchIndex : umb.choiceToBranches.value()) {
326 if (branchIndex > 0) {
330 umb.index.transitionSystem.numBranches += 1;
336 auto umb = createUmbWithDuplicateBranch(3.0);
337 std::stringstream validationErrors;
338 ASSERT_TRUE(umb.validate(validationErrors)) << validationErrors.str();
340 ASSERT_TRUE(importedModel);
341 EXPECT_EQ(3ul, importedModel->getNumberOfStates());
342 EXPECT_EQ(3ul, importedModel->getNumberOfChoices());
343 EXPECT_EQ(4ul, importedModel->getNumberOfTransitions());
345 ASSERT_TRUE(importedModel->hasUniqueRewardModel());
346 auto const& importedRewardModel = importedModel->getUniqueRewardModel();
347 ASSERT_TRUE(importedRewardModel.hasTransitionRewards());
348 EXPECT_EQ(model.
getUniqueRewardModel().getTransitionRewardMatrix(), importedRewardModel.getTransitionRewardMatrix());
352 auto umb = createUmbWithDuplicateBranch(4.0);
363 builder.newRowGroup(0);
364 builder.addNextValue(0, 0, 0.5);
365 builder.addNextValue(0, 1, 0.5);
366 builder.addNextValue(1, 1, 1.0);
367 builder.newRowGroup(2);
368 builder.addNextValue(2, 1, 1.0);
370 initialStates.set(0);
372 stateLabeling.addLabel(
"init", std::move(initialStates));
374 components.choiceLabeling.emplace(3);
375 for (
auto const& [label, choices] : labels) {
377 for (
auto const choice : choices) {
378 choiceBits.set(choice);
380 components.choiceLabeling->addLabel(label, std::move(choiceBits));
386 std::vector<std::string> result;
388 result.emplace_back(action);
398TEST(UmbTest, ChoiceLabelingExport) {
400 std::string description;
401 std::map<std::string, std::vector<uint64_t>> labels;
402 uint64_t expectedNumActions;
403 std::vector<uint32_t> expectedChoiceToAction;
404 std::vector<std::string> expectedActionStrings;
406 std::vector<TestCase>
const testCases{{
"no labels", {}, 1, {0, 0, 0}, {
""}},
407 {
"label that is not assigned to any choice", {{
"a", {}}}, 1, {0, 0, 0}, {
""}},
408 {
"some choices unlabeled", {{
"a", {0}}}, 2, {1, 0, 0}, {
"",
"a"}},
409 {
"all choices have the same label", {{
"a", {0, 1, 2}}}, 1, {0, 0, 0}, {
"a"}},
410 {
"all choices labeled with different labels", {{
"a", {0}}, {
"b", {1, 2}}}, 2, {0, 1, 1}, {
"a",
"b"}}};
411 for (
auto const& testCase : testCases) {
412 SCOPED_TRACE(testCase.description);
413 auto const mdp = createMdpWithChoiceLabeling(testCase.labels);
418 std::stringstream validationErrors;
419 EXPECT_TRUE(umb.
validate(validationErrors)) << validationErrors.str();
423 EXPECT_EQ(testCase.expectedChoiceToAction, umb.
choiceActions->values.value());
424 EXPECT_EQ(testCase.expectedActionStrings, getChoiceActionStrings(umb));
428 ASSERT_TRUE(imported);
429 ASSERT_TRUE(imported->hasChoiceLabeling());
430 auto const& importedLabeling = imported->getChoiceLabeling();
431 std::size_t numNonEmptyLabels = 0;
432 for (
auto const& [label, choices] : testCase.labels) {
433 if (choices.empty()) {
437 ASSERT_TRUE(importedLabeling.containsLabel(label));
439 for (
auto const choice : choices) {
440 expectedChoices.
set(choice);
442 EXPECT_EQ(expectedChoices, importedLabeling.getChoices(label));
444 EXPECT_EQ(numNonEmptyLabels, importedLabeling.getNumberOfLabels());
TEST_F(AssumptionCheckerTest, Brp_no_bisimulation)
TEST(UmbTest, RationalEncoding)
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?
This class represents a discrete-time Markov chain.
void addLabel(std::string const &label)
Adds a new label to the labelings.
This class represents a (discrete-time) Markov decision process.
Base class for all sparse models.
RewardModelType const & getUniqueRewardModel() const
Retrieves the unique reward model, if there exists exactly one.
storm::storage::SparseMatrix< ValueType > const & getTransitionMatrix() const
Retrieves the matrix representing the transitions of the model.
This class manages the labeling of the state space with a number of (atomic) labels.
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...
A bit vector that is internally represented as a vector of 64-bit values.
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.
SparseMatrix< value_type > build(index_type overriddenRowCount=0, index_type overriddenColumnCount=0, index_type overriddenRowGroupCount=0)
Represents a model in the UMB format.
bool validate(std::ostream &errors) const
Validates the given UMB model and writes potential errors to the given output stream.
std::optional< ActionLabels > choiceActions
TO1< uint64_t > branchToTarget
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)
auto stringVectorView(SEQ< char >::value_type const &strings, CSR::value_type const &stringMapping)
template std::shared_ptr< storm::models::sparse::Model< double > > sparseModelFromUmb< double >(storm::umb::UmbModel const &umbModel, ImportOptions const &options)
TargetType convertNumber(SourceType const &number)
#define STORM_SILENT_EXPECT_THROW(statement, expected_exception)
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.
uint64_t numChoiceActions
struct storm::umb::ModelIndex::TransitionSystem transitionSystem
TO1< uint32_t > valuationToClass