Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
MarkovAutomatonCslModelCheckerTest.cpp
Go to the documentation of this file.
1#include "storm-config.h"
2#include "test/storm_gtest.h"
3
7#include "storm/api/builder.h"
26
27namespace {
28
29enum class MaEngine { PrismSparse, JaniSparse, JaniHybrid };
30
31class SparseDoubleValueIterationEnvironment {
32 public:
33 static const storm::dd::DdType ddType = storm::dd::DdType::Sylvan; // Unused for sparse models
34 static const MaEngine engine = MaEngine::PrismSparse;
35 static const bool isExact = false;
36 typedef double ValueType;
38 static storm::Environment createEnvironment() {
40 env.solver().minMax().setMethod(storm::solver::MinMaxMethod::ValueIteration, true);
41 env.solver().minMax().setPrecision(storm::utility::convertNumber<storm::RationalNumber>(1e-10));
42 return env;
43 }
44};
45class JaniSparseDoubleValueIterationEnvironment {
46 public:
47 static const storm::dd::DdType ddType = storm::dd::DdType::Sylvan; // Unused for sparse models
48 static const MaEngine engine = MaEngine::JaniSparse;
49 static const bool isExact = false;
50 typedef double ValueType;
52 static storm::Environment createEnvironment() {
54 env.solver().minMax().setMethod(storm::solver::MinMaxMethod::ValueIteration, true);
55 env.solver().minMax().setPrecision(storm::utility::convertNumber<storm::RationalNumber>(1e-10));
56 return env;
57 }
58};
59class JaniHybridDoubleValueIterationEnvironment {
60 public:
61 static const storm::dd::DdType ddType = storm::dd::DdType::Sylvan;
62 static const MaEngine engine = MaEngine::JaniHybrid;
63 static const bool isExact = false;
64 typedef double ValueType;
66
67 static void checkLibraryAvailable() {
68#ifndef STORM_HAVE_SYLVAN
69 GTEST_SKIP() << "Library Sylvan not available.";
70#endif
71 }
72
73 static storm::Environment createEnvironment() {
75 env.solver().minMax().setMethod(storm::solver::MinMaxMethod::ValueIteration, true);
76 env.solver().minMax().setPrecision(storm::utility::convertNumber<storm::RationalNumber>(1e-10));
77 return env;
78 }
79};
80class SparseDoubleIntervalIterationEnvironment {
81 public:
82 static const storm::dd::DdType ddType = storm::dd::DdType::Sylvan; // Unused for sparse models
83 static const MaEngine engine = MaEngine::PrismSparse;
84 static const bool isExact = false;
85 typedef double ValueType;
87 static storm::Environment createEnvironment() {
89 env.solver().setForceSoundness(true);
90 env.solver().minMax().setMethod(storm::solver::MinMaxMethod::IntervalIteration, true);
91 env.solver().minMax().setPrecision(storm::utility::convertNumber<storm::RationalNumber>(1e-6));
93 return env;
94 }
95};
96class SparseRationalPolicyIterationEnvironment {
97 public:
98 static const storm::dd::DdType ddType = storm::dd::DdType::Sylvan; // Unused for sparse models
99 static const MaEngine engine = MaEngine::PrismSparse;
100 static const bool isExact = true;
101 typedef storm::RationalNumber ValueType;
103 static storm::Environment createEnvironment() {
105 env.solver().minMax().setMethod(storm::solver::MinMaxMethod::PolicyIteration, true);
106 return env;
107 }
108};
109class SparseRationalRationalSearchEnvironment {
110 public:
111 static const storm::dd::DdType ddType = storm::dd::DdType::Sylvan; // Unused for sparse models
112 static const MaEngine engine = MaEngine::PrismSparse;
113 static const bool isExact = true;
114 typedef storm::RationalNumber ValueType;
116 static storm::Environment createEnvironment() {
118 env.solver().minMax().setMethod(storm::solver::MinMaxMethod::RationalSearch, true);
119 return env;
120 }
121};
122
123template<typename TestType>
124class MarkovAutomatonCslModelCheckerTest : public ::testing::Test {
125 public:
126 typedef typename TestType::ValueType ValueType;
127 typedef typename storm::models::sparse::MarkovAutomaton<ValueType> SparseModelType;
129
130 MarkovAutomatonCslModelCheckerTest() : _environment(TestType::createEnvironment()) {}
131
132 void SetUp() override {
133#ifndef STORM_HAVE_Z3
134 GTEST_SKIP() << "Z3 not available.";
135#endif
136 if constexpr (TestType::engine == MaEngine::JaniHybrid) {
137 TestType::checkLibraryAvailable();
138 }
139 }
140
141 storm::Environment const& env() const {
142 return _environment;
143 }
144 ValueType parseNumber(std::string const& input) const {
145 return storm::utility::convertNumber<ValueType>(input);
146 }
147 ValueType precision() const {
148 return TestType::isExact ? parseNumber("0") : parseNumber("1e-6");
149 }
150 bool isSparseModel() const {
151 return std::is_same<typename TestType::ModelType, SparseModelType>::value;
152 }
153 bool isSymbolicModel() const {
154 return std::is_same<typename TestType::ModelType, SymbolicModelType>::value;
155 }
156
157 template<typename MT = typename TestType::ModelType>
158 typename std::enable_if<std::is_same<MT, SparseModelType>::value,
159 std::pair<std::shared_ptr<MT>, std::vector<std::shared_ptr<storm::logic::Formula const>>>>::type
160 buildModelFormulas(std::string const& pathToPrismFile, std::string const& formulasAsString, std::string const& constantDefinitionString = "") const {
161 std::pair<std::shared_ptr<MT>, std::vector<std::shared_ptr<storm::logic::Formula const>>> result;
162 storm::prism::Program program = storm::api::parseProgram(pathToPrismFile);
163 program = storm::utility::prism::preprocess(program, constantDefinitionString);
164 if (TestType::engine == MaEngine::PrismSparse) {
166 result.first = storm::api::buildSparseModel<ValueType>(program, result.second)->template as<MT>();
167 } else if (TestType::engine == MaEngine::JaniSparse) {
168 auto janiData = storm::api::convertPrismToJani(program, storm::api::parsePropertiesForPrismProgram(formulasAsString, program));
169 result.second = storm::api::extractFormulasFromProperties(janiData.second);
170 result.first = storm::api::buildSparseModel<ValueType>(janiData.first, result.second)->template as<MT>();
171 }
172 return result;
173 }
174
175 template<typename MT = typename TestType::ModelType>
176 typename std::enable_if<std::is_same<MT, SymbolicModelType>::value,
177 std::pair<std::shared_ptr<MT>, std::vector<std::shared_ptr<storm::logic::Formula const>>>>::type
178 buildModelFormulas(std::string const& pathToPrismFile, std::string const& formulasAsString, std::string const& constantDefinitionString = "") const {
179 std::pair<std::shared_ptr<MT>, std::vector<std::shared_ptr<storm::logic::Formula const>>> result;
180 storm::prism::Program program = storm::api::parseProgram(pathToPrismFile);
181 program = storm::utility::prism::preprocess(program, constantDefinitionString);
182 auto janiData = storm::api::convertPrismToJani(program, storm::api::parsePropertiesForPrismProgram(formulasAsString, program));
183 result.second = storm::api::extractFormulasFromProperties(janiData.second);
184 result.first = storm::api::buildSymbolicModel<TestType::ddType, ValueType>(janiData.first, result.second)->template as<MT>();
185 return result;
186 }
187
188 std::vector<storm::modelchecker::CheckTask<storm::logic::Formula, ValueType>> getTasks(
189 std::vector<std::shared_ptr<storm::logic::Formula const>> const& formulas) const {
190 std::vector<storm::modelchecker::CheckTask<storm::logic::Formula, ValueType>> result;
191 for (auto const& f : formulas) {
192 result.emplace_back(*f);
193 }
194 return result;
195 }
196
197 template<typename MT = typename TestType::ModelType>
198 typename std::enable_if<std::is_same<MT, SparseModelType>::value, std::shared_ptr<storm::modelchecker::AbstractModelChecker<MT>>>::type createModelChecker(
199 std::shared_ptr<MT> const& model) const {
200 if (TestType::engine == MaEngine::PrismSparse || TestType::engine == MaEngine::JaniSparse) {
201 return std::make_shared<storm::modelchecker::SparseMarkovAutomatonCslModelChecker<SparseModelType>>(*model);
202 }
203 return nullptr;
204 }
205
206 template<typename MT = typename TestType::ModelType>
207 typename std::enable_if<std::is_same<MT, SymbolicModelType>::value, std::shared_ptr<storm::modelchecker::AbstractModelChecker<MT>>>::type
208 createModelChecker(std::shared_ptr<MT> const& model) const {
209 if (TestType::engine == MaEngine::JaniHybrid) {
210 return std::make_shared<storm::modelchecker::HybridMarkovAutomatonCslModelChecker<SymbolicModelType>>(*model);
211 // } else if (TestType::engine == MaEngine::Dd) {
212 // return std::make_shared<storm::modelchecker::SymbolicMarkovAutomatonCslModelChecker<SymbolicModelType>>(*model);
213 }
214 return nullptr;
215 }
216
217 template<typename MT = typename TestType::ModelType>
218 typename std::enable_if<std::is_same<MT, SparseModelType>::value, void>::type execute(std::shared_ptr<MT> const& model,
219 std::function<void()> const& f) const {
220 f();
221 }
222
223 template<typename MT = typename TestType::ModelType>
224 typename std::enable_if<std::is_same<MT, SymbolicModelType>::value, void>::type execute(std::shared_ptr<MT> const& model,
225 std::function<void()> const& f) const {
226 model->getManager().execute(f);
227 }
228
229 bool getQualitativeResultAtInitialState(std::shared_ptr<storm::models::Model<ValueType>> const& model,
230 std::unique_ptr<storm::modelchecker::CheckResult>& result) {
231 auto filter = getInitialStateFilter(model);
232 result->filter(*filter);
233 return result->asQualitativeCheckResult().forallTrue();
234 }
235
237 std::unique_ptr<storm::modelchecker::CheckResult>& result) {
238 auto filter = getInitialStateFilter(model);
239 result->filter(*filter);
240 return result->asQuantitativeCheckResult<ValueType>().getMin();
241 }
242
243 private:
244 storm::Environment _environment;
245
246 std::unique_ptr<storm::modelchecker::QualitativeCheckResult> getInitialStateFilter(std::shared_ptr<storm::models::Model<ValueType>> const& model) const {
247 if (isSparseModel()) {
248 return std::make_unique<storm::modelchecker::ExplicitQualitativeCheckResult>(model->template as<SparseModelType>()->getInitialStates());
249 } else {
250 return std::make_unique<storm::modelchecker::SymbolicQualitativeCheckResult<TestType::ddType>>(
251 model->template as<SymbolicModelType>()->getReachableStates(), model->template as<SymbolicModelType>()->getInitialStates());
252 }
253 }
254};
255
256typedef ::testing::Types<SparseDoubleValueIterationEnvironment, JaniSparseDoubleValueIterationEnvironment, JaniHybridDoubleValueIterationEnvironment,
257 SparseDoubleIntervalIterationEnvironment, SparseRationalPolicyIterationEnvironment, SparseRationalRationalSearchEnvironment>
259
260TYPED_TEST_SUITE(MarkovAutomatonCslModelCheckerTest, TestingTypes, );
261
262TYPED_TEST(MarkovAutomatonCslModelCheckerTest, server) {
263 std::string formulasString = "Tmax=? [F \"error\"]";
264 formulasString += "; Pmax=? [F \"processB\"]";
265 formulasString += "; Pmax=? [F<1 \"error\"]";
266
267 auto modelFormulas = this->buildModelFormulas(STORM_TEST_RESOURCES_DIR "/ma/server.ma", formulasString);
268 auto model = std::move(modelFormulas.first);
269 auto tasks = this->getTasks(modelFormulas.second);
270 this->execute(model, [&]() {
271 EXPECT_EQ(6ul, model->getNumberOfStates());
272 EXPECT_EQ(10ul, model->getNumberOfTransitions());
273 ASSERT_EQ(model->getType(), storm::models::ModelType::MarkovAutomaton);
274 auto checker = this->createModelChecker(model);
275 std::unique_ptr<storm::modelchecker::CheckResult> result;
276
277 result = checker->check(this->env(), tasks[0]);
278 EXPECT_NEAR(this->parseNumber("11/6"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
279
280 result = checker->check(this->env(), tasks[1]);
281 EXPECT_NEAR(this->parseNumber("2/3"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
282
283 if (!storm::utility::isZero(this->precision())) {
284 result = checker->check(this->env(), tasks[2]);
285 EXPECT_NEAR(this->parseNumber("0.455504"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
286 }
287 });
288}
289
290TYPED_TEST(MarkovAutomatonCslModelCheckerTest, simple) {
291 std::string formulasString = "Pmin=? [F<1 s>2]";
292 formulasString += "; Pmax=? [F<1.3 s=3]";
293
294 auto modelFormulas = this->buildModelFormulas(STORM_TEST_RESOURCES_DIR "/ma/simple.ma", formulasString);
295 auto model = std::move(modelFormulas.first);
296 auto tasks = this->getTasks(modelFormulas.second);
297 this->execute(model, [&]() {
298 EXPECT_EQ(5ul, model->getNumberOfStates());
299 EXPECT_EQ(8ul, model->getNumberOfTransitions());
300 ASSERT_EQ(model->getType(), storm::models::ModelType::MarkovAutomaton);
301 auto checker = this->createModelChecker(model);
302 std::unique_ptr<storm::modelchecker::CheckResult> result;
303
304 if (!storm::utility::isZero(this->precision())) {
305 result = checker->check(this->env(), tasks[0]);
306 EXPECT_NEAR(this->parseNumber("0.6321205588"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
307
308 result = checker->check(this->env(), tasks[1]);
309 EXPECT_NEAR(this->parseNumber("0.727468207"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
310 }
311 });
312}
313
314TYPED_TEST(MarkovAutomatonCslModelCheckerTest, simple2) {
315 std::string formulasString = "R{\"rew0\"}max=? [C]";
316 formulasString += "; R{\"rew0\"}min=? [C]";
317 formulasString += "; R{\"rew1\"}max=? [C]";
318 formulasString += "; R{\"rew1\"}min=? [C]";
319 formulasString += "; R{\"rew2\"}max=? [C]";
320 formulasString += "; R{\"rew2\"}min=? [C]";
321 formulasString += "; R{\"rew3\"}min=? [C]";
322 formulasString += "; LRAmin=? [s=0 | s=3]"; // 0
323 formulasString += "; R{\"rew3\"}max=?[ LRA ]"; // 407
324 formulasString += "; R{\"rew3\"}min=?[ LRA ]"; // 27
325
326 auto modelFormulas = this->buildModelFormulas(STORM_TEST_RESOURCES_DIR "/ma/simple2.ma", formulasString);
327 auto model = std::move(modelFormulas.first);
328 auto tasks = this->getTasks(modelFormulas.second);
329 EXPECT_EQ(6ul, model->getNumberOfStates());
330 EXPECT_EQ(11ul, model->getNumberOfTransitions());
331 ASSERT_EQ(model->getType(), storm::models::ModelType::MarkovAutomaton);
332 auto checker = this->createModelChecker(model);
333 std::unique_ptr<storm::modelchecker::CheckResult> result;
334
335 if (TypeParam::engine != MaEngine::JaniHybrid) {
336 // Total Reward Formulas are not supported in the hybrid engine (for now).
337
338 result = checker->check(this->env(), tasks[0]);
339 EXPECT_NEAR(this->parseNumber("2"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
340
341 result = checker->check(this->env(), tasks[1]);
342 EXPECT_NEAR(this->parseNumber("0"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
343
344 result = checker->check(this->env(), tasks[2]);
345 EXPECT_TRUE(storm::utility::isInfinity(this->getQuantitativeResultAtInitialState(model, result)));
346
347 result = checker->check(this->env(), tasks[3]);
348 EXPECT_NEAR(this->parseNumber("7/8"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
349
350 result = checker->check(this->env(), tasks[4]);
351 EXPECT_TRUE(storm::utility::isInfinity(this->getQuantitativeResultAtInitialState(model, result)));
352
353 result = checker->check(this->env(), tasks[5]);
354 EXPECT_NEAR(this->parseNumber("7/8"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
355
356 result = checker->check(this->env(), tasks[6]);
357 EXPECT_TRUE(storm::utility::isInfinity(this->getQuantitativeResultAtInitialState(model, result)));
358 }
359
360#ifndef STORM_HAVE_Z3_OPTIMIZE
361 if (!storm::utility::isZero(this->precision())) {
362#endif
363 // Checking LRA properties exactly requires an exact LP solver.
364 result = checker->check(this->env(), tasks[7]);
365 EXPECT_NEAR(this->parseNumber("0"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
366
367 result = checker->check(this->env(), tasks[8]);
368 EXPECT_NEAR(this->parseNumber("407"), this->getQuantitativeResultAtInitialState(model, result),
369 this->precision() * this->parseNumber("407")); // use relative precision!
370
371 result = checker->check(this->env(), tasks[9]);
372 EXPECT_NEAR(this->parseNumber("27"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
373#ifndef STORM_HAVE_Z3_OPTIMIZE
374 }
375#endif
376}
377
378TYPED_TEST(MarkovAutomatonCslModelCheckerTest, LtlSimple) {
379#ifdef STORM_HAVE_LTL_MODELCHECKING_SUPPORT
380 std::string formulasString = "Pmax=? [X X s=3]";
381 formulasString += "; Pmax=? [X X G s>2]";
382 formulasString += "; Pmin=? [X X G s>2]";
383 formulasString += "; Pmax=? [F ((s=0) U X(s=0) & X(s=2))]";
384
385 auto modelFormulas = this->buildModelFormulas(STORM_TEST_RESOURCES_DIR "/ma/simple.ma", formulasString);
386 auto model = std::move(modelFormulas.first);
387 auto tasks = this->getTasks(modelFormulas.second);
388 EXPECT_EQ(5ul, model->getNumberOfStates());
389 EXPECT_EQ(8ul, model->getNumberOfTransitions());
390 ASSERT_EQ(model->getType(), storm::models::ModelType::MarkovAutomaton);
391 auto checker = this->createModelChecker(model);
392 std::unique_ptr<storm::modelchecker::CheckResult> result;
393
394 // LTL not supported in all engines (Hybrid, PrismDd, JaniDd)
395 if (TypeParam::engine == MaEngine::PrismSparse || TypeParam::engine == MaEngine::JaniSparse) {
396 result = checker->check(this->env(), tasks[0]);
397 EXPECT_NEAR(this->parseNumber("1/10"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
398
399 result = checker->check(this->env(), tasks[1]);
400 EXPECT_NEAR(this->parseNumber("1/5"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
401
402 result = checker->check(this->env(), tasks[2]);
403 EXPECT_NEAR(this->parseNumber("1/10"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
404
405 result = checker->check(this->env(), tasks[3]);
406 EXPECT_NEAR(this->parseNumber("9/10"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
407
408 } else {
409 EXPECT_FALSE(checker->canHandle(tasks[0]));
410 }
411#else
412 GTEST_SKIP();
413#endif
414}
415
416TYPED_TEST(MarkovAutomatonCslModelCheckerTest, HOASimple) {
417 // "P=? [ F (s=3) & (X s=1)]"
418 std::string formulasString = "; P=?[HOA: {\"" STORM_TEST_RESOURCES_DIR "/hoa/automaton_Fandp0Xp1.hoa\", \"p0\" -> (s>1), \"p1\" -> !(s=1) }]";
419 // "P=? [ (s=2) U (s=1)]"
420 formulasString += "; P=?[HOA: {\"" STORM_TEST_RESOURCES_DIR "/hoa/automaton_UXp0p1.hoa\", \"p0\" -> (s=2), \"p1\" -> (s=1) }]";
421
422 auto modelFormulas = this->buildModelFormulas(STORM_TEST_RESOURCES_DIR "/ma/simple.ma", formulasString);
423 auto model = std::move(modelFormulas.first);
424 auto tasks = this->getTasks(modelFormulas.second);
425 EXPECT_EQ(5ul, model->getNumberOfStates());
426 EXPECT_EQ(8ul, model->getNumberOfTransitions());
427 ASSERT_EQ(model->getType(), storm::models::ModelType::MarkovAutomaton);
428 auto checker = this->createModelChecker(model);
429 std::unique_ptr<storm::modelchecker::CheckResult> result;
430
431 // Not supported in all engines (Hybrid, PrismDd, JaniDd)
432 if (TypeParam::engine == MaEngine::PrismSparse || TypeParam::engine == MaEngine::JaniSparse) {
433 result = checker->check(tasks[0]);
434 EXPECT_NEAR(this->parseNumber("1"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
435
436 result = checker->check(tasks[1]);
437 EXPECT_NEAR(this->parseNumber("0"), this->getQuantitativeResultAtInitialState(model, result), this->precision());
438 } else {
439 EXPECT_FALSE(checker->canHandle(tasks[0]));
440 }
441}
442} // namespace
std::unique_ptr< storm::modelchecker::QualitativeCheckResult > getInitialStateFilter(std::shared_ptr< storm::models::sparse::Model< storm::Interval > > const &model)
double getQuantitativeResultAtInitialState(std::shared_ptr< storm::models::sparse::Model< storm::Interval > > const &model, std::unique_ptr< storm::modelchecker::CheckResult > &result)
SolverEnvironment & solver()
void setPrecision(storm::RationalNumber value)
void setMethod(storm::solver::MinMaxMethod value, bool isSetFromDefault=false)
MinMaxSolverEnvironment & minMax()
This class represents a Markov automaton.
This class represents a discrete-time Markov decision process.
std::vector< storm::jani::Property > parsePropertiesForPrismProgram(std::string const &inputString, storm::prism::Program const &program, boost::optional< std::set< std::string > > const &propertyFilter)
storm::prism::Program parseProgram(std::string const &filename, bool prismCompatibility, bool simplify)
std::vector< std::shared_ptr< storm::logic::Formula const > > extractFormulasFromProperties(std::vector< storm::jani::Property > const &properties)
std::pair< storm::jani::Model, std::vector< storm::jani::Property > > convertPrismToJani(storm::prism::Program const &program, storm::converter::PrismToJaniConverterOptions options)
SFTBDDChecker::ValueType ValueType
NumberType parseNumber(std::string const &value)
Parse number from string.
storm::prism::Program preprocess(storm::prism::Program const &program, std::map< storm::expressions::Variable, storm::expressions::Expression > const &constantDefinitions)
Definition prism.cpp:19
storm::storage::BitVector filter(std::vector< T > const &values, std::function< bool(T const &value)> const &function)
Retrieves a bit vector containing all the indices for which the value at this position makes the give...
Definition vector.h:490
bool isZero(ValueType const &a)
Definition constants.cpp:38
bool isInfinity(ValueType const &a)
TYPED_TEST(GraphTestAR, SymbolicProb01StochasticGameDieSmall)
Definition GraphTest.cpp:62
TYPED_TEST_SUITE(GraphTestAR, TestingTypes,)
::testing::Types< Cudd, Sylvan > TestingTypes
Definition GraphTest.cpp:59