Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
ExportCheckResultTest.cpp
Go to the documentation of this file.
1#include "storm-config.h"
2#include "test/storm_gtest.h"
3
4#include <filesystem>
5#include <fstream>
6#include <random>
7#include <sstream>
8
13#include "storm/api/builder.h"
14#include "storm/api/export.h"
26
27namespace {
28
29// From state 1 the target is never reached, so the expected reward is infinite there and in state 0, which reaches
30// state 1 with probability 1/2. States 2 and 3 have a finite expected reward of 1 and 0, respectively.
31//
32// Every state is initial: a program that pins the initial state down instead has its initial states enumerated by an
33// SMT solver, which this test would then need a Storm built with one to run at all. Which states are initial makes no
34// difference to what is checked below, since the check result and its export cover all of them either way.
35std::string const modelDescription = R"(
36dtmc
37
38module main
39 s : [0..3];
40 [] s=0 -> 1/2 : (s'=1) + 1/2 : (s'=2);
41 [] s=1 -> 1 : (s'=1);
42 [] s=2 -> 1 : (s'=3);
43 [] s=3 -> 1 : (s'=3);
44endmodule
45
46init
47 true
48endinit
49
50label "target" = s=3;
51
52rewards "steps"
53 s<3 : 1;
54endrewards
55)";
56
57std::filesystem::path getTemporaryFilePath() {
58 std::error_code ec;
59 auto tmpDir = std::filesystem::temp_directory_path(ec);
60 EXPECT_EQ(0, ec.value()) << "Unable to get temporary directory for check result export test: " << ec.message();
61 std::random_device rd;
62 std::filesystem::path result;
63 do {
64 result = tmpDir / std::filesystem::path("storm_check_result_export_test_" + std::to_string(rd()) + ".json");
65 } while (std::filesystem::exists(result));
66 return result;
67}
68
74template<typename ValueType>
75void runExportTest(storm::Environment const& env) {
76 storm::prism::Program program = storm::parser::PrismParser::parseFromString(modelDescription, "testfile");
77 auto formulas = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram("R{\"steps\"}=? [F \"target\"]", program));
78 ASSERT_EQ(1ull, formulas.size());
79 auto model = storm::api::buildSparseModel<ValueType>(program, formulas)->template as<storm::models::sparse::Dtmc<ValueType>>();
80 ASSERT_EQ(4ull, model->getNumberOfStates());
81
83 std::unique_ptr<storm::modelchecker::CheckResult> result = checker.check(env, *formulas[0]);
84 ASSERT_TRUE(result->isExplicitQuantitativeCheckResult());
85 auto const& values = result->template asExplicitQuantitativeCheckResult<ValueType>().getValueVector();
86 ASSERT_EQ(4ull, values.size());
87
88 // Two states cannot reach the target, so their expected reward is infinite. Without those, the export below would
89 // not exercise anything.
90 uint64_t numberOfInfiniteValues = 0;
91 for (auto const& value : values) {
92 if (storm::utility::isInfinity(value)) {
93 ++numberOfInfiniteValues;
94 }
95 }
96 ASSERT_EQ(2ull, numberOfInfiniteValues);
97
98 auto const path = getTemporaryFilePath();
99 ASSERT_NO_THROW(storm::api::exportCheckResultToJson<ValueType>(model, result, path.string()));
100 std::ifstream exportedStream(path);
101 ASSERT_TRUE(exportedStream.good());
102 storm::json<double> exported;
103 ASSERT_NO_THROW(exportedStream >> exported);
104 exportedStream.close();
105 std::filesystem::remove(path);
106
107 ASSERT_TRUE(exported.is_array());
108 ASSERT_EQ(values.size(), exported.size());
109 for (auto const& entry : exported) {
110 ASSERT_TRUE(entry.count("s") == 1 && entry.count("v") == 1);
111 uint64_t const state = entry.at("s").template get<uint64_t>();
112 ASSERT_LT(state, values.size());
113 if (storm::utility::isInfinity(values[state])) {
114 // JSON has no infinity of its own, so an infinite value is exported as this string.
115 ASSERT_TRUE(entry.at("v").is_string()) << "State " << state << " has an infinite value, but was exported as " << entry.at("v").dump() << ".";
116 EXPECT_EQ("inf", entry.at("v").template get<std::string>());
117 } else {
118 ASSERT_TRUE(entry.at("v").is_number()) << "State " << state << " has the finite value " << values[state] << ", but was exported as "
119 << entry.at("v").dump() << ".";
120 EXPECT_EQ(storm::utility::convertNumber<double>(storm::utility::getFinite(values[state])), entry.at("v").template get<double>());
121 }
122 }
123}
124} // namespace
125
130template<typename ValueType>
132 typedef storm::utility::ExtendedValueType<ValueType> ExtendedValueType;
136
137 storm::json<ValueType> exported;
138 ASSERT_NO_THROW(exported = result.toJson());
139 ASSERT_TRUE(exported.is_array());
140 ASSERT_EQ(3ull, exported.size());
141
142 EXPECT_TRUE(exported[0].at("v").is_number());
143 // JSON has no infinity of its own, and the two of them must not be exported as the same thing.
144 ASSERT_TRUE(exported[1].at("v").is_string()) << "The negative infinity was exported as " << exported[1].at("v").dump() << ".";
145 EXPECT_EQ("-inf", exported[1].at("v").template get<std::string>());
146 ASSERT_TRUE(exported[2].at("v").is_string()) << "The positive infinity was exported as " << exported[2].at("v").dump() << ".";
147 EXPECT_EQ("inf", exported[2].at("v").template get<std::string>());
148}
149
150TEST(ExportCheckResultTest, NegativeInfinityDouble) {
152}
153
154TEST(ExportCheckResultTest, NegativeInfinityExact) {
156}
157
163TEST(ExportCheckResultTest, PrintsTheApproximationOfAnExactValue) {
165 storm::RationalNumber const third = storm::RationalNumber(1) / storm::RationalNumber(3);
166 storm::modelchecker::ExplicitQuantitativeCheckResult<storm::RationalNumber> result(std::vector<ExtendedValueType>{ExtendedValueType(third)});
167
168 std::stringstream out;
169 result.writeToStream(out);
170 EXPECT_NE(std::string::npos, out.str().find("1/3")) << "The exact value is missing from " << out.str() << ".";
171 EXPECT_NE(std::string::npos, out.str().find("(approx.")) << "The approximation is missing from " << out.str() << ".";
172}
173
177TEST(ExportCheckResultTest, PrintsNoApproximationOfAnInexactValue) {
178 storm::modelchecker::ExplicitQuantitativeCheckResult<double> result(std::vector<double>{0.25});
179
180 std::stringstream out;
181 result.writeToStream(out);
182 EXPECT_EQ(std::string::npos, out.str().find("(approx.")) << "An approximation was printed for " << out.str() << ".";
183}
184
188TEST(ExportCheckResultTest, RangeOutputShowsBounds) {
189 std::vector<double> values(10, 0.5);
190 values.front() = 0.25;
192 std::vector<double> lower(10, 0.4);
193 lower.front() = 0.2;
194 result.setLowerBounds(storm::utility::widen(std::move(lower)));
195
196 std::stringstream out;
197 result.writeToStream(out);
198 EXPECT_EQ("[0.25, 0.5] (range). Solution bounds: [0.2, ?].", out.str());
199}
200
204TEST(ExportCheckResultTest, ExactSolutionIsAnnouncedInsteadOfItsBounds) {
205 storm::modelchecker::ExplicitQuantitativeCheckResult<double> result(std::vector<double>{0.25});
206 result.setValuesExact();
207
208 std::stringstream out;
209 result.writeToStream(out);
210 EXPECT_EQ("0.25. Exact solution.", out.str());
211}
212
213TEST(ExportCheckResultTest, RangeOutputAnnouncesAnExactSolution) {
214 std::vector<double> values(10, 0.5);
215 values.front() = 0.25;
217 result.setValuesExact();
218
219 std::stringstream out;
220 result.writeToStream(out);
221 EXPECT_EQ("[0.25, 0.5] (range). Exact solution.", out.str());
222}
223
224TEST(ExportCheckResultTest, InfiniteRewardDouble) {
226 runExportTest<double>(env);
227}
228
229TEST(ExportCheckResultTest, InfiniteRewardExact) {
231 env.solver().setLinearEquationSolverType(storm::solver::EquationSolverType::Eigen);
232 env.solver().eigen().setMethod(storm::solver::EigenLinearEquationSolverMethod::SparseLU);
233 runExportTest<storm::RationalNumber>(env);
234}
TEST(ExportCheckResultTest, NegativeInfinityDouble)
void runNegativeInfinityExportTest()
No model checker hands out a negative infinity today, so this builds the check result directly.
void setMethod(storm::solver::EigenLinearEquationSolverMethod value, bool isSetFromDefault=false)
SolverEnvironment & solver()
void setLinearEquationSolverType(storm::solver::EquationSolverType const &value, bool isSetFromDefault=false)
EigenSolverEnvironment & eigen()
A quantitative check result over the states of a sparse model.
virtual std::ostream & writeToStream(std::ostream &out) const override
void setLowerBounds(vector_type lowerBounds)
Sets sound bounds on the actual values.
storm::json< ValueType > toJson(std::optional< storm::storage::sparse::Valuations > const &stateValuations=std::nullopt, std::optional< storm::models::sparse::StateLabeling > const &stateLabels=std::nullopt) const
void setValuesExact()
States that the values of this result are the exact ones, i.e.
static storm::prism::Program parseFromString(std::string const &input, std::string const &filename, bool prismCompatability=false)
Parses the given input stream into the PRISM storage classes assuming it complies with the PRISM synt...
std::vector< storm::jani::Property > parsePropertiesForPrismProgram(std::string const &inputString, storm::prism::Program const &program, boost::optional< std::set< std::string > > const &propertyFilter)
std::shared_ptr< storm::models::sparse::Model< ValueType > > buildSparseModel(storm::storage::SymbolicModelDescription const &model, storm::builder::BuilderOptions const &options, typename storm::builder::ExplicitModelBuilder< ValueType >::Options const &explorationOptions=typename storm::builder::ExplicitModelBuilder< ValueType >::Options())
Definition builder.h:117
std::vector< std::shared_ptr< storm::logic::Formula const > > extractFormulasFromProperties(std::vector< storm::jani::Property > const &properties)
void exportCheckResultToJson(std::shared_ptr< storm::models::sparse::Model< ValueType > > const &model, std::unique_ptr< storm::modelchecker::CheckResult > const &checkResult, std::string const &filename)
Definition export.h:127
ExtendedValueType< ValueType > positiveInfinity()
std::vector< ExtendedValueType< ValueType > > widen(std::vector< ValueType > &&values)
Widens a vector of finite values into the extended value type.
ExtendedValueType< ValueType > negativeInfinity()
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
bool isInfinity(ValueType const &a)
ValueType const & getFinite(ExtendedNumber< ValueType > const &value)
TargetType convertNumber(SourceType const &number)
nlohmann::basic_json< std::map, std::vector, std::string, bool, int64_t, uint64_t, ValueType > json
Definition JsonForward.h:11