#include "storm/abstraction/jani/JaniMenuGameAbstractor.h" #include "storm/abstraction/BottomStateResult.h" #include "storm/abstraction/GameBddResult.h" #include "storm/abstraction/ExpressionTranslator.h" #include "storm/storage/BitVector.h" #include "storm/storage/jani/Model.h" #include "storm/storage/jani/Automaton.h" #include "storm/storage/jani/Edge.h" #include "storm/storage/dd/DdManager.h" #include "storm/storage/dd/Add.h" #include "storm/models/symbolic/StandardRewardModel.h" #include "storm/settings/SettingsManager.h" #include "storm/utility/Stopwatch.h" #include "storm/utility/dd.h" #include "storm/utility/macros.h" #include "storm/utility/solver.h" #include "storm/exceptions/WrongFormatException.h" #include "storm/exceptions/InvalidArgumentException.h" #include "storm/exceptions/NotSupportedException.h" #include "storm-config.h" #include "storm/adapters/RationalFunctionAdapter.h" namespace storm { namespace abstraction { namespace jani { using storm::settings::modules::AbstractionSettings; template JaniMenuGameAbstractor::JaniMenuGameAbstractor(storm::jani::Model const& model, std::shared_ptr const& smtSolverFactory) : model(model), smtSolverFactory(smtSolverFactory), abstractionInformation(model.getManager(), model.getAllExpressionVariables(), smtSolverFactory->create(model.getManager())), automata(), initialStateAbstractor(abstractionInformation, {model.getInitialStatesExpression()}, this->smtSolverFactory), validBlockAbstractor(abstractionInformation, smtSolverFactory), currentGame(nullptr), refinementPerformed(true) { // Check whether the model is linear as the abstraction requires this. STORM_LOG_THROW(model.isLinear(), storm::exceptions::WrongFormatException, "Cannot create abstract model from non-linear model."); // For now, we assume that there is a single module. If the program has more than one module, it needs // to be flattened before the procedure. STORM_LOG_THROW(model.getNumberOfAutomata() == 1, storm::exceptions::WrongFormatException, "Cannot create abstract model from program containing more than one automaton."); // Add all variables range expressions to the information object. for (auto const& range : this->model.get().getAllRangeExpressions()) { abstractionInformation.addConstraint(range); initialStateAbstractor.constrain(range); } uint_fast64_t totalNumberOfCommands = 0; uint_fast64_t maximalUpdateCount = 0; std::vector allGuards; for (auto const& automaton : model.getAutomata()) { for (auto const& edge : automaton.getEdges()) { maximalUpdateCount = std::max(maximalUpdateCount, static_cast(edge.getNumberOfDestinations())); } totalNumberOfCommands += automaton.getNumberOfEdges(); } // NOTE: currently we assume that 64 player 2 variables suffice, which corresponds to 2^64 possible // choices. If for some reason this should not be enough, we could grow this vector dynamically, but // odds are that it's impossible to treat such models in any event. abstractionInformation.createEncodingVariables(static_cast(std::ceil(std::log2(totalNumberOfCommands))), 64, static_cast(std::ceil(std::log2(maximalUpdateCount)))); // For each module of the concrete program, we create an abstract counterpart. bool useDecomposition = storm::settings::getModule().isUseDecompositionSet(); for (auto const& automaton : model.getAutomata()) { automata.emplace_back(automaton, abstractionInformation, this->smtSolverFactory, useDecomposition); } // Retrieve global BDDs/ADDs so we can multiply them in the abstraction process. initialLocationsBdd = automata.front().getInitialLocationsBdd(); edgeDecoratorAdd = automata.front().getEdgeDecoratorAdd(); } template void JaniMenuGameAbstractor::refine(RefinementCommand const& command) { // Add the predicates to the global list of predicates and gather their indices. std::vector predicateIndices; for (auto const& predicate : command.getPredicates()) { STORM_LOG_THROW(predicate.hasBooleanType(), storm::exceptions::InvalidArgumentException, "Expecting a predicate of type bool."); predicateIndices.push_back(abstractionInformation.getOrAddPredicate(predicate)); } // Refine all abstract automata. for (auto& automaton : automata) { automaton.refine(predicateIndices); } // Refine initial state abstractor. initialStateAbstractor.refine(predicateIndices); // Refine the valid blocks. validBlockAbstractor.refine(predicateIndices); refinementPerformed |= !command.getPredicates().empty(); } template MenuGame JaniMenuGameAbstractor::abstract() { if (refinementPerformed) { currentGame = buildGame(); refinementPerformed = true; } return *currentGame; } template AbstractionInformation const& JaniMenuGameAbstractor::getAbstractionInformation() const { return abstractionInformation; } template storm::expressions::Expression const& JaniMenuGameAbstractor::getGuard(uint64_t player1Choice) const { return automata.front().getGuard(player1Choice); } template uint64_t JaniMenuGameAbstractor::getNumberOfUpdates(uint64_t player1Choice) const { return automata.front().getNumberOfUpdates(player1Choice); } template std::map JaniMenuGameAbstractor::getVariableUpdates(uint64_t player1Choice, uint64_t auxiliaryChoice) const { return automata.front().getVariableUpdates(player1Choice, auxiliaryChoice); } template std::set const& JaniMenuGameAbstractor::getAssignedVariables(uint64_t player1Choice) const { return automata.front().getAssignedVariables(player1Choice); } template std::pair JaniMenuGameAbstractor::getPlayer1ChoiceRange() const { return std::make_pair(0, automata.front().getNumberOfEdges()); } template storm::expressions::Expression JaniMenuGameAbstractor::getInitialExpression() const { return model.get().getInitialStatesExpression({model.get().getAutomaton(0)}); } template storm::dd::Bdd JaniMenuGameAbstractor::getStates(storm::expressions::Expression const& expression) { storm::abstraction::ExpressionTranslator translator(abstractionInformation, smtSolverFactory->create(abstractionInformation.getExpressionManager())); return translator.translate(expression); } template std::unique_ptr> JaniMenuGameAbstractor::buildGame() { // As long as there is only one module, we only build its game representation. GameBddResult game = automata.front().abstract(); // Add the locations to the transitions. game.bdd &= edgeDecoratorAdd.notZero(); // Construct a set of all unnecessary variables, so we can abstract from it. std::set variablesToAbstract(abstractionInformation.getPlayer1VariableSet(abstractionInformation.getPlayer1VariableCount())); auto player2Variables = abstractionInformation.getPlayer2VariableSet(game.numberOfPlayer2Variables); variablesToAbstract.insert(player2Variables.begin(), player2Variables.end()); auto auxVariables = abstractionInformation.getAuxVariableSet(0, abstractionInformation.getAuxVariableCount()); variablesToAbstract.insert(auxVariables.begin(), auxVariables.end()); storm::utility::Stopwatch relevantStatesWatch(true); storm::dd::Bdd nonTerminalStates = this->abstractionInformation.getDdManager().getBddOne(); if (this->isRestrictToRelevantStatesSet()) { // Compute which states are non-terminal. for (auto const& expression : this->terminalStateExpressions) { nonTerminalStates &= !this->getStates(expression); } if (this->hasTargetStateExpression()) { nonTerminalStates &= !this->getStates(this->getTargetStateExpression()); } } relevantStatesWatch.stop(); // Do a reachability analysis on the raw transition relation. storm::dd::Bdd transitionRelation = nonTerminalStates && game.bdd.existsAbstract(variablesToAbstract); storm::dd::Bdd initialStates = initialLocationsBdd && initialStateAbstractor.getAbstractStates(); initialStates.addMetaVariables(abstractionInformation.getSourcePredicateVariables()); storm::dd::Bdd reachableStates = storm::utility::dd::computeReachableStates(initialStates, transitionRelation, abstractionInformation.getSourceVariables(), abstractionInformation.getSuccessorVariables()); relevantStatesWatch.start(); if (this->isRestrictToRelevantStatesSet() && this->hasTargetStateExpression()) { // Cut transition relation to the reachable states for backward search. transitionRelation &= reachableStates; // Get the target state BDD. storm::dd::Bdd targetStates = reachableStates && this->getStates(this->getTargetStateExpression()); // In the presence of target states, we keep only states that can reach the target states. reachableStates = storm::utility::dd::computeBackwardsReachableStates(targetStates, reachableStates && !initialStates, transitionRelation, abstractionInformation.getSourceVariables(), abstractionInformation.getSuccessorVariables()) || initialStates; // Cut the transition relation to the 'extended backward reachable states', so we have the appropriate self- // loops of (now) deadlock states. transitionRelation &= reachableStates; // Include all successors of reachable states, because the backward search otherwise potentially // cuts probability 0 choices of these states. reachableStates |= reachableStates.relationalProduct(transitionRelation, abstractionInformation.getSourceVariables(), abstractionInformation.getSuccessorVariables()); relevantStatesWatch.stop(); STORM_LOG_TRACE("Restricting to relevant states took " << relevantStatesWatch.getTimeInMilliseconds() << "ms."); } // Find the deadlock states in the model. Note that this does not find the 'deadlocks' in bottom states, // as the bottom states are not contained in the reachable states. storm::dd::Bdd deadlockStates = transitionRelation.existsAbstract(abstractionInformation.getSuccessorVariables()); deadlockStates = reachableStates && !deadlockStates; // If there are deadlock states, we fix them now. storm::dd::Add deadlockTransitions = abstractionInformation.getDdManager().template getAddZero(); if (!deadlockStates.isZero()) { deadlockTransitions = (deadlockStates && abstractionInformation.getAllPredicateIdentities() && abstractionInformation.getAllLocationIdentities() && abstractionInformation.encodePlayer1Choice(0, abstractionInformation.getPlayer1VariableCount()) && abstractionInformation.encodePlayer2Choice(0, 0, game.numberOfPlayer2Variables) && abstractionInformation.encodeAux(0, 0, abstractionInformation.getAuxVariableCount())).template toAdd(); } // Compute bottom states and the appropriate transitions if necessary. BottomStateResult bottomStateResult(abstractionInformation.getDdManager().getBddZero(), abstractionInformation.getDdManager().getBddZero()); bottomStateResult = automata.front().getBottomStateTransitions(reachableStates, game.numberOfPlayer2Variables); bool hasBottomStates = !bottomStateResult.states.isZero(); // Construct the transition matrix by cutting away the transitions of unreachable states. storm::dd::Add transitionMatrix = (game.bdd && reachableStates && reachableStates.swapVariables(abstractionInformation.getSourceSuccessorVariablePairs())).template toAdd(); transitionMatrix *= edgeDecoratorAdd; transitionMatrix += deadlockTransitions; // Extend the current game information with the 'non-bottom' tag before potentially adding bottom state transitions. transitionMatrix *= (abstractionInformation.getBottomStateBdd(true, true) && abstractionInformation.getBottomStateBdd(false, true)).template toAdd(); reachableStates &= abstractionInformation.getBottomStateBdd(true, true); initialStates &= abstractionInformation.getBottomStateBdd(true, true); // If there are bottom transitions, exnted the transition matrix and reachable states now. if (hasBottomStates) { transitionMatrix += bottomStateResult.transitions.template toAdd(); reachableStates |= bottomStateResult.states; } std::set allNondeterminismVariables = player2Variables; allNondeterminismVariables.insert(abstractionInformation.getPlayer1Variables().begin(), abstractionInformation.getPlayer1Variables().end()); std::set allSourceVariables(abstractionInformation.getSourceVariables()); allSourceVariables.insert(abstractionInformation.getBottomStateVariable(true)); std::set allSuccessorVariables(abstractionInformation.getSuccessorVariables()); allSuccessorVariables.insert(abstractionInformation.getBottomStateVariable(false)); return std::make_unique>(abstractionInformation.getDdManagerAsSharedPointer(), reachableStates, initialStates, abstractionInformation.getDdManager().getBddZero(), transitionMatrix, bottomStateResult.states, allSourceVariables, allSuccessorVariables, abstractionInformation.getExtendedSourceSuccessorVariablePairs(), std::set(abstractionInformation.getPlayer1Variables().begin(), abstractionInformation.getPlayer1Variables().end()), player2Variables, allNondeterminismVariables, auxVariables, abstractionInformation.getPredicateToBddMap()); } template void JaniMenuGameAbstractor::exportToDot(std::string const& filename, storm::dd::Bdd const& highlightStates, storm::dd::Bdd const& filter) const { this->exportToDot(*currentGame, filename, highlightStates, filter); } template uint64_t JaniMenuGameAbstractor::getNumberOfPredicates() const { return abstractionInformation.getNumberOfPredicates(); } template void JaniMenuGameAbstractor::addTerminalStates(storm::expressions::Expression const& expression) { terminalStateExpressions.emplace_back(expression); } // Explicitly instantiate the class. template class JaniMenuGameAbstractor; template class JaniMenuGameAbstractor; #ifdef STORM_HAVE_CARL template class JaniMenuGameAbstractor; #endif } } }