#include "src/modelchecker/abstraction/GameBasedMdpModelChecker.h" #include "src/models/symbolic/StandardRewardModel.h" #include "src/storage/expressions/ExpressionManager.h" #include "src/abstraction/prism/PrismMenuGameAbstractor.h" #include "src/logic/FragmentSpecification.h" #include "src/utility/graph.h" #include "src/utility/macros.h" #include "src/exceptions/NotSupportedException.h" #include "src/exceptions/InvalidPropertyException.h" #include "src/modelchecker/results/CheckResult.h" namespace storm { namespace modelchecker { template GameBasedMdpModelChecker::GameBasedMdpModelChecker(storm::prism::Program const& program, std::shared_ptr const& smtSolverFactory) : originalProgram(program), smtSolverFactory(smtSolverFactory) { STORM_LOG_THROW(program.getModelType() == storm::prism::Program::ModelType::DTMC || program.getModelType() == storm::prism::Program::ModelType::MDP, storm::exceptions::NotSupportedException, "Currently only DTMCs/MDPs are supported by the game-based model checker."); // Start by preparing the program. That is, we flatten the modules if there is more than one. if (originalProgram.getNumberOfModules() > 1) { preprocessedProgram = originalProgram.flattenModules(this->smtSolverFactory); } else { preprocessedProgram = originalProgram; } } template GameBasedMdpModelChecker::~GameBasedMdpModelChecker() { // Intentionally left empty. } template bool GameBasedMdpModelChecker::canHandle(CheckTask const& checkTask) const { storm::logic::Formula const& formula = checkTask.getFormula(); storm::logic::FragmentSpecification fragment = storm::logic::reachability(); return formula.isInFragment(fragment) && checkTask.isOnlyInitialStatesRelevantSet(); } template std::unique_ptr GameBasedMdpModelChecker::computeUntilProbabilities(CheckTask const& checkTask) { storm::logic::UntilFormula const& pathFormula = checkTask.getFormula(); return performGameBasedAbstractionRefinement(CheckTask(pathFormula), getExpression(pathFormula.getLeftSubformula()), getExpression(pathFormula.getRightSubformula())); } template std::unique_ptr GameBasedMdpModelChecker::computeReachabilityProbabilities(CheckTask const& checkTask) { storm::logic::EventuallyFormula const& pathFormula = checkTask.getFormula(); return performGameBasedAbstractionRefinement(checkTask.substituteFormula(pathFormula), originalProgram.getManager().boolean(true), getExpression(pathFormula.getSubformula())); } template std::unique_ptr GameBasedMdpModelChecker::performGameBasedAbstractionRefinement(CheckTask const& checkTask, storm::expressions::Expression const& constraintExpression, storm::expressions::Expression const& targetStateExpression) { STORM_LOG_THROW(checkTask.isOnlyInitialStatesRelevantSet(), storm::exceptions::InvalidPropertyException, "The game-based abstraction refinement model checker can only compute the result for the initial states."); // Set up initial predicates. std::vector initialPredicates; initialPredicates.push_back(targetStateExpression); if (!constraintExpression.isTrue() && !constraintExpression.isFalse()) { initialPredicates.push_back(constraintExpression); } // Derive the optimization direction for player 1 (assuming menu-game abstraction). storm::OptimizationDirection player1Direction = checkTask.isOptimizationDirectionSet() ? checkTask.getOptimizationDirection() : storm::OptimizationDirection::Maximize; storm::abstraction::prism::PrismMenuGameAbstractor abstractor(preprocessedProgram, initialPredicates, smtSolverFactory); // 1. build initial abstraction based on the the constraint expression (if not 'true') and the target state expression. storm::abstraction::MenuGame game = abstractor.abstract(); abstractor.exportToDot("game.dot"); STORM_LOG_DEBUG("Initial abstraction has " << game.getNumberOfStates() << " (player 1) states and " << game.getNumberOfTransitions() << " transitions."); // 2. solve the game wrt. to min/max as given by checkTask and min/max for the abstraction player to obtain two bounds. // Note that we have to deal with bottom states if not all guards were added in the beginning. // Also note that it might be the case that not both bounds need to be computed if there is a bound given in checkTask. computeProb01States(player1Direction, game, constraintExpression, targetStateExpression); // std::unique_ptr> gameSolverFactory = std::make_unique>(); // gameSolverFactory->create(game.getTransitionMatrix(), game.getReachableStates()); // storm::dd::Add const& A, storm::dd::Bdd const& allRows, std::set const& rowMetaVariables, std::set const& columnMetaVariables, std::vector> const& rowColumnMetaVariablePairs, std::set const& player1Variables, std::set const& player2Variables // 3. if the bounds suffice to complete checkTask, return result now. // 4. if the bounds do not suffice return nullptr; } template void GameBasedMdpModelChecker::computeProb01States(storm::OptimizationDirection player1Direction, storm::abstraction::MenuGame const& game, storm::expressions::Expression const& constraintExpression, storm::expressions::Expression const& targetStateExpression) { storm::dd::Bdd transitionMatrixBdd = game.getTransitionMatrix().toBdd(); storm::dd::Bdd bottomStatesBdd = game.getBottomStates(); storm::dd::Bdd targetStates = game.getStates(targetStateExpression); if (player1Direction == storm::OptimizationDirection::Minimize) { targetStates |= game.getBottomStates(); } transitionMatrixBdd.template toAdd().exportToDot("transbdd.dot"); // Start by computing the states with probability 0/1 when player 2 minimizes. storm::utility::graph::GameProb01Result prob0Min = storm::utility::graph::performProb0(game, transitionMatrixBdd, game.getStates(constraintExpression), targetStates, player1Direction, storm::OptimizationDirection::Minimize, true); storm::utility::graph::GameProb01Result prob1Min = storm::utility::graph::performProb1(game, transitionMatrixBdd, game.getStates(constraintExpression), targetStates, player1Direction, storm::OptimizationDirection::Minimize, false); // Now compute the states with probability 0/1 when player 2 maximizes. storm::utility::graph::GameProb01Result prob0Max = storm::utility::graph::performProb0(game, transitionMatrixBdd, game.getStates(constraintExpression), targetStates, player1Direction, storm::OptimizationDirection::Maximize, false); storm::utility::graph::GameProb01Result prob1Max = storm::utility::graph::performProb1(game, transitionMatrixBdd, game.getStates(constraintExpression), targetStates, player1Direction, storm::OptimizationDirection::Maximize, true); // STORM_LOG_ASSERT(prob0Min.hasPlayer1Strategy(), "Unable to proceed without strategy."); STORM_LOG_ASSERT(prob0Min.hasPlayer2Strategy(), "Unable to proceed without strategy."); STORM_LOG_ASSERT(prob1Max.hasPlayer1Strategy(), "Unable to proceed without strategy."); STORM_LOG_ASSERT(prob1Max.hasPlayer2Strategy(), "Unable to proceed without strategy."); // prob0Min.getPlayer1Strategy().template toAdd().exportToDot("prob0_min_pl1_strat.dot"); prob0Min.getPlayer2Strategy().template toAdd().exportToDot("prob0_min_pl2_strat.dot"); prob1Max.getPlayer1Strategy().template toAdd().exportToDot("prob1_max_pl1_strat.dot"); prob1Max.getPlayer2Strategy().template toAdd().exportToDot("prob1_max_pl2_strat.dot"); STORM_LOG_DEBUG("Min: " << prob0Min.states.getNonZeroCount() << " no states, " << prob1Min.states.getNonZeroCount() << " yes states."); STORM_LOG_DEBUG("Max: " << prob0Max.states.getNonZeroCount() << " no states, " << prob1Max.states.getNonZeroCount() << " yes states."); } template storm::expressions::Expression GameBasedMdpModelChecker::getExpression(storm::logic::Formula const& formula) { STORM_LOG_THROW(formula.isAtomicExpressionFormula() || formula.isAtomicLabelFormula(), storm::exceptions::InvalidPropertyException, "The target states have to be given as label or an expression."); storm::expressions::Expression result; if (formula.isAtomicLabelFormula()) { result = preprocessedProgram.getLabelExpression(formula.asAtomicLabelFormula().getLabel()); } else { result = formula.asAtomicExpressionFormula().getExpression(); } return result; } template class GameBasedMdpModelChecker; } }