#include "test/storm_gtest.h" #include "storm-config.h" #include "storm/api/builder.h" #include "storm-conv/api/storm-conv.h" #include "storm-parsers/api/model_descriptions.h" #include "storm/api/properties.h" #include "storm-parsers/api/properties.h" #include "storm/logic/Formulas.h" #include "storm/models/sparse/StandardRewardModel.h" #include "storm/modelchecker/prctl/SparseMdpPrctlModelChecker.h" #include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "storm/settings/SettingsManager.h" #include "storm/settings/modules/GeneralSettings.h" #include "storm/environment/solver/MinMaxSolverEnvironment.h" #include "storm/environment/solver/LongRunAverageSolverEnvironment.h" namespace { class DoubleViEnvironment { public: typedef double ValueType; static storm::Environment createEnvironment() { storm::Environment env; env.solver().minMax().setMethod(storm::solver::MinMaxMethod::ValueIteration); env.solver().minMax().setPrecision(storm::utility::convertNumber(1e-8)); env.solver().lra().setNondetLraMethod(storm::solver::LraMethod::ValueIteration); return env; } }; class DoubleSoundViEnvironment { public: typedef double ValueType; static storm::Environment createEnvironment() { storm::Environment env; env.solver().minMax().setMethod(storm::solver::MinMaxMethod::ValueIteration); env.solver().setForceSoundness(true); env.solver().minMax().setPrecision(storm::utility::convertNumber(1e-6)); env.solver().lra().setNondetLraMethod(storm::solver::LraMethod::ValueIteration); return env; } }; class DoublePIEnvironment { public: typedef double ValueType; static storm::Environment createEnvironment() { storm::Environment env; env.solver().minMax().setMethod(storm::solver::MinMaxMethod::PolicyIteration); env.solver().minMax().setPrecision(storm::utility::convertNumber(1e-8)); env.solver().lra().setNondetLraMethod(storm::solver::LraMethod::ValueIteration); return env; } }; class RationalPIEnvironment { public: typedef storm::RationalNumber ValueType; static storm::Environment createEnvironment() { storm::Environment env; env.solver().minMax().setMethod(storm::solver::MinMaxMethod::PolicyIteration); return env; } }; // class RationalRationalSearchEnvironment { // public: // typedef storm::RationalNumber ValueType; // static storm::Environment createEnvironment() { // storm::Environment env; // env.solver().minMax().setMethod(storm::solver::MinMaxMethod::RationalSearch); // return env; // } // }; template class SchedulerGenerationMdpPrctlModelCheckerTest : public ::testing::Test { public: typedef typename TestType::ValueType ValueType; SchedulerGenerationMdpPrctlModelCheckerTest() : _environment(TestType::createEnvironment()) {} storm::Environment const& env() const { return _environment; } std::pair>, std::vector>> buildModelFormulas(std::string const& pathToPrismFile, std::string const& formulasAsString, std::string const& constantDefinitionString = "") const { std::pair>, std::vector>> result; storm::prism::Program program = storm::api::parseProgram(pathToPrismFile); program = storm::utility::prism::preprocess(program, constantDefinitionString); result.second = storm::api::extractFormulasFromProperties(storm::api::parsePropertiesForPrismProgram(formulasAsString, program)); result.first = storm::api::buildSparseModel(program, result.second)->template as>(); return result; } std::vector> getTasks(std::vector> const& formulas) const { std::vector> result; for (auto const& f : formulas) { result.emplace_back(*f); result.back().setProduceSchedulers(true); } return result; } ValueType parseNumber(std::string const& input) const { return storm::utility::convertNumber(input);} private: storm::Environment _environment; }; typedef ::testing::Types< DoubleViEnvironment, DoubleSoundViEnvironment, DoublePIEnvironment, RationalPIEnvironment //RationalRationalSearchEnvironment > TestingTypes; TYPED_TEST_SUITE(SchedulerGenerationMdpPrctlModelCheckerTest, TestingTypes,); TYPED_TEST(SchedulerGenerationMdpPrctlModelCheckerTest, reachability) { typedef typename TestFixture::ValueType ValueType; std::string formulasString = "Pmin=? [F \"target\"]; Pmax=? [F \"target\"];"; auto modelFormulas = this->buildModelFormulas(STORM_TEST_RESOURCES_DIR "/mdp/scheduler_generation.nm", formulasString); auto mdp = std::move(modelFormulas.first); auto tasks = this->getTasks(modelFormulas.second); EXPECT_EQ(4ull, mdp->getNumberOfStates()); EXPECT_EQ(11ull, mdp->getNumberOfTransitions()); ASSERT_EQ(mdp->getType(), storm::models::ModelType::Mdp); EXPECT_EQ(7ull, mdp->getNumberOfChoices()); storm::modelchecker::SparseMdpPrctlModelChecker> checker(*mdp); auto result = checker.check(this->env(), tasks[0]); ASSERT_TRUE(result->isExplicitQuantitativeCheckResult()); ASSERT_TRUE(result->template asExplicitQuantitativeCheckResult().hasScheduler()); storm::storage::Scheduler const& scheduler = result->template asExplicitQuantitativeCheckResult().getScheduler(); EXPECT_EQ(0ull, scheduler.getChoice(0).getDeterministicChoice()); EXPECT_EQ(1ull, scheduler.getChoice(1).getDeterministicChoice()); EXPECT_EQ(0ull, scheduler.getChoice(2).getDeterministicChoice()); EXPECT_EQ(0ull, scheduler.getChoice(3).getDeterministicChoice()); result = checker.check(tasks[1]); ASSERT_TRUE(result->isExplicitQuantitativeCheckResult()); ASSERT_TRUE(result->template asExplicitQuantitativeCheckResult().hasScheduler()); storm::storage::Scheduler const& scheduler2 = result->template asExplicitQuantitativeCheckResult().getScheduler(); EXPECT_EQ(1ull, scheduler2.getChoice(0).getDeterministicChoice()); EXPECT_EQ(2ull, scheduler2.getChoice(1).getDeterministicChoice()); EXPECT_EQ(0ull, scheduler2.getChoice(2).getDeterministicChoice()); EXPECT_EQ(0ull, scheduler2.getChoice(3).getDeterministicChoice()); } TYPED_TEST(SchedulerGenerationMdpPrctlModelCheckerTest, lra) { typedef typename TestFixture::ValueType ValueType; std::string formulasString = "R{\"grants\"}max=? [ MP ]; R{\"grants\"}min=? [ MP ];"; auto modelFormulas = this->buildModelFormulas(STORM_TEST_RESOURCES_DIR "/mdp/cs_nfail3.nm", formulasString); auto mdp = std::move(modelFormulas.first); auto tasks = this->getTasks(modelFormulas.second); EXPECT_EQ(184ul, mdp->getNumberOfStates()); EXPECT_EQ(541ul, mdp->getNumberOfTransitions()); EXPECT_EQ(439ul, mdp->getNumberOfChoices()); ASSERT_EQ(mdp->getType(), storm::models::ModelType::Mdp); storm::modelchecker::SparseMdpPrctlModelChecker> checker(*mdp); if (!std::is_same::value) { GTEST_SKIP() << "Lra scheduler extraction not supported for LP based method"; } { auto result = checker.check(this->env(), tasks[0]); ASSERT_TRUE(result->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("333/1000"), result->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()], this->env().solver().lra().getPrecision()); ASSERT_TRUE(result->template asExplicitQuantitativeCheckResult().hasScheduler()); storm::storage::Scheduler const& scheduler = result->template asExplicitQuantitativeCheckResult().getScheduler(); EXPECT_TRUE(scheduler.isDeterministicScheduler()); EXPECT_TRUE(scheduler.isMemorylessScheduler()); EXPECT_TRUE(!scheduler.isPartialScheduler()); auto inducedModel = mdp->applyScheduler(scheduler); ASSERT_EQ(inducedModel->getType(), storm::models::ModelType::Mdp); auto const& inducedMdp = inducedModel->template as>(); EXPECT_EQ(inducedMdp->getNumberOfChoices(), inducedMdp->getNumberOfStates()); storm::modelchecker::SparseMdpPrctlModelChecker> inducedChecker(*inducedMdp); auto inducedResult = inducedChecker.check(this->env(), tasks[0]); ASSERT_TRUE(inducedResult->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("333/1000"), inducedResult->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()], this->env().solver().lra().getPrecision()); } { auto result = checker.check(this->env(), tasks[1]); ASSERT_TRUE(result->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("0"), result->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()], this->env().solver().lra().getPrecision()); ASSERT_TRUE(result->template asExplicitQuantitativeCheckResult().hasScheduler()); storm::storage::Scheduler const& scheduler = result->template asExplicitQuantitativeCheckResult().getScheduler(); EXPECT_TRUE(scheduler.isDeterministicScheduler()); EXPECT_TRUE(scheduler.isMemorylessScheduler()); EXPECT_TRUE(!scheduler.isPartialScheduler()); auto inducedModel = mdp->applyScheduler(scheduler); ASSERT_EQ(inducedModel->getType(), storm::models::ModelType::Mdp); auto const& inducedMdp = inducedModel->template as>(); EXPECT_EQ(inducedMdp->getNumberOfChoices(), inducedMdp->getNumberOfStates()); storm::modelchecker::SparseMdpPrctlModelChecker> inducedChecker(*inducedMdp); auto inducedResult = inducedChecker.check(this->env(), tasks[1]); ASSERT_TRUE(inducedResult->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("0"), inducedResult->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()], this->env().solver().lra().getPrecision()); } } TYPED_TEST(SchedulerGenerationMdpPrctlModelCheckerTest, ltl) { typedef typename TestFixture::ValueType ValueType; // TODO skip without LTL support std::string formulasString = "Pmax=? [X X s=0]; Pmin=? [G F \"target\"]; Pmax=? [(G F s>2) & (F G !(s=3))];"; auto modelFormulas = this->buildModelFormulas(STORM_TEST_RESOURCES_DIR "/mdp/scheduler_generation.nm", formulasString); auto mdp = std::move(modelFormulas.first); auto tasks = this->getTasks(modelFormulas.second); EXPECT_EQ(4ull, mdp->getNumberOfStates()); EXPECT_EQ(11ull, mdp->getNumberOfTransitions()); ASSERT_EQ(mdp->getType(), storm::models::ModelType::Mdp); EXPECT_EQ(7ull, mdp->getNumberOfChoices()); storm::modelchecker::SparseMdpPrctlModelChecker> checker(*mdp); { auto result = checker.check(this->env(), tasks[0]); ASSERT_TRUE(result->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("81/100"), result->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()],storm::settings::getModule().getPrecision()); ASSERT_TRUE(result->template asExplicitQuantitativeCheckResult().hasScheduler()); storm::storage::Scheduler const &scheduler = result->template asExplicitQuantitativeCheckResult().getScheduler(); EXPECT_TRUE(scheduler.isDeterministicScheduler()); EXPECT_TRUE(!scheduler.isMemorylessScheduler()); EXPECT_TRUE(scheduler.isPartialScheduler()); auto inducedModel = mdp->applyScheduler(scheduler); ASSERT_EQ(inducedModel->getType(), storm::models::ModelType::Mdp); auto const &inducedMdp = inducedModel->template as>(); EXPECT_EQ(inducedMdp->getNumberOfChoices(), inducedMdp->getNumberOfStates()); storm::modelchecker::SparseMdpPrctlModelChecker> inducedChecker(*inducedMdp); auto inducedResult = inducedChecker.check(this->env(), tasks[0]); ASSERT_TRUE(inducedResult->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("81/100"), inducedResult->template asExplicitQuantitativeCheckResult()[*inducedMdp->getInitialStates().begin()],storm::settings::getModule().getPrecision()); } { auto result = checker.check(this->env(), tasks[1]); ASSERT_TRUE(result->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("1/2"), result->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()],storm::settings::getModule().getPrecision()); ASSERT_TRUE(result->template asExplicitQuantitativeCheckResult().hasScheduler()); storm::storage::Scheduler const &scheduler = result->template asExplicitQuantitativeCheckResult().getScheduler(); EXPECT_TRUE(scheduler.isDeterministicScheduler()); EXPECT_TRUE(!scheduler.isMemorylessScheduler()); EXPECT_TRUE(scheduler.isPartialScheduler()); auto inducedModel = mdp->applyScheduler(scheduler); ASSERT_EQ(inducedModel->getType(), storm::models::ModelType::Mdp); auto const &inducedMdp = inducedModel->template as>(); EXPECT_EQ(inducedMdp->getNumberOfChoices(), inducedMdp->getNumberOfStates()); storm::modelchecker::SparseMdpPrctlModelChecker> inducedChecker( *inducedMdp); auto inducedResult = inducedChecker.check(this->env(), tasks[1]); ASSERT_TRUE(inducedResult->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("1/2"),inducedResult->template asExplicitQuantitativeCheckResult()[*inducedMdp->getInitialStates().begin()],storm::settings::getModule().getPrecision()); } { auto result = checker.check(this->env(), tasks[2]); ASSERT_TRUE(result->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("1/2"), result->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()], storm::settings::getModule().getPrecision()); ASSERT_TRUE(result->template asExplicitQuantitativeCheckResult().hasScheduler()); storm::storage::Scheduler const &scheduler = result->template asExplicitQuantitativeCheckResult().getScheduler(); EXPECT_TRUE(scheduler.isDeterministicScheduler()); EXPECT_TRUE(!scheduler.isMemorylessScheduler()); EXPECT_TRUE(scheduler.isPartialScheduler()); auto inducedModel = mdp->applyScheduler(scheduler); ASSERT_EQ(inducedModel->getType(), storm::models::ModelType::Mdp); auto const &inducedMdp = inducedModel->template as>(); EXPECT_EQ(inducedMdp->getNumberOfChoices(), inducedMdp->getNumberOfStates()); storm::modelchecker::SparseMdpPrctlModelChecker> inducedChecker( *inducedMdp); auto inducedResult = inducedChecker.check(this->env(), tasks[2]); ASSERT_TRUE(inducedResult->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("1/2"), inducedResult->template asExplicitQuantitativeCheckResult()[*inducedMdp->getInitialStates().begin()], storm::settings::getModule().getPrecision()); } } TYPED_TEST(SchedulerGenerationMdpPrctlModelCheckerTest, ltlNondetChoice) { typedef typename TestFixture::ValueType ValueType; // TODO skip without LTL support // Nondeterministic choice in an accepting EC. std::string formulasString = "Pmax=? [X X !(x=0)];"; auto modelFormulas = this->buildModelFormulas(STORM_TEST_RESOURCES_DIR "/mdp/prism-mec-example1.nm", formulasString); auto mdp = std::move(modelFormulas.first); auto tasks = this->getTasks(modelFormulas.second); EXPECT_EQ(3ull, mdp->getNumberOfStates()); EXPECT_EQ(5ull, mdp->getNumberOfTransitions()); ASSERT_EQ(mdp->getType(), storm::models::ModelType::Mdp); EXPECT_EQ(4ull, mdp->getNumberOfChoices()); storm::modelchecker::SparseMdpPrctlModelChecker> checker(*mdp); { auto result = checker.check(this->env(), tasks[0]); ASSERT_TRUE(result->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("0.5"),result->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()], storm::settings::getModule().getPrecision()); ASSERT_TRUE(result->template asExplicitQuantitativeCheckResult().hasScheduler()); storm::storage::Scheduler const &scheduler = result->template asExplicitQuantitativeCheckResult().getScheduler(); EXPECT_TRUE(scheduler.isDeterministicScheduler()); EXPECT_TRUE(!scheduler.isMemorylessScheduler()); EXPECT_TRUE(scheduler.isPartialScheduler()); auto inducedModel = mdp->applyScheduler(scheduler, true); ASSERT_EQ(inducedModel->getType(), storm::models::ModelType::Mdp); auto const &inducedMdp = inducedModel->template as>(); EXPECT_EQ(inducedMdp->getNumberOfChoices(), inducedMdp->getNumberOfStates()); storm::modelchecker::SparseMdpPrctlModelChecker> inducedChecker(*inducedMdp); auto inducedResult = inducedChecker.check(this->env(), tasks[0]); ASSERT_TRUE(inducedResult->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("0.5"), inducedResult->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()], storm::settings::getModule().getPrecision()); } } TYPED_TEST(SchedulerGenerationMdpPrctlModelCheckerTest, ltlUnsat) { typedef typename TestFixture::ValueType ValueType; // TODO skip without LTL support // Nondeterministic choice in an accepting EC, Pmax unsatisfiable, Pmin tautology (compute 1-Pmax(!phi)) std::string formulasString = "Pmax=? [(X X !(s=0)) & (X X (s=0))]; Pmin=? [(X X !(s=0)) | (X X (s=0))];"; auto modelFormulas = this->buildModelFormulas(STORM_TEST_RESOURCES_DIR "/mdp/scheduler_generation.nm", formulasString); auto mdp = std::move(modelFormulas.first); auto tasks = this->getTasks(modelFormulas.second); EXPECT_EQ(4ull, mdp->getNumberOfStates()); EXPECT_EQ(11ull, mdp->getNumberOfTransitions()); ASSERT_EQ(mdp->getType(), storm::models::ModelType::Mdp); EXPECT_EQ(7ull, mdp->getNumberOfChoices()); storm::modelchecker::SparseMdpPrctlModelChecker> checker(*mdp); { auto result = checker.check(this->env(), tasks[0]); ASSERT_TRUE(result->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("0"),result->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()], storm::settings::getModule().getPrecision()); ASSERT_TRUE(result->template asExplicitQuantitativeCheckResult().hasScheduler()); storm::storage::Scheduler const &scheduler = result->template asExplicitQuantitativeCheckResult().getScheduler(); EXPECT_TRUE(scheduler.isDeterministicScheduler()); EXPECT_TRUE(scheduler.isMemorylessScheduler()); EXPECT_TRUE(!scheduler.isPartialScheduler()); auto inducedModel = mdp->applyScheduler(scheduler, true); ASSERT_EQ(inducedModel->getType(), storm::models::ModelType::Mdp); auto const &inducedMdp = inducedModel->template as>(); EXPECT_EQ(inducedMdp->getNumberOfChoices(), inducedMdp->getNumberOfStates()); storm::modelchecker::SparseMdpPrctlModelChecker> inducedChecker(*inducedMdp); auto inducedResult = inducedChecker.check(this->env(), tasks[0]); ASSERT_TRUE(inducedResult->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("0"), inducedResult->template asExplicitQuantitativeCheckResult()[*inducedMdp->getInitialStates().begin()], storm::settings::getModule().getPrecision()); } { auto result = checker.check(this->env(), tasks[1]); ASSERT_TRUE(result->isExplicitQuantitativeCheckResult()); ASSERT_TRUE(result->template asExplicitQuantitativeCheckResult().hasScheduler()); EXPECT_NEAR(this->parseNumber("1"),result->template asExplicitQuantitativeCheckResult()[*mdp->getInitialStates().begin()], storm::settings::getModule().getPrecision()); storm::storage::Scheduler const &scheduler = result->template asExplicitQuantitativeCheckResult().getScheduler(); EXPECT_TRUE(scheduler.isDeterministicScheduler()); EXPECT_TRUE(scheduler.isMemorylessScheduler()); EXPECT_TRUE(!scheduler.isPartialScheduler()); auto inducedModel = mdp->applyScheduler(scheduler, true); ASSERT_EQ(inducedModel->getType(), storm::models::ModelType::Mdp); auto const &inducedMdp = inducedModel->template as>(); EXPECT_EQ(inducedMdp->getNumberOfChoices(), inducedMdp->getNumberOfStates()); storm::modelchecker::SparseMdpPrctlModelChecker> inducedChecker(*inducedMdp); auto inducedResult = inducedChecker.check(this->env(), tasks[1]); ASSERT_TRUE(inducedResult->isExplicitQuantitativeCheckResult()); EXPECT_NEAR(this->parseNumber("1"), inducedResult->template asExplicitQuantitativeCheckResult()[*inducedMdp->getInitialStates().begin()], storm::settings::getModule().getPrecision()); } } }