#include "storm/abstraction/MenuGameRefiner.h" #include "storm/abstraction/AbstractionInformation.h" #include "storm/abstraction/MenuGameAbstractor.h" #include "storm/storage/BitVector.h" #include "storm/abstraction/ExplicitQualitativeGameResultMinMax.h" #include "storm/abstraction/ExplicitGameStrategyPair.h" #include "storm/abstraction/ExplicitQuantitativeResultMinMax.h" #include "storm/storage/dd/DdManager.h" #include "storm/storage/dd/Odd.h" #include "storm/utility/dd.h" #include "storm/utility/solver.h" #include "storm/utility/shortestPaths.h" #include "storm-parsers/parser/ExpressionParser.h" #include "storm/solver/MathsatSmtSolver.h" #include "storm/models/symbolic/StandardRewardModel.h" #include "storm/exceptions/InvalidStateException.h" #include "storm/settings/SettingsManager.h" #include "storm-config.h" #include "storm/adapters/RationalFunctionAdapter.h" namespace storm { namespace abstraction { using storm::settings::modules::AbstractionSettings; RefinementPredicates::RefinementPredicates(Source const& source, std::vector const& predicates) : source(source), predicates(predicates) { // Intentionally left empty. } RefinementPredicates::Source RefinementPredicates::getSource() const { return source; } std::vector const& RefinementPredicates::getPredicates() const { return predicates; } void RefinementPredicates::addPredicates(std::vector const& newPredicates) { this->predicates.insert(this->predicates.end(), newPredicates.begin(), newPredicates.end()); } template SymbolicMostProbablePathsResult::SymbolicMostProbablePathsResult(storm::dd::Add const& maxProbabilities, storm::dd::Bdd const& spanningTree) : maxProbabilities(maxProbabilities), spanningTree(spanningTree) { // Intentionally left empty. } template struct PivotStateCandidatesResult { storm::dd::Bdd reachableTransitionsMin; storm::dd::Bdd reachableTransitionsMax; storm::dd::Bdd pivotStates; }; template SymbolicPivotStateResult::SymbolicPivotStateResult(storm::dd::Bdd const& pivotState, storm::OptimizationDirection fromDirection, boost::optional> const& symbolicMostProbablePathsResult) : pivotState(pivotState), fromDirection(fromDirection), symbolicMostProbablePathsResult(symbolicMostProbablePathsResult) { // Intentionally left empty. } template MenuGameRefiner::MenuGameRefiner(MenuGameAbstractor& abstractor, std::unique_ptr&& smtSolver, MenuGameRefinerOptions const& options) : abstractor(abstractor), useInterpolation(storm::settings::getModule().isUseInterpolationSet()), splitAll(false), splitPredicates(false), rankPredicates(false), addedAllGuardsFlag(false), refinementPredicatesToInject(options.refinementPredicates), pivotSelectionHeuristic(), splitter(), equivalenceChecker(std::move(smtSolver)) { auto const& abstractionSettings = storm::settings::getModule(); pivotSelectionHeuristic = abstractionSettings.getPivotSelectionHeuristic(); AbstractionSettings::SplitMode splitMode = storm::settings::getModule().getSplitMode(); splitAll = splitMode == AbstractionSettings::SplitMode::All; splitPredicates = splitMode == AbstractionSettings::SplitMode::NonGuard; rankPredicates = abstractionSettings.isRankRefinementPredicatesSet(); addPredicatesEagerly = abstractionSettings.isUseEagerRefinementSet(); equivalenceChecker.addConstraints(abstractor.getAbstractionInformation().getConstraints()); if (abstractionSettings.isAddAllGuardsSet()) { std::vector guards; std::pair player1Choices = this->abstractor.get().getPlayer1ChoiceRange(); for (uint64_t index = player1Choices.first; index < player1Choices.second; ++index) { storm::expressions::Expression guard = this->abstractor.get().getGuard(index); if (!guard.isTrue() && !guard.isFalse()) { guards.push_back(guard); } } performRefinement(createGlobalRefinement(preprocessPredicates(guards, RefinementPredicates::Source::InitialGuard))); this->abstractor.get().notifyGuardsArePredicates(); addedAllGuardsFlag = true; } if (abstractionSettings.isAddAllInitialExpressionsSet()) { storm::expressions::Expression initialExpression = this->abstractor.get().getInitialExpression(); performRefinement(createGlobalRefinement(preprocessPredicates({initialExpression}, RefinementPredicates::Source::InitialExpression))); } } template void MenuGameRefiner::refine(std::vector const& predicates, bool allowInjection) const { performRefinement(createGlobalRefinement(preprocessPredicates(predicates, RefinementPredicates::Source::Manual)), allowInjection); } template SymbolicMostProbablePathsResult getMostProbablePathSpanningTree(storm::abstraction::MenuGame const& game, storm::dd::Bdd const& transitionFilter) { storm::dd::Add maxProbabilities = game.getInitialStates().template toAdd(); storm::dd::Bdd border = game.getInitialStates(); storm::dd::Bdd spanningTree = game.getManager().getBddZero(); storm::dd::Add transitionMatrix = ((transitionFilter && game.getExtendedTransitionMatrix().maxAbstractRepresentative(game.getProbabilisticBranchingVariables())).template toAdd() * game.getExtendedTransitionMatrix()).sumAbstract(game.getPlayer2Variables()); std::set variablesToAbstract(game.getRowVariables()); variablesToAbstract.insert(game.getPlayer1Variables().begin(), game.getPlayer1Variables().end()); variablesToAbstract.insert(game.getProbabilisticBranchingVariables().begin(), game.getProbabilisticBranchingVariables().end()); while (!border.isZero()) { // Determine the new maximal probabilities to all states. storm::dd::Add tmp = border.template toAdd() * transitionMatrix * maxProbabilities; storm::dd::Bdd newMaxProbabilityChoices = tmp.maxAbstractRepresentative(variablesToAbstract); storm::dd::Add newMaxProbabilities = tmp.maxAbstract(variablesToAbstract).swapVariables(game.getRowColumnMetaVariablePairs()); // Determine the probability values for which states strictly increased. storm::dd::Bdd updateStates = newMaxProbabilities.greater(maxProbabilities); maxProbabilities = updateStates.ite(newMaxProbabilities, maxProbabilities); // Delete all edges in the spanning tree that lead to states that need to be updated. spanningTree &= ((!updateStates).swapVariables(game.getRowColumnMetaVariablePairs())); // Add all edges that achieve the new maximal value to the spanning tree. spanningTree |= updateStates.swapVariables(game.getRowColumnMetaVariablePairs()) && newMaxProbabilityChoices; // Continue exploration from states that have been updated. border = updateStates; } return SymbolicMostProbablePathsResult(maxProbabilities, spanningTree); } template SymbolicPivotStateResult pickPivotState(AbstractionSettings::PivotSelectionHeuristic const& heuristic, storm::abstraction::MenuGame const& game, PivotStateCandidatesResult const& pivotStateCandidateResult, boost::optional> const& qualitativeResult, boost::optional> const& quantitativeResult) { // Get easy access to strategies. storm::dd::Bdd minPlayer1Strategy; storm::dd::Bdd minPlayer2Strategy; storm::dd::Bdd maxPlayer1Strategy; storm::dd::Bdd maxPlayer2Strategy; if (qualitativeResult) { minPlayer1Strategy = qualitativeResult.get().prob0Min.getPlayer1Strategy(); minPlayer2Strategy = qualitativeResult.get().prob0Min.getPlayer2Strategy(); maxPlayer1Strategy = qualitativeResult.get().prob1Max.getPlayer1Strategy(); maxPlayer2Strategy = qualitativeResult.get().prob1Max.getPlayer2Strategy(); } else if (quantitativeResult) { minPlayer1Strategy = quantitativeResult.get().min.getPlayer1Strategy(); minPlayer2Strategy = quantitativeResult.get().min.getPlayer2Strategy(); maxPlayer1Strategy = quantitativeResult.get().max.getPlayer1Strategy(); maxPlayer2Strategy = quantitativeResult.get().max.getPlayer2Strategy(); } else { STORM_LOG_ASSERT(false, "Either qualitative or quantitative result is required."); } storm::dd::Bdd pivotStates = pivotStateCandidateResult.pivotStates; if (heuristic == AbstractionSettings::PivotSelectionHeuristic::NearestMaximalDeviation) { // Set up used variables. storm::dd::Bdd initialStates = game.getInitialStates(); std::set const& rowVariables = game.getRowVariables(); std::set const& columnVariables = game.getColumnVariables(); storm::dd::Bdd transitionsMin = pivotStateCandidateResult.reachableTransitionsMin; storm::dd::Bdd transitionsMax = pivotStateCandidateResult.reachableTransitionsMax; storm::dd::Bdd frontierMin = initialStates; storm::dd::Bdd frontierMax = initialStates; storm::dd::Bdd frontierPivotStates = frontierMin && pivotStates; // Check whether we have pivot states on the very first level. uint64_t level = 0; bool foundPivotState = !frontierPivotStates.isZero(); if (foundPivotState) { STORM_LOG_TRACE("Picked pivot state from " << frontierPivotStates.getNonZeroCount() << " candidates on level " << level << ", " << pivotStates.getNonZeroCount() << " candidates in total."); return SymbolicPivotStateResult(frontierPivotStates.existsAbstractRepresentative(rowVariables), storm::OptimizationDirection::Minimize); } else { // Otherwise, we perform a simulatenous BFS in the sense that we make one step in both the min and max // transitions and check for pivot states we encounter. while (!foundPivotState) { frontierMin = frontierMin.relationalProduct(transitionsMin, rowVariables, columnVariables); frontierMax = frontierMax.relationalProduct(transitionsMax, rowVariables, columnVariables); ++level; storm::dd::Bdd frontierMinPivotStates = frontierMin && pivotStates; storm::dd::Bdd frontierMaxPivotStates = frontierMax && pivotStates; uint64_t numberOfPivotStateCandidatesOnLevel = frontierMinPivotStates.getNonZeroCount() + frontierMaxPivotStates.getNonZeroCount(); if (!frontierMinPivotStates.isZero() || !frontierMaxPivotStates.isZero()) { if (quantitativeResult) { storm::dd::Add frontierMinPivotStatesAdd = frontierMinPivotStates.template toAdd(); storm::dd::Add frontierMaxPivotStatesAdd = frontierMaxPivotStates.template toAdd(); storm::dd::Add diffMin = frontierMinPivotStatesAdd * quantitativeResult.get().max.values - frontierMinPivotStatesAdd * quantitativeResult.get().min.values; storm::dd::Add diffMax = frontierMaxPivotStatesAdd * quantitativeResult.get().max.values - frontierMaxPivotStatesAdd * quantitativeResult.get().min.values; ValueType diffValue; storm::OptimizationDirection direction; if (diffMin.getMax() >= diffMax.getMax()) { direction = storm::OptimizationDirection::Minimize; diffValue = diffMin.getMax(); } else { direction = storm::OptimizationDirection::Maximize; diffValue = diffMax.getMax(); } STORM_LOG_TRACE("Picked pivot state with difference " << diffValue << " from " << numberOfPivotStateCandidatesOnLevel << " candidates on level " << level << ", " << pivotStates.getNonZeroCount() << " candidates in total."); return SymbolicPivotStateResult(direction == storm::OptimizationDirection::Minimize ? diffMin.maxAbstractRepresentative(rowVariables) : diffMax.maxAbstractRepresentative(rowVariables), direction); } else { STORM_LOG_TRACE("Picked pivot state from " << numberOfPivotStateCandidatesOnLevel << " candidates on level " << level << ", " << pivotStates.getNonZeroCount() << " candidates in total."); storm::OptimizationDirection direction; if (!frontierMinPivotStates.isZero()) { direction = storm::OptimizationDirection::Minimize; } else { direction = storm::OptimizationDirection::Maximize; } return SymbolicPivotStateResult(direction == storm::OptimizationDirection::Minimize ? frontierMinPivotStates.existsAbstractRepresentative(rowVariables) : frontierMaxPivotStates.existsAbstractRepresentative(rowVariables), direction); } } } } } else { // Compute the most probable paths to the reachable states and the corresponding spanning trees. SymbolicMostProbablePathsResult minSymbolicMostProbablePathsResult = getMostProbablePathSpanningTree(game, minPlayer1Strategy && minPlayer2Strategy); SymbolicMostProbablePathsResult maxSymbolicMostProbablePathsResult = getMostProbablePathSpanningTree(game, maxPlayer1Strategy && maxPlayer2Strategy); storm::dd::Bdd selectedPivotState; storm::dd::Add score = pivotStates.template toAdd() * minSymbolicMostProbablePathsResult.maxProbabilities.maximum(maxSymbolicMostProbablePathsResult.maxProbabilities); if (heuristic == AbstractionSettings::PivotSelectionHeuristic::MaxWeightedDeviation && quantitativeResult) { score = score * (quantitativeResult.get().max.values - quantitativeResult.get().min.values); } selectedPivotState = score.maxAbstractRepresentative(game.getRowVariables()); STORM_LOG_TRACE("Selected pivot state with score " << score.getMax() << "."); storm::OptimizationDirection fromDirection = storm::OptimizationDirection::Minimize; storm::dd::Add selectedPivotStateAsAdd = selectedPivotState.template toAdd(); if ((selectedPivotStateAsAdd * maxSymbolicMostProbablePathsResult.maxProbabilities).getMax() > (selectedPivotStateAsAdd * minSymbolicMostProbablePathsResult.maxProbabilities).getMax()) { fromDirection = storm::OptimizationDirection::Maximize; } return SymbolicPivotStateResult(selectedPivotState, fromDirection, fromDirection == storm::OptimizationDirection::Minimize ? minSymbolicMostProbablePathsResult : maxSymbolicMostProbablePathsResult); } STORM_LOG_ASSERT(false, "This point must not be reached, because then no pivot state could be found."); return SymbolicPivotStateResult(storm::dd::Bdd(), storm::OptimizationDirection::Minimize); } template RefinementPredicates MenuGameRefiner::derivePredicatesFromDifferingChoices(storm::dd::Bdd const& player1Choice, storm::dd::Bdd const& lowerChoice, storm::dd::Bdd const& upperChoice) const { // Prepare result. storm::expressions::Expression newPredicate; bool fromGuard = false; // Get abstraction information for easier access. AbstractionInformation const& abstractionInformation = abstractor.get().getAbstractionInformation(); // Decode the index of the command chosen by player 1. storm::dd::Add player1ChoiceAsAdd = player1Choice.template toAdd(); auto pl1It = player1ChoiceAsAdd.begin(); uint_fast64_t player1Index = abstractionInformation.decodePlayer1Choice((*pl1It).first, abstractionInformation.getPlayer1VariableCount()); // Check whether there are bottom states in the game and whether one of the choices actually picks the // bottom state as the successor. bool buttomStateSuccessor = !((abstractionInformation.getBottomStateBdd(false, false) && lowerChoice) || (abstractionInformation.getBottomStateBdd(false, false) && upperChoice)).isZero(); // If one of the choices picks the bottom state, the new predicate is based on the guard of the appropriate // command (that is the player 1 choice). if (buttomStateSuccessor) { STORM_LOG_TRACE("One of the successors is a bottom state, taking a guard as a new predicate."); newPredicate = abstractor.get().getGuard(player1Index); fromGuard = true; STORM_LOG_DEBUG("Derived new predicate (based on guard): " << newPredicate); } else { STORM_LOG_TRACE("No bottom state successor. Deriving a new predicate using weakest precondition."); // Decode both choices to explicit mappings. std::map> lowerChoiceUpdateToSuccessorMapping = abstractionInformation.template decodeChoiceToUpdateSuccessorMapping(lowerChoice); std::map> upperChoiceUpdateToSuccessorMapping = abstractionInformation.template decodeChoiceToUpdateSuccessorMapping(upperChoice); STORM_LOG_ASSERT(lowerChoiceUpdateToSuccessorMapping.size() == upperChoiceUpdateToSuccessorMapping.size(), "Mismatching sizes after decode (" << lowerChoiceUpdateToSuccessorMapping.size() << " vs. " << upperChoiceUpdateToSuccessorMapping.size() << ")."); // First, sort updates according to probability mass. std::vector> updateIndicesAndMasses; for (auto const& entry : lowerChoiceUpdateToSuccessorMapping) { updateIndicesAndMasses.emplace_back(entry.first, entry.second.second); } std::sort(updateIndicesAndMasses.begin(), updateIndicesAndMasses.end(), [] (std::pair const& a, std::pair const& b) { return a.second > b.second; }); // Now find the update with the highest probability mass among all deviating updates. More specifically, // we determine the set of predicate indices for which there is a deviation. std::set deviationPredicates; uint64_t orderedUpdateIndex = 0; std::vector possibleRefinementPredicates; for (; orderedUpdateIndex < updateIndicesAndMasses.size(); ++orderedUpdateIndex) { storm::storage::BitVector const& lower = lowerChoiceUpdateToSuccessorMapping.at(updateIndicesAndMasses[orderedUpdateIndex].first).first; storm::storage::BitVector const& upper = upperChoiceUpdateToSuccessorMapping.at(updateIndicesAndMasses[orderedUpdateIndex].first).first; bool deviates = lower != upper; if (deviates) { std::map variableUpdates = abstractor.get().getVariableUpdates(player1Index, updateIndicesAndMasses[orderedUpdateIndex].first); for (uint64_t predicateIndex = 0; predicateIndex < lower.size(); ++predicateIndex) { if (lower[predicateIndex] != upper[predicateIndex]) { possibleRefinementPredicates.push_back(abstractionInformation.getPredicateByIndex(predicateIndex).substitute(variableUpdates).simplify()); if (!rankPredicates) { break; } } } break; } } STORM_LOG_ASSERT(!possibleRefinementPredicates.empty(), "Expected refinement predicates."); STORM_LOG_TRACE("Possible refinement predicates:"); for (auto const& pred : possibleRefinementPredicates) { STORM_LOG_TRACE(pred); } if (rankPredicates) { // Since we can choose any of the deviation predicates to perform the split, we go through the remaining // updates and build all deviation predicates. We can then check whether any of the possible refinement // predicates also eliminates another deviation. std::vector otherRefinementPredicates; for (; orderedUpdateIndex < updateIndicesAndMasses.size(); ++orderedUpdateIndex) { storm::storage::BitVector const& lower = lowerChoiceUpdateToSuccessorMapping.at(updateIndicesAndMasses[orderedUpdateIndex].first).first; storm::storage::BitVector const& upper = upperChoiceUpdateToSuccessorMapping.at(updateIndicesAndMasses[orderedUpdateIndex].first).first; bool deviates = lower != upper; if (deviates) { std::map newVariableUpdates = abstractor.get().getVariableUpdates(player1Index, updateIndicesAndMasses[orderedUpdateIndex].first); for (uint64_t predicateIndex = 0; predicateIndex < lower.size(); ++predicateIndex) { if (lower[predicateIndex] != upper[predicateIndex]) { otherRefinementPredicates.push_back(abstractionInformation.getPredicateByIndex(predicateIndex).substitute(newVariableUpdates).simplify()); } } } } // Finally, go through the refinement predicates and see how many deviations they cover. std::vector refinementPredicateIndexToCount(possibleRefinementPredicates.size(), 0); for (uint64_t index = 0; index < possibleRefinementPredicates.size(); ++index) { refinementPredicateIndexToCount[index] = 1; } for (auto const& otherPredicate : otherRefinementPredicates) { for (uint64_t index = 0; index < possibleRefinementPredicates.size(); ++index) { if (equivalenceChecker.areEquivalentModuloNegation(otherPredicate, possibleRefinementPredicates[index])) { ++refinementPredicateIndexToCount[index]; } } } // Find predicate that covers the most deviations. uint64_t chosenPredicateIndex = 0; for (uint64_t index = 0; index < possibleRefinementPredicates.size(); ++index) { if (refinementPredicateIndexToCount[index] > refinementPredicateIndexToCount[chosenPredicateIndex]) { chosenPredicateIndex = index; } } newPredicate = possibleRefinementPredicates[chosenPredicateIndex]; STORM_LOG_DEBUG("Derived new predicate (based on weakest-precondition): " << newPredicate << ", (equivalent to " << (refinementPredicateIndexToCount[chosenPredicateIndex] - 1) << " other refinement predicates)."); } else { newPredicate = possibleRefinementPredicates.front(); STORM_LOG_DEBUG("Derived new predicate (based on weakest-precondition): " << newPredicate << "."); } STORM_LOG_ASSERT(newPredicate.isInitialized(), "Could not derive new predicate as there is no deviation."); } return RefinementPredicates(fromGuard ? RefinementPredicates::Source::Guard : RefinementPredicates::Source::WeakestPrecondition, {newPredicate}); } template RefinementPredicates MenuGameRefiner::derivePredicatesFromChoice(storm::abstraction::MenuGame const& game, storm::dd::Bdd const& pivotState, storm::dd::Bdd const& player1Choice, storm::dd::Bdd const& choice, storm::dd::Bdd const& choiceSuccessors) const { // Prepare result. storm::expressions::Expression newPredicate; bool fromGuard = false; // Get abstraction information for easier access. AbstractionInformation const& abstractionInformation = abstractor.get().getAbstractionInformation(); // Decode the index of the command chosen by player 1. storm::dd::Add player1ChoiceAsAdd = player1Choice.template toAdd(); auto pl1It = player1ChoiceAsAdd.begin(); uint_fast64_t player1Index = abstractionInformation.decodePlayer1Choice((*pl1It).first, abstractionInformation.getPlayer1VariableCount()); // Check whether there are bottom states in the game and whether the choice actually picks the bottom state // as the successor. bool buttomStateSuccessor = !((abstractionInformation.getBottomStateBdd(false, false) && choiceSuccessors)).isZero(); std::vector possibleRefinementPredicates; // If the choice picks the bottom state, the new predicate is based on the guard of the appropriate command // (that is the player 1 choice). if (buttomStateSuccessor) { STORM_LOG_TRACE("One of the successors is a bottom state, taking a guard as a new predicate."); possibleRefinementPredicates.emplace_back(abstractor.get().getGuard(player1Index)); fromGuard = true; STORM_LOG_DEBUG("Derived new predicate (based on guard): " << possibleRefinementPredicates.back()); } else { STORM_LOG_TRACE("No bottom state successor. Deriving a new predicate using weakest precondition."); // Decode the choice successors. std::map> choiceUpdateToSuccessorMapping = abstractionInformation.template decodeChoiceToUpdateSuccessorMapping(choiceSuccessors); std::vector> sortedChoiceUpdateIndicesAndMasses; for (auto const& e : choiceUpdateToSuccessorMapping) { sortedChoiceUpdateIndicesAndMasses.emplace_back(e.first, e.second.second); } std::sort(sortedChoiceUpdateIndicesAndMasses.begin(), sortedChoiceUpdateIndicesAndMasses.end(), [] (std::pair const& a, std::pair const& b) { return a.second > b.second; }); // Compute all other (not taken) choices. std::set variablesToAbstract = game.getRowVariables(); variablesToAbstract.insert(game.getPlayer1Variables().begin(), game.getPlayer1Variables().end()); storm::dd::Bdd otherChoices = (pivotState && !choice && player1Choice && game.getExtendedTransitionMatrix().toBdd()).existsAbstract(variablesToAbstract); STORM_LOG_ASSERT(!otherChoices.isZero(), "Expected other choices."); // Decode the other choices. std::vector>> otherChoicesUpdateToSuccessorMappings = abstractionInformation.template decodeChoicesToUpdateSuccessorMapping(game.getPlayer2Variables(), otherChoices); for (auto const& otherChoice : otherChoicesUpdateToSuccessorMappings) { for (uint64_t updateIndex = 0; updateIndex < sortedChoiceUpdateIndicesAndMasses.size(); ++updateIndex) { storm::storage::BitVector const& choiceSuccessor = choiceUpdateToSuccessorMapping.at(sortedChoiceUpdateIndicesAndMasses[updateIndex].first).first; storm::storage::BitVector const& otherChoiceSuccessor = otherChoice.at(sortedChoiceUpdateIndicesAndMasses[updateIndex].first).first; bool deviates = choiceSuccessor != otherChoiceSuccessor; if (deviates) { std::map variableUpdates = abstractor.get().getVariableUpdates(player1Index, sortedChoiceUpdateIndicesAndMasses[updateIndex].first); for (uint64_t predicateIndex = 0; predicateIndex < choiceSuccessor.size(); ++predicateIndex) { if (choiceSuccessor[predicateIndex] != otherChoiceSuccessor[predicateIndex]) { possibleRefinementPredicates.push_back(abstractionInformation.getPredicateByIndex(predicateIndex).substitute(variableUpdates).simplify()); if (!rankPredicates) { break; } } } break; } } } STORM_LOG_ASSERT(!possibleRefinementPredicates.empty(), "Expected refinement predicates."); STORM_LOG_TRACE("Possible refinement predicates:"); for (auto const& pred : possibleRefinementPredicates) { STORM_LOG_TRACE(pred); } } return RefinementPredicates(fromGuard ? RefinementPredicates::Source::Guard : RefinementPredicates::Source::WeakestPrecondition, {possibleRefinementPredicates}); } template PivotStateCandidatesResult computePivotStates(storm::abstraction::MenuGame const& game, storm::dd::Bdd const& transitionMatrixBdd, storm::dd::Bdd const& minPlayer1Strategy, storm::dd::Bdd const& minPlayer2Strategy, storm::dd::Bdd const& maxPlayer1Strategy, storm::dd::Bdd const& maxPlayer2Strategy) { PivotStateCandidatesResult result; // Build the fragment of transitions that is reachable by either the min or the max strategies. result.reachableTransitionsMin = (transitionMatrixBdd && minPlayer1Strategy && minPlayer2Strategy).existsAbstract(game.getNondeterminismVariables()); result.reachableTransitionsMax = (transitionMatrixBdd && maxPlayer1Strategy && maxPlayer2Strategy).existsAbstract(game.getNondeterminismVariables()); // Start with all reachable states as potential pivot states. result.pivotStates = storm::utility::dd::computeReachableStates(game.getInitialStates(), result.reachableTransitionsMin, game.getRowVariables(), game.getColumnVariables()) || storm::utility::dd::computeReachableStates(game.getInitialStates(), result.reachableTransitionsMax, game.getRowVariables(), game.getColumnVariables()); // Then constrain these states by the requirement that for either the lower or upper player 1 choice the player 2 choices must be different and // that the difference is not because of a missing strategy in either case. // Start with constructing the player 2 states that have a min and a max strategy. storm::dd::Bdd constraint = minPlayer2Strategy.existsAbstract(game.getPlayer2Variables()) && maxPlayer2Strategy.existsAbstract(game.getPlayer2Variables()); // Now construct all player 2 choices that actually exist and differ in the min and max case. constraint &= minPlayer2Strategy.exclusiveOr(maxPlayer2Strategy); // Then restrict the pivot states by requiring existing and different player 2 choices. result.pivotStates &= ((minPlayer1Strategy || maxPlayer1Strategy) && constraint).existsAbstract(game.getNondeterminismVariables()); return result; } template RefinementPredicates MenuGameRefiner::derivePredicatesFromPivotState(storm::abstraction::MenuGame const& game, storm::dd::Bdd const& pivotState, storm::dd::Bdd const& minPlayer1Strategy, storm::dd::Bdd const& minPlayer2Strategy, storm::dd::Bdd const& maxPlayer1Strategy, storm::dd::Bdd const& maxPlayer2Strategy) const { // Compute the lower and the upper choice for the pivot state. std::set variablesToAbstract = game.getNondeterminismVariables(); variablesToAbstract.insert(game.getRowVariables().begin(), game.getRowVariables().end()); bool player1ChoicesDifferent = !(pivotState && minPlayer1Strategy).exclusiveOr(pivotState && maxPlayer1Strategy).isZero(); boost::optional predicates; // Derive predicates from lower choice. storm::dd::Bdd lowerChoice = pivotState && game.getExtendedTransitionMatrix().toBdd() && minPlayer1Strategy; storm::dd::Bdd lowerChoice1 = (lowerChoice && minPlayer2Strategy).existsAbstract(variablesToAbstract); storm::dd::Bdd lowerChoice2 = (lowerChoice && maxPlayer2Strategy).existsAbstract(variablesToAbstract); bool lowerChoicesDifferent = !lowerChoice1.exclusiveOr(lowerChoice2).isZero() && !lowerChoice1.isZero() && !lowerChoice2.isZero(); if (lowerChoicesDifferent) { STORM_LOG_TRACE("Deriving predicates based on lower choice."); if (this->addPredicatesEagerly) { predicates = derivePredicatesFromChoice(game, pivotState, (pivotState && minPlayer1Strategy).existsAbstract(game.getRowVariables()), lowerChoice && minPlayer2Strategy, lowerChoice1); } else { predicates = derivePredicatesFromDifferingChoices((pivotState && minPlayer1Strategy).existsAbstract(game.getRowVariables()), lowerChoice1, lowerChoice2); } } if (predicates && !player1ChoicesDifferent) { return predicates.get(); } boost::optional additionalPredicates; storm::dd::Bdd upperChoice = pivotState && game.getExtendedTransitionMatrix().toBdd() && maxPlayer1Strategy; storm::dd::Bdd upperChoice1 = (upperChoice && minPlayer2Strategy).existsAbstract(variablesToAbstract); storm::dd::Bdd upperChoice2 = (upperChoice && maxPlayer2Strategy).existsAbstract(variablesToAbstract); bool upperChoicesDifferent = !upperChoice1.exclusiveOr(upperChoice2).isZero() && !upperChoice1.isZero() && !upperChoice2.isZero(); if (upperChoicesDifferent) { STORM_LOG_TRACE("Deriving predicates based on upper choice."); if (this->addPredicatesEagerly) { additionalPredicates = derivePredicatesFromChoice(game, pivotState, (pivotState && maxPlayer1Strategy).existsAbstract(game.getRowVariables()), upperChoice && maxPlayer2Strategy, upperChoice1); } else { additionalPredicates = derivePredicatesFromDifferingChoices((pivotState && maxPlayer1Strategy).existsAbstract(game.getRowVariables()), upperChoice1, upperChoice2); } } if (additionalPredicates) { if (additionalPredicates.get().getSource() == RefinementPredicates::Source::Guard) { return additionalPredicates.get(); } else { if (!predicates) { predicates = additionalPredicates; } else { predicates.get().addPredicates(additionalPredicates.get().getPredicates()); } } } STORM_LOG_THROW(static_cast(predicates), storm::exceptions::InvalidStateException, "Could not derive predicates for refinement."); return predicates.get(); } template std::pair>, std::map> MenuGameRefiner::buildTrace(storm::expressions::ExpressionManager& expressionManager, storm::abstraction::MenuGame const& game, storm::dd::Bdd const& spanningTree, storm::dd::Bdd const& pivotState) const { std::vector> predicates; // Prepare some variables. AbstractionInformation const& abstractionInformation = abstractor.get().getAbstractionInformation(); std::set variablesToAbstract(game.getColumnVariables()); variablesToAbstract.insert(game.getPlayer1Variables().begin(), game.getPlayer1Variables().end()); variablesToAbstract.insert(game.getProbabilisticBranchingVariables().begin(), game.getProbabilisticBranchingVariables().end()); storm::expressions::Expression initialExpression = abstractor.get().getInitialExpression(); std::set oldVariables = initialExpression.getVariables(); for (auto const& predicate : abstractionInformation.getPredicates()) { std::set usedVariables = predicate.getVariables(); oldVariables.insert(usedVariables.begin(), usedVariables.end()); } std::map oldToNewVariables; for (auto const& variable : oldVariables) { oldToNewVariables[variable] = expressionManager.getVariable(variable.getName()); } std::map lastSubstitution; for (auto const& variable : oldToNewVariables) { lastSubstitution[variable.second] = variable.second; } std::map stepVariableToCopiedVariableMap; // Start with the target state part of the trace. storm::storage::BitVector decodedTargetState = abstractionInformation.decodeState(pivotState); predicates.emplace_back(abstractionInformation.getPredicates(decodedTargetState)); for (auto& predicate : predicates.back()) { predicate = predicate.changeManager(expressionManager); } // Add ranges of variables. for (auto const& pred : abstractionInformation.getConstraints()) { predicates.back().push_back(pred.changeManager(expressionManager)); } // Perform a backward search for an initial state. storm::dd::Bdd currentState = pivotState; while ((currentState && game.getInitialStates()).isZero()) { storm::dd::Bdd predecessorTransition = currentState.swapVariables(game.getRowColumnMetaVariablePairs()) && spanningTree; std::tuple decodedPredecessor = abstractionInformation.decodeStatePlayer1ChoiceAndUpdate(predecessorTransition); // Create a new copy of each variable to use for this step. std::map substitution; for (auto const& variablePair : oldToNewVariables) { storm::expressions::Variable variableCopy = expressionManager.declareVariableCopy(variablePair.second); substitution[variablePair.second] = variableCopy; stepVariableToCopiedVariableMap[variableCopy] = variablePair.second; } // Retrieve the variable updates that the predecessor needs to perform to get to the current state. auto variableUpdates = abstractor.get().getVariableUpdates(std::get<1>(decodedPredecessor), std::get<2>(decodedPredecessor)); for (auto const& oldNewVariablePair : oldToNewVariables) { storm::expressions::Variable const& newVariable = oldNewVariablePair.second; // If the variable was set, use its update expression. auto updateIt = variableUpdates.find(oldNewVariablePair.first); if (updateIt != variableUpdates.end()) { auto const& update = *updateIt; if (update.second.hasBooleanType()) { predicates.back().push_back(storm::expressions::iff(lastSubstitution.at(newVariable), update.second.changeManager(expressionManager).substitute(substitution))); } else { predicates.back().push_back(lastSubstitution.at(newVariable) == update.second.changeManager(expressionManager).substitute(substitution)); } } else { // Otherwise, make sure that the new variable maintains the old value. if (newVariable.hasBooleanType()) { predicates.back().push_back(storm::expressions::iff(lastSubstitution.at(newVariable), substitution.at(newVariable))); } else { predicates.back().push_back(lastSubstitution.at(newVariable) == substitution.at(newVariable)); } } } // Add the guard of the choice. predicates.back().push_back(abstractor.get().getGuard(std::get<1>(decodedPredecessor)).changeManager(expressionManager).substitute(substitution)); // Retrieve the predicate valuation in the predecessor. predicates.emplace_back(abstractionInformation.getPredicates(std::get<0>(decodedPredecessor))); for (auto& predicate : predicates.back()) { predicate = predicate.changeManager(expressionManager).substitute(substitution); } // Add ranges of variables. for (auto const& pred : abstractionInformation.getConstraints()) { predicates.back().push_back(pred.changeManager(expressionManager).substitute(substitution)); } // Move backwards one step. lastSubstitution = std::move(substitution); currentState = predecessorTransition.existsAbstract(variablesToAbstract); } predicates.back().push_back(initialExpression.changeManager(expressionManager).substitute(lastSubstitution)); return std::make_pair(predicates, stepVariableToCopiedVariableMap); } template const uint64_t ExplicitPivotStateResult::NO_PREDECESSOR = std::numeric_limits::max(); template std::pair, std::vector> MenuGameRefiner::buildReversedLabeledPath(ExplicitPivotStateResult const& pivotStateResult) const { std::pair, std::vector> result; result.first.emplace_back(pivotStateResult.pivotState); uint64_t currentState = pivotStateResult.predecessors[pivotStateResult.pivotState].first; uint64_t currentAction = pivotStateResult.predecessors[pivotStateResult.pivotState].second; while (currentState != ExplicitPivotStateResult::NO_PREDECESSOR) { STORM_LOG_ASSERT(currentAction != ExplicitPivotStateResult::NO_PREDECESSOR, "Expected predecessor action."); result.first.emplace_back(currentState); result.second.emplace_back(currentAction); currentAction = pivotStateResult.predecessors[currentState].second; currentState = pivotStateResult.predecessors[currentState].first; } STORM_LOG_ASSERT(result.first.size() == result.second.size() + 1, "Path size mismatch."); return result; } template std::pair>, std::map> MenuGameRefiner::buildTraceFromReversedLabeledPath(storm::expressions::ExpressionManager& expressionManager, std::vector const& reversedPath, std::vector const& reversedLabels, storm::dd::Odd const& odd, std::vector const* stateToOffset) const { STORM_LOG_ASSERT(reversedPath.size() == reversedLabels.size() + 1, "Path size mismatch."); std::vector> predicates; AbstractionInformation const& abstractionInformation = abstractor.get().getAbstractionInformation(); storm::expressions::Expression initialExpression = abstractor.get().getInitialExpression(); std::set oldVariables = initialExpression.getVariables(); for (auto const& predicate : abstractionInformation.getPredicates()) { std::set usedVariables = predicate.getVariables(); oldVariables.insert(usedVariables.begin(), usedVariables.end()); } std::map oldToNewVariables; for (auto const& variable : oldVariables) { oldToNewVariables[variable] = expressionManager.getVariable(variable.getName()); } std::map currentSubstitution; for (auto const& variable : oldToNewVariables) { currentSubstitution[variable.second] = variable.second; } std::map stepVariableToCopiedVariableMap; auto pathIt = reversedPath.rbegin(); // Decode pivot state. The state valuation also includes // * the bottom state, so we need to reserve one more, and // * the location variables, // so we need to reserve an appropriate size. uint64_t predicateValuationOffset = abstractionInformation.getNumberOfDdSourceLocationVariables() + 1; storm::storage::BitVector extendedPredicateValuation = odd.getEncoding(stateToOffset ? (*stateToOffset)[*pathIt] : *pathIt, abstractionInformation.getNumberOfPredicates() + predicateValuationOffset); ++pathIt; // Add all predicates of initial block. predicates.emplace_back(abstractionInformation.getPredicatesExcludingBottom(extendedPredicateValuation)); for (auto& predicate : predicates.back()) { predicate = predicate.changeManager(expressionManager); } // Add further constraints (like ranges). for (auto const& pred : abstractionInformation.getConstraints()) { predicates.back().push_back(pred.changeManager(expressionManager)); } // Add initial expression. predicates.back().push_back(initialExpression.changeManager(expressionManager)); // Traverse the path and construct necessary formula parts. auto actionIt = reversedLabels.rbegin(); uint64_t step = 0; for (; pathIt != reversedPath.rend(); ++pathIt) { // Add new predicate frame. predicates.emplace_back(); // Add guard of action. predicates.back().emplace_back(abstractor.get().getGuard(*actionIt).changeManager(expressionManager).substitute(currentSubstitution)); // Determine which variables are affected by the updates of the player 1 choice. std::set const& assignedVariables = abstractor.get().getAssignedVariables(*actionIt); // Create new instances of the affected variables. std::map newVariableMaps; for (auto const& variable : assignedVariables) { storm::expressions::Variable variableCopy = expressionManager.declareVariableCopy(variable); newVariableMaps[oldToNewVariables.at(variable)] = variableCopy; stepVariableToCopiedVariableMap[variableCopy] = variable; } // Retrieves the possible updates to the variables. auto variableUpdateVector = abstractor.get().getVariableUpdates(*actionIt); // Encode these updates. storm::expressions::Expression allVariableUpdateExpression; for (auto const& variableUpdates : variableUpdateVector) { storm::expressions::Expression variableUpdateExpression; for (auto const& update : variableUpdates) { if (update.second.hasBooleanType()) { variableUpdateExpression = variableUpdateExpression && storm::expressions::iff(newVariableMaps.at(update.first), update.second.changeManager(expressionManager).substitute(currentSubstitution)); } else { variableUpdateExpression = variableUpdateExpression && newVariableMaps.at(update.first) == update.second.changeManager(expressionManager).substitute(currentSubstitution); } } allVariableUpdateExpression = allVariableUpdateExpression || variableUpdateExpression; } if (!allVariableUpdateExpression.isInitialized()) { allVariableUpdateExpression = expressionManager.boolean(true); } predicates.back().emplace_back(allVariableUpdateExpression); // Incorporate the new variables in the current substitution. for (auto const& variablePair : newVariableMaps) { currentSubstitution[variablePair.first] = variablePair.second; } // Decode current state. extendedPredicateValuation = odd.getEncoding(stateToOffset ? (*stateToOffset)[*pathIt] : *pathIt, abstractionInformation.getNumberOfPredicates() + predicateValuationOffset); // Encode the predicates whose value might have changed. // FIXME: could be optimized by precomputation. for (uint64_t predicateIndex = 0; predicateIndex < abstractionInformation.getNumberOfPredicates(); ++predicateIndex) { auto const& predicate = abstractionInformation.getPredicateByIndex(predicateIndex); std::set usedVariables = predicate.getVariables(); bool containsAssignedVariables = false; for (auto usedIt = usedVariables.begin(), assignedIt = assignedVariables.begin();;) { if (usedIt == usedVariables.end() || assignedIt == assignedVariables.end()) { break; } if (*usedIt == *assignedIt) { containsAssignedVariables = true; break; } if (*usedIt < *assignedIt) { ++usedIt; } else { ++assignedIt; } } if (containsAssignedVariables) { auto transformedPredicate = predicate.changeManager(expressionManager).substitute(currentSubstitution); predicates.back().emplace_back(extendedPredicateValuation.get(predicateIndex + predicateValuationOffset) ? transformedPredicate : !transformedPredicate); } } // Enforce constraints of all assigned variables. for (auto const& constraint : abstractionInformation.getConstraints()) { std::set usedVariables = constraint.getVariables(); bool containsAssignedVariables = false; for (auto usedIt = usedVariables.begin(), assignedIt = assignedVariables.begin();;) { if (usedIt == usedVariables.end() || assignedIt == assignedVariables.end()) { break; } if (*usedIt == *assignedIt) { containsAssignedVariables = true; break; } if (*usedIt < *assignedIt) { ++usedIt; } else { ++assignedIt; } } if (containsAssignedVariables) { auto transformedConstraint = constraint.changeManager(expressionManager).substitute(currentSubstitution); predicates.back().emplace_back(transformedConstraint); } } ++actionIt; ++step; } return std::make_pair(predicates, stepVariableToCopiedVariableMap); } template boost::optional MenuGameRefiner::derivePredicatesFromInterpolationFromTrace(storm::expressions::ExpressionManager& interpolationManager, std::vector> const& trace, std::map const& variableSubstitution) const { AbstractionInformation const& abstractionInformation = abstractor.get().getAbstractionInformation(); auto start = std::chrono::high_resolution_clock::now(); boost::optional predicates; // Create solver and interpolation groups. auto assertionStart = std::chrono::high_resolution_clock::now(); storm::solver::MathsatSmtSolver interpolatingSolver(interpolationManager, storm::solver::MathsatSmtSolver::Options(true, false, true)); uint64_t stepCounter = 0; for (auto const& traceElement : trace) { // interpolatingSolver.push(); interpolatingSolver.setInterpolationGroup(stepCounter); for (auto const& predicate : traceElement) { interpolatingSolver.add(predicate); } ++stepCounter; } auto assertionEnd = std::chrono::high_resolution_clock::now(); STORM_LOG_TRACE("Asserting trace formula took " << std::chrono::duration_cast(assertionEnd - assertionStart).count() << "ms."); // Now encode the trace as an SMT problem. storm::solver::SmtSolver::CheckResult result = interpolatingSolver.check(); if (result == storm::solver::SmtSolver::CheckResult::Unsat) { STORM_LOG_TRACE("Trace formula is unsatisfiable. Starting interpolation."); std::vector interpolants; std::vector prefix; for (uint64_t step = 0; step < stepCounter; ++step) { prefix.push_back(step); storm::expressions::Expression interpolant = interpolatingSolver.getInterpolant(prefix).substitute(variableSubstitution).changeManager(abstractionInformation.getExpressionManager()); if (interpolant.isFalse()) { // If the interpolant is false, it means that the prefix has become unsatisfiable. STORM_LOG_TRACE("Trace formula became unsatisfiable at position " << step << " of " << stepCounter << "."); break; } if (!interpolant.isTrue()) { STORM_LOG_DEBUG("Derived new predicate (based on interpolation at step " << step << " out of " << stepCounter << "): " << interpolant); interpolants.push_back(interpolant); } } STORM_LOG_ASSERT(!interpolants.empty(), "Expected to have non-empty set of interpolants."); predicates = boost::make_optional(RefinementPredicates(RefinementPredicates::Source::Interpolation, interpolants)); } else { STORM_LOG_TRACE("Trace formula is satisfiable, not using interpolation."); } auto end = std::chrono::high_resolution_clock::now(); if (predicates) { STORM_LOG_TRACE("Deriving predicates using interpolation from witness of size " << trace.size() << " took " << std::chrono::duration_cast(end - start).count() << "ms."); } else { STORM_LOG_TRACE("Tried deriving predicates using interpolation but failed in " << std::chrono::duration_cast(end - start).count() << "ms."); } return predicates; } template boost::optional MenuGameRefiner::derivePredicatesFromInterpolation(storm::abstraction::MenuGame const& game, SymbolicPivotStateResult const& symbolicPivotStateResult, storm::dd::Bdd const& minPlayer1Strategy, storm::dd::Bdd const& minPlayer2Strategy, storm::dd::Bdd const& maxPlayer1Strategy, storm::dd::Bdd const& maxPlayer2Strategy) const { // Compute the most probable path from any initial state to the pivot state. SymbolicMostProbablePathsResult symbolicMostProbablePathsResult; if (!symbolicPivotStateResult.symbolicMostProbablePathsResult) { symbolicMostProbablePathsResult = getMostProbablePathSpanningTree(game, symbolicPivotStateResult.fromDirection == storm::OptimizationDirection::Minimize ? minPlayer1Strategy && minPlayer2Strategy : maxPlayer1Strategy && maxPlayer2Strategy); } else { symbolicMostProbablePathsResult = symbolicPivotStateResult.symbolicMostProbablePathsResult.get(); } // Create a new expression manager that we can use for the interpolation. AbstractionInformation const& abstractionInformation = abstractor.get().getAbstractionInformation(); std::shared_ptr interpolationManager = abstractionInformation.getExpressionManager().clone(); // Build the trace of the most probable path in terms of which predicates hold in each step. auto start = std::chrono::high_resolution_clock::now(); std::pair>, std::map> traceAndVariableSubstitution = buildTrace(*interpolationManager, game, symbolicMostProbablePathsResult.spanningTree, symbolicPivotStateResult.pivotState); auto end = std::chrono::high_resolution_clock::now(); STORM_LOG_DEBUG("Building the trace and variable substitution for interpolation from symbolic most-probable paths result took " << std::chrono::duration_cast(end - start).count() << "ms."); return derivePredicatesFromInterpolationFromTrace(*interpolationManager, traceAndVariableSubstitution.first, traceAndVariableSubstitution.second); } template boost::optional MenuGameRefiner::derivePredicatesFromInterpolation(storm::abstraction::MenuGame const& game, ExplicitPivotStateResult const& pivotStateResult, storm::dd::Odd const& odd) const { // Create a new expression manager that we can use for the interpolation. AbstractionInformation const& abstractionInformation = abstractor.get().getAbstractionInformation(); std::shared_ptr interpolationManager = abstractionInformation.getExpressionManager().clone(); // Build the trace of the most probable path in terms of which predicates hold in each step. auto start = std::chrono::high_resolution_clock::now(); std::pair, std::vector> labeledReversedPath = buildReversedLabeledPath(pivotStateResult); // If the initial state is the pivot state, we can stop here. if (labeledReversedPath.first.size() == 1) { return boost::none; } std::pair>, std::map> traceAndVariableSubstitution = buildTraceFromReversedLabeledPath(*interpolationManager, labeledReversedPath.first, labeledReversedPath.second, odd); auto end = std::chrono::high_resolution_clock::now(); STORM_LOG_DEBUG("Building the trace and variable substitution for interpolation from explicit most-probable paths result took " << std::chrono::duration_cast(end - start).count() << "ms."); return derivePredicatesFromInterpolationFromTrace(*interpolationManager, traceAndVariableSubstitution.first, traceAndVariableSubstitution.second); } template bool MenuGameRefiner::refine(storm::abstraction::MenuGame const& game, storm::dd::Bdd const& transitionMatrixBdd, SymbolicQualitativeGameResultMinMax const& qualitativeResult) const { STORM_LOG_TRACE("Trying refinement after qualitative check."); // Get all relevant strategies. storm::dd::Bdd minPlayer1Strategy = qualitativeResult.prob0Min.getPlayer1Strategy(); storm::dd::Bdd minPlayer2Strategy = qualitativeResult.prob0Min.getPlayer2Strategy(); storm::dd::Bdd maxPlayer1Strategy = qualitativeResult.prob1Max.getPlayer1Strategy(); storm::dd::Bdd maxPlayer2Strategy = qualitativeResult.prob1Max.getPlayer2Strategy(); // Redirect all player 1 choices of the min strategy to that of the max strategy if this leads to a player 2 // state that is also a prob 0 state. auto oldMinPlayer1Strategy = minPlayer1Strategy; minPlayer1Strategy = (maxPlayer1Strategy && qualitativeResult.prob0Min.getPlayer2States()).existsAbstract(game.getPlayer1Variables()).ite(maxPlayer1Strategy, minPlayer1Strategy); // Compute all reached pivot states. PivotStateCandidatesResult pivotStateCandidatesResult = computePivotStates(game, transitionMatrixBdd, minPlayer1Strategy, minPlayer2Strategy, maxPlayer1Strategy, maxPlayer2Strategy); // We can only refine in case we have a reachable player 1 state with a player 2 successor (under either // player 1's min or max strategy) such that from this player 2 state, both prob0 min and prob1 max define // strategies and they differ. Hence, it is possible that we arrive at a point where no suitable pivot state // is found. In this case, we abort the qualitative refinement here. if (pivotStateCandidatesResult.pivotStates.isZero()) { STORM_LOG_TRACE("Did not find pivot state in qualitative fragment."); return false; } STORM_LOG_ASSERT(!pivotStateCandidatesResult.pivotStates.isZero(), "Unable to proceed without pivot state candidates."); // Now that we have the pivot state candidates, we need to pick one. SymbolicPivotStateResult symbolicPivotStateResult = pickPivotState(pivotSelectionHeuristic, game, pivotStateCandidatesResult, qualitativeResult, boost::none); // // SANITY CHECK TO MAKE SURE OUR STRATEGIES ARE NOT BROKEN. // // FIXME. // auto min1ChoiceInPivot = SymbolicPivotStateResult.pivotState && game.getExtendedTransitionMatrix().toBdd() && minPlayer1Strategy; // STORM_LOG_ASSERT(!min1ChoiceInPivot.isZero(), "wth?"); // bool min1ChoiceInPivotIsProb0Min = !(min1ChoiceInPivot && qualitativeResult.prob0Min.getPlayer2States()).isZero(); // bool min1ChoiceInPivotIsProb0Max = !(min1ChoiceInPivot && qualitativeResult.prob0Max.getPlayer2States()).isZero(); // bool min1ChoiceInPivotIsProb1Min = !(min1ChoiceInPivot && qualitativeResult.prob1Min.getPlayer2States()).isZero(); // bool min1ChoiceInPivotIsProb1Max = !(min1ChoiceInPivot && qualitativeResult.prob1Max.getPlayer2States()).isZero(); // std::cout << "after redirection (min)" << std::endl; // std::cout << "min choice is prob0 in min? " << min1ChoiceInPivotIsProb0Min << ", max? " << min1ChoiceInPivotIsProb0Max << std::endl; // std::cout << "min choice is prob1 in min? " << min1ChoiceInPivotIsProb1Min << ", max? " << min1ChoiceInPivotIsProb1Max << std::endl; // std::cout << "min" << std::endl; // for (auto const& e : (min1ChoiceInPivot && minPlayer2Strategy).template toAdd()) { // std::cout << e.first << " -> " << e.second << std::endl; // } // std::cout << "max" << std::endl; // for (auto const& e : (min1ChoiceInPivot && maxPlayer2Strategy).template toAdd()) { // std::cout << e.first << " -> " << e.second << std::endl; // } // bool different = (min1ChoiceInPivot && minPlayer2Strategy) != (min1ChoiceInPivot && maxPlayer2Strategy); // std::cout << "min/max choice of player 2 is different? " << different << std::endl; // bool min1MinPlayer2Choice = !(min1ChoiceInPivot && minPlayer2Strategy).isZero(); // bool min1MaxPlayer2Choice = !(min1ChoiceInPivot && maxPlayer2Strategy).isZero(); // std::cout << "max/min choice there? " << min1MinPlayer2Choice << std::endl; // std::cout << "max/max choice there? " << min1MaxPlayer2Choice << std::endl; // // auto max1ChoiceInPivot = SymbolicPivotStateResult.pivotState && game.getExtendedTransitionMatrix().toBdd() && maxPlayer1Strategy; // STORM_LOG_ASSERT(!max1ChoiceInPivot.isZero(), "wth?"); // bool max1ChoiceInPivotIsProb0Min = !(max1ChoiceInPivot && qualitativeResult.prob0Min.getPlayer2States()).isZero(); // bool max1ChoiceInPivotIsProb0Max = !(max1ChoiceInPivot && qualitativeResult.prob0Max.getPlayer2States()).isZero(); // bool max1ChoiceInPivotIsProb1Min = !(max1ChoiceInPivot && qualitativeResult.prob1Min.getPlayer2States()).isZero(); // bool max1ChoiceInPivotIsProb1Max = !(max1ChoiceInPivot && qualitativeResult.prob1Max.getPlayer2States()).isZero(); // std::cout << "after redirection (max)" << std::endl; // std::cout << "max choice is prob0 in min? " << max1ChoiceInPivotIsProb0Min << ", max? " << max1ChoiceInPivotIsProb0Max << std::endl; // std::cout << "max choice is prob1 in min? " << max1ChoiceInPivotIsProb1Min << ", max? " << max1ChoiceInPivotIsProb1Max << std::endl; // different = (max1ChoiceInPivot && minPlayer2Strategy) != (max1ChoiceInPivot && maxPlayer2Strategy); // std::cout << "min/max choice of player 2 is different? " << different << std::endl; // bool max1MinPlayer2Choice = !(max1ChoiceInPivot && minPlayer2Strategy).isZero(); // bool max1MaxPlayer2Choice = !(max1ChoiceInPivot && maxPlayer2Strategy).isZero(); // std::cout << "max/min choice there? " << max1MinPlayer2Choice << std::endl; // std::cout << "max/max choice there? " << max1MaxPlayer2Choice << std::endl; boost::optional predicates; if (useInterpolation) { predicates = derivePredicatesFromInterpolation(game, symbolicPivotStateResult, minPlayer1Strategy, minPlayer2Strategy, maxPlayer1Strategy, maxPlayer2Strategy); } if (predicates) { STORM_LOG_TRACE("Obtained predicates by interpolation."); } else { predicates = derivePredicatesFromPivotState(game, symbolicPivotStateResult.pivotState, minPlayer1Strategy, minPlayer2Strategy, maxPlayer1Strategy, maxPlayer2Strategy); } STORM_LOG_THROW(static_cast(predicates), storm::exceptions::InvalidStateException, "Predicates needed to continue."); // Derive predicate based on the selected pivot state. std::vector preparedPredicates = preprocessPredicates(predicates.get().getPredicates(), predicates.get().getSource()); performRefinement(createGlobalRefinement(preparedPredicates)); return true; } template struct ExplicitDijkstraQueueElement { ExplicitDijkstraQueueElement(ValueType const& distance, uint64_t state, bool lower) : distance(distance), state(state), lower(lower) { // Intentionally left empty. } ValueType distance; uint64_t state; bool lower; }; template struct ExplicitDijkstraQueueElementLess { bool operator()(ExplicitDijkstraQueueElement const& a, ExplicitDijkstraQueueElement const& b) const { if (a.distance < b.distance) { return true; } else if (a.distance > b.distance) { return false; } else { if (a.state < b.state) { return true; } else if (a.state > b.state) { return false; } else { if (a.lower < b.lower) { return true; } else { return false; } } } } }; template void performDijkstraStep(std::set, ExplicitDijkstraQueueElementLess>& dijkstraQueue, bool probabilityDistances, std::vector& distances, std::vector>& predecessors, bool generatePredecessors, bool lower, uint64_t currentState, ValueType const& currentDistance, bool isPivotState, ExplicitGameStrategyPair const& strategyPair, ExplicitGameStrategyPair const& otherStrategyPair, std::vector const& player1Labeling, std::vector const& player2Grouping, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::BitVector const& targetStates, storm::storage::BitVector const& relevantStates) { if (strategyPair.getPlayer1Strategy().hasDefinedChoice(currentState)) { uint64_t player2Successor = strategyPair.getPlayer1Strategy().getChoice(currentState); uint64_t player2Choice = strategyPair.getPlayer2Strategy().getChoice(player2Successor); STORM_LOG_ASSERT(isPivotState || !otherStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2Successor) || strategyPair.getPlayer2Strategy().getChoice(player2Successor) == player2Choice, "Did not expect deviation in player 2 strategy."); STORM_LOG_ASSERT(player2Grouping[player2Successor] <= player2Choice && player2Choice < player2Grouping[player2Successor + 1], "Illegal choice for player 2."); for (auto const& entry : transitionMatrix.getRow(player2Choice)) { uint64_t player1Successor = entry.getColumn(); if (!relevantStates.get(player1Successor)) { continue; } ValueType alternateDistance; if (probabilityDistances) { alternateDistance = currentDistance * entry.getValue(); } else { alternateDistance = currentDistance + storm::utility::one(); } if (probabilityDistances ? alternateDistance > distances[player1Successor] : alternateDistance < distances[player1Successor]) { distances[player1Successor] = alternateDistance; if (generatePredecessors) { predecessors[player1Successor] = std::make_pair(currentState, player1Labeling[player2Successor]); } dijkstraQueue.emplace(alternateDistance, player1Successor, lower); } } } else { STORM_LOG_ASSERT(targetStates.get(currentState), "Expecting min strategy for non-target states."); } } template boost::optional> pickPivotState(bool generatePredecessors, AbstractionSettings::PivotSelectionHeuristic pivotSelectionHeuristic, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& player1Grouping, std::vector const& player1Labeling, storm::storage::BitVector const& initialStates, storm::storage::BitVector const& relevantStates, storm::storage::BitVector const& targetStates, ExplicitGameStrategyPair const& minStrategyPair, ExplicitGameStrategyPair const& maxStrategyPair, std::vector const* lowerValues = nullptr, std::vector const* upperValues = nullptr) { STORM_LOG_ASSERT(!lowerValues || upperValues, "Expected none or both value results."); STORM_LOG_ASSERT(!upperValues || lowerValues, "Expected none or both value results."); // Perform Dijkstra search that stays within the relevant states and searches for a (pivot) state in which // the strategies the lower or upper player 1 action leads to a player 2 state in which the choices differ. // To guarantee that the pivot state is reachable by either of the strategies, we do a parallel Dijkstra // search in both the lower and upper strategy. bool probabilityDistances = pivotSelectionHeuristic == storm::settings::modules::AbstractionSettings::PivotSelectionHeuristic::MostProbablePath; uint64_t numberOfStates = initialStates.size(); ValueType inftyDistance = probabilityDistances ? storm::utility::zero() : storm::utility::infinity(); ValueType zeroDistance = probabilityDistances ? storm::utility::one() : storm::utility::zero(); // Create storages for the lower and upper Dijkstra search. std::vector lowerDistances(numberOfStates, inftyDistance); std::vector> lowerPredecessors; std::vector upperDistances(numberOfStates, inftyDistance); std::vector> upperPredecessors; if (generatePredecessors) { lowerPredecessors.resize(numberOfStates, std::make_pair(ExplicitPivotStateResult::NO_PREDECESSOR, ExplicitPivotStateResult::NO_PREDECESSOR)); upperPredecessors.resize(numberOfStates, std::make_pair(ExplicitPivotStateResult::NO_PREDECESSOR, ExplicitPivotStateResult::NO_PREDECESSOR)); } // Use set as priority queue with unique membership. std::set, ExplicitDijkstraQueueElementLess> dijkstraQueue; for (auto initialState : initialStates) { if (!relevantStates.get(initialState)) { continue; } lowerDistances[initialState] = zeroDistance; upperDistances[initialState] = zeroDistance; dijkstraQueue.emplace(zeroDistance, initialState, true); dijkstraQueue.emplace(zeroDistance, initialState, false); } // For some heuristics, we need to potentially find more than just one pivot. bool considerDeviation = (pivotSelectionHeuristic == storm::settings::modules::AbstractionSettings::PivotSelectionHeuristic::NearestMaximalDeviation || pivotSelectionHeuristic == storm::settings::modules::AbstractionSettings::PivotSelectionHeuristic::MaxWeightedDeviation) && lowerValues && upperValues; bool foundPivotState = false; ExplicitDijkstraQueueElement pivotState(inftyDistance, 0, true); ValueType pivotStateDeviation = storm::utility::zero(); auto const& player2Grouping = transitionMatrix.getRowGroupIndices(); while (!dijkstraQueue.empty()) { // Take out currently best state. auto currentDijkstraElement = *dijkstraQueue.begin(); ValueType currentDistance = currentDijkstraElement.distance; uint64_t currentState = currentDijkstraElement.state; bool currentLower = currentDijkstraElement.lower; dijkstraQueue.erase(dijkstraQueue.begin()); if (foundPivotState && (probabilityDistances ? currentDistance < pivotState.distance : currentDistance > pivotState.distance)) { if (pivotSelectionHeuristic != storm::settings::modules::AbstractionSettings::PivotSelectionHeuristic::MaxWeightedDeviation) { // For the nearest maximal deviation and most probable path heuristics, future pivot states are // not important any more, because their distance will be strictly larger, so we can return the // current pivot state. return ExplicitPivotStateResult(pivotState.state, pivotState.distance, pivotState.lower ? std::move(lowerPredecessors) : std::move(upperPredecessors)); } else if (pivotStateDeviation >= currentDistance) { // If the heuristic is maximal weighted deviation and the weighted deviation for any future pivot // state is necessarily at most as high as the current one, we can abort the search. return ExplicitPivotStateResult(pivotState.state, pivotState.distance, pivotState.lower ? std::move(lowerPredecessors) : std::move(upperPredecessors)); } } // Determine whether the current state is a pivot state. bool isPivotState = false; if (minStrategyPair.getPlayer1Strategy().hasDefinedChoice(currentState)) { uint64_t player2Successor = minStrategyPair.getPlayer1Strategy().getChoice(currentState); if (minStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2Successor) && maxStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2Successor) && minStrategyPair.getPlayer2Strategy().getChoice(player2Successor) != maxStrategyPair.getPlayer2Strategy().getChoice(player2Successor)) { isPivotState = true; } } if (!isPivotState && maxStrategyPair.getPlayer1Strategy().hasDefinedChoice(currentState)) { uint64_t player2Successor = maxStrategyPair.getPlayer1Strategy().getChoice(currentState); if (minStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2Successor) && maxStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2Successor) && minStrategyPair.getPlayer2Strategy().getChoice(player2Successor) != maxStrategyPair.getPlayer2Strategy().getChoice(player2Successor)) { isPivotState = true; } } // If it is indeed a pivot state, we can potentially abort the search here. if (isPivotState) { if (considerDeviation && foundPivotState) { ValueType deviationOfCurrentState = (*upperValues)[currentState] - (*lowerValues)[currentState]; if (deviationOfCurrentState > pivotStateDeviation) { pivotState = currentDijkstraElement; pivotStateDeviation = deviationOfCurrentState; if (pivotSelectionHeuristic == storm::settings::modules::AbstractionSettings::PivotSelectionHeuristic::MaxWeightedDeviation) { // Scale the deviation with the distance (probability) for this heuristic. pivotStateDeviation *= currentDistance; } } } else if (!foundPivotState) { pivotState = currentDijkstraElement; foundPivotState = true; } // If there is no need to look at other deviations, stop here. if (!considerDeviation) { return ExplicitPivotStateResult(pivotState.state, pivotState.distance, pivotState.lower ? std::move(lowerPredecessors) : std::move(upperPredecessors)); } } // We only need to search further if the state has some value deviation. if (!lowerValues || !upperValues || (*lowerValues)[currentState] < (*upperValues)[currentState]) { if (currentLower) { performDijkstraStep(dijkstraQueue, probabilityDistances, lowerDistances, lowerPredecessors, generatePredecessors, true, currentState, currentDistance, isPivotState, minStrategyPair, maxStrategyPair, player1Labeling, player2Grouping, transitionMatrix, targetStates, relevantStates); } else { performDijkstraStep(dijkstraQueue, probabilityDistances, upperDistances, upperPredecessors, generatePredecessors, false, currentState, currentDistance, isPivotState, maxStrategyPair, minStrategyPair, player1Labeling, player2Grouping, transitionMatrix, targetStates, relevantStates); } } } if (foundPivotState) { return ExplicitPivotStateResult(pivotState.state, pivotState.distance, pivotState.lower ? std::move(lowerPredecessors) : std::move(upperPredecessors)); } // If we arrived at this point, we have explored all relevant states, but none of them was a pivot state, // which can happen when trying to refine using the qualitative result only. STORM_LOG_TRACE("Did not find pivot state in explicit Dijkstra search."); return boost::none; } template boost::optional MenuGameRefiner::derivePredicatesFromInterpolationReversedPath(storm::dd::Odd const& odd, storm::expressions::ExpressionManager& interpolationManager, std::vector const& reversedPath, std::vector const& stateToOffset, std::vector const& reversedLabels) const { // Build the trace of the most probable path in terms of which predicates hold in each step. auto start = std::chrono::high_resolution_clock::now(); std::pair>, std::map> traceAndVariableSubstitution = buildTraceFromReversedLabeledPath(interpolationManager, reversedPath, reversedLabels, odd, &stateToOffset); auto end = std::chrono::high_resolution_clock::now(); STORM_LOG_DEBUG("Building the trace and variable substitution for interpolation from explicit most-probable paths result took " << std::chrono::duration_cast(end - start).count() << "ms."); return derivePredicatesFromInterpolationFromTrace(interpolationManager, traceAndVariableSubstitution.first, traceAndVariableSubstitution.second); } template boost::optional MenuGameRefiner::derivePredicatesFromInterpolationKShortestPaths(storm::dd::Odd const& odd, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& player1Grouping, std::vector const& player1Labeling, std::vector const& player2Labeling, storm::storage::BitVector const& initialStates, storm::storage::BitVector const& constraintStates, storm::storage::BitVector const& targetStates, ValueType minProbability, ValueType maxProbability, ExplicitGameStrategyPair const& maxStrategyPair) const { // Extract the underlying DTMC of the max strategy pair. // Start by determining which states are reachable. storm::storage::BitVector reachableStatesInMaxFragment(initialStates); std::vector stack(initialStates.begin(), initialStates.end()); while (!stack.empty()) { uint64_t currentState = stack.back(); stack.pop_back(); uint64_t player2Successor = maxStrategyPair.getPlayer1Strategy().getChoice(currentState); uint64_t player2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(player2Successor); for (auto const& successorEntry : transitionMatrix.getRow(player2Choice)) { if (!reachableStatesInMaxFragment.get(successorEntry.getColumn())) { reachableStatesInMaxFragment.set(successorEntry.getColumn()); if (!targetStates.get(successorEntry.getColumn())) { stack.push_back(successorEntry.getColumn()); } } } } uint64_t numberOfReachableStates = reachableStatesInMaxFragment.getNumberOfSetBits(); std::vector reachableStatesWithLowerIndex = reachableStatesInMaxFragment.getNumberOfSetBitsBeforeIndices(); // Now construct the matrix just for these entries. storm::storage::SparseMatrixBuilder builder(numberOfReachableStates, numberOfReachableStates); storm::storage::BitVector dtmcInitialStates(numberOfReachableStates); typename storm::utility::ksp::ShortestPathsGenerator::StateProbMap targetProbabilities; std::vector stateToOffset(numberOfReachableStates); std::vector dtmcPlayer1Labels(numberOfReachableStates); uint64_t currentRow = 0; for (auto currentState : reachableStatesInMaxFragment) { stateToOffset[currentRow] = currentState; if (targetStates.get(currentState)) { targetProbabilities[currentRow] = storm::utility::one(); builder.addNextValue(currentRow, currentRow, storm::utility::one()); } else { uint64_t player2Successor = maxStrategyPair.getPlayer1Strategy().getChoice(currentState); dtmcPlayer1Labels[currentRow] = player1Labeling[player2Successor]; uint64_t player2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(player2Successor); for (auto const& successorEntry : transitionMatrix.getRow(player2Choice)) { builder.addNextValue(currentRow, reachableStatesWithLowerIndex[successorEntry.getColumn()], successorEntry.getValue()); } } if (initialStates.get(currentState)) { dtmcInitialStates.set(currentRow); } ++currentRow; } storm::storage::SparseMatrix dtmcMatrix = builder.build(); // Create a new expression manager that we can use for the interpolation. AbstractionInformation const& abstractionInformation = abstractor.get().getAbstractionInformation(); std::shared_ptr interpolationManager = abstractionInformation.getExpressionManager().clone(); // Use a path generator to compute k-shortest-paths. storm::utility::ksp::ShortestPathsGenerator pathGenerator(dtmcMatrix, targetProbabilities, dtmcInitialStates, storm::utility::ksp::MatrixFormat::straight); uint64_t currentShortestPath = 1; bool considerNextPath = true; boost::optional result; while (currentShortestPath < 100 && considerNextPath) { auto reversedPath = pathGenerator.getPathAsList(currentShortestPath); std::vector reversedLabels; for (auto stateIt = reversedPath.rbegin(); stateIt != reversedPath.rend() - 1; ++stateIt) { reversedLabels.push_back(player1Labeling[maxStrategyPair.getPlayer1Strategy().getChoice(stateToOffset[*stateIt])]); } boost::optional pathPredicates = derivePredicatesFromInterpolationReversedPath(odd, *interpolationManager, reversedPath, stateToOffset, reversedLabels); if (pathPredicates) { if (!result) { result = RefinementPredicates(RefinementPredicates::Source::Interpolation, pathPredicates.get().getPredicates()); } else { result.get().addPredicates(pathPredicates.get().getPredicates()); } } ++currentShortestPath; } exit(-1); return result; } template bool MenuGameRefiner::refine(storm::abstraction::MenuGame const& game, storm::dd::Odd const& odd, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& player1Grouping, std::vector const& player1Labeling, std::vector const& player2Labeling, storm::storage::BitVector const& initialStates, storm::storage::BitVector const& constraintStates, storm::storage::BitVector const& targetStates, ExplicitQualitativeGameResultMinMax const& qualitativeResult, ExplicitGameStrategyPair const& minStrategyPair, ExplicitGameStrategyPair const& maxStrategyPair) const { // boost::optional kShortestPathPredicates = derivePredicatesFromInterpolationKShortestPaths(odd, transitionMatrix, player1Grouping, player1Labeling, player2Labeling, initialStates, constraintStates, targetStates, storm::utility::zero(), storm::utility::one(), maxStrategyPair); // Compute the set of states whose result we have for the min and max case. storm::storage::BitVector relevantStates = (qualitativeResult.getProb0Min().getStates() | qualitativeResult.getProb1Min().getStates()) & (qualitativeResult.getProb0Max().getStates() | qualitativeResult.getProb1Max().getStates()); boost::optional> optionalPivotStateResult = pickPivotState(useInterpolation, pivotSelectionHeuristic, transitionMatrix, player1Grouping, player1Labeling, initialStates, relevantStates, targetStates, minStrategyPair, maxStrategyPair); // If there was no pivot state, continue the search. if (!optionalPivotStateResult) { STORM_LOG_TRACE("Did not find pivot state in qualitative fragment."); return false; } // Otherwise, we can refine. auto const& pivotStateResult = optionalPivotStateResult.get(); STORM_LOG_TRACE("Found pivot state " << pivotStateResult.pivotState << " with distance " << pivotStateResult.distance << "."); // Translate the explicit states/choices to the symbolic ones, so we can reuse the predicate derivation techniques. auto const& abstractionInformation = abstractor.get().getAbstractionInformation(); uint64_t pivotState = pivotStateResult.pivotState; storm::dd::Bdd symbolicPivotState = storm::dd::Bdd::getEncoding(game.getManager(), pivotState, odd, game.getRowVariables()); storm::dd::Bdd minPlayer1Strategy = game.getManager().getBddZero(); storm::dd::Bdd maxPlayer1Strategy = game.getManager().getBddZero(); storm::dd::Bdd minPlayer2Strategy = game.getManager().getBddZero(); storm::dd::Bdd maxPlayer2Strategy = game.getManager().getBddZero(); if (minStrategyPair.getPlayer1Strategy().hasDefinedChoice(pivotState)) { uint64_t player2State = minStrategyPair.getPlayer1Strategy().getChoice(pivotState); STORM_LOG_ASSERT(player1Grouping[pivotState] <= player2State && player2State < player1Grouping[pivotState + 1], "Illegal choice for player 1."); minPlayer1Strategy |= symbolicPivotState && abstractionInformation.encodePlayer1Choice(player1Labeling[player2State], abstractionInformation.getPlayer1VariableCount()); if (minStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2State)) { uint64_t player2Choice = minStrategyPair.getPlayer2Strategy().getChoice(player2State); minPlayer2Strategy |= minPlayer1Strategy && abstractionInformation.encodePlayer2Choice(player2Labeling[player2Choice], 0, game.getPlayer2Variables().size()); } if (maxStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2State)) { uint64_t player2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(player2State); maxPlayer2Strategy |= minPlayer1Strategy && abstractionInformation.encodePlayer2Choice(player2Labeling[player2Choice], 0, game.getPlayer2Variables().size()); } } if (maxStrategyPair.getPlayer1Strategy().hasDefinedChoice(pivotState)) { uint64_t player2State = maxStrategyPair.getPlayer1Strategy().getChoice(pivotState); STORM_LOG_ASSERT(player1Grouping[pivotState] <= player2State && player2State < player1Grouping[pivotState + 1], "Illegal choice for player 1."); maxPlayer1Strategy |= symbolicPivotState && abstractionInformation.encodePlayer1Choice(player1Labeling[player2State], abstractionInformation.getPlayer1VariableCount()); if (minStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2State)) { uint64_t player2Choice = minStrategyPair.getPlayer2Strategy().getChoice(player2State); minPlayer2Strategy |= maxPlayer1Strategy && abstractionInformation.encodePlayer2Choice(player2Labeling[player2Choice], 0, game.getPlayer2Variables().size()); } if (maxStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2State)) { uint64_t player2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(player2State); maxPlayer2Strategy |= maxPlayer1Strategy && abstractionInformation.encodePlayer2Choice(player2Labeling[player2Choice], 0, game.getPlayer2Variables().size()); } } auto end = std::chrono::high_resolution_clock::now(); boost::optional predicates; if (useInterpolation) { predicates = derivePredicatesFromInterpolation(game, pivotStateResult, odd); } if (!predicates) { predicates = derivePredicatesFromPivotState(game, symbolicPivotState, minPlayer1Strategy, minPlayer2Strategy, maxPlayer1Strategy, maxPlayer2Strategy); } end = std::chrono::high_resolution_clock::now(); STORM_LOG_THROW(static_cast(predicates), storm::exceptions::InvalidStateException, "Predicates needed to continue."); std::vector preparedPredicates = preprocessPredicates(predicates.get().getPredicates(), predicates.get().getSource()); performRefinement(createGlobalRefinement(preparedPredicates)); return true; } template bool MenuGameRefiner::refine(storm::abstraction::MenuGame const& game, storm::dd::Odd const& odd, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& player1Grouping, std::vector const& player1Labeling, std::vector const& player2Labeling, storm::storage::BitVector const& initialStates, storm::storage::BitVector const& constraintStates, storm::storage::BitVector const& targetStates, ExplicitQuantitativeResultMinMax const& quantitativeResult, ExplicitGameStrategyPair const& minStrategyPair, ExplicitGameStrategyPair const& maxStrategyPair) const { // ValueType lower = quantitativeResult.getMin().getRange(initialStates).first; // ValueType upper = quantitativeResult.getMax().getRange(initialStates).second; // boost::optional kShortestPathPredicates = derivePredicatesFromInterpolationKShortestPaths(odd, transitionMatrix, player1Grouping, player1Labeling, player2Labeling, initialStates, constraintStates, targetStates, lower, upper, maxStrategyPair); // Compute the set of states whose result we have for the min and max case. storm::storage::BitVector relevantStates(odd.getTotalOffset(), true); boost::optional> optionalPivotStateResult = pickPivotState(useInterpolation, pivotSelectionHeuristic, transitionMatrix, player1Grouping, player1Labeling, initialStates, relevantStates, targetStates, minStrategyPair, maxStrategyPair, &quantitativeResult.getMin().getValues(), &quantitativeResult.getMax().getValues()); STORM_LOG_THROW(optionalPivotStateResult, storm::exceptions::InvalidStateException, "Did not find pivot state to proceed."); // Otherwise, we can refine. auto const& pivotStateResult = optionalPivotStateResult.get(); STORM_LOG_TRACE("Found pivot state " << pivotStateResult.pivotState << " with distance " << pivotStateResult.distance << "."); // Translate the explicit states/choices to the symbolic ones, so we can reuse the predicate derivation techniques. auto const& abstractionInformation = abstractor.get().getAbstractionInformation(); uint64_t pivotState = pivotStateResult.pivotState; storm::dd::Bdd symbolicPivotState = storm::dd::Bdd::getEncoding(game.getManager(), pivotState, odd, game.getRowVariables()); storm::dd::Bdd minPlayer1Strategy = game.getManager().getBddZero(); storm::dd::Bdd maxPlayer1Strategy = game.getManager().getBddZero(); storm::dd::Bdd minPlayer2Strategy = game.getManager().getBddZero(); storm::dd::Bdd maxPlayer2Strategy = game.getManager().getBddZero(); if (minStrategyPair.getPlayer1Strategy().hasDefinedChoice(pivotState)) { uint64_t player2State = minStrategyPair.getPlayer1Strategy().getChoice(pivotState); STORM_LOG_ASSERT(player1Grouping[pivotState] <= player2State && player2State < player1Grouping[pivotState + 1], "Illegal choice for player 1."); minPlayer1Strategy |= symbolicPivotState && abstractionInformation.encodePlayer1Choice(player1Labeling[player2State], abstractionInformation.getPlayer1VariableCount()); if (minStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2State)) { uint64_t player2Choice = minStrategyPair.getPlayer2Strategy().getChoice(player2State); minPlayer2Strategy |= minPlayer1Strategy && abstractionInformation.encodePlayer2Choice(player2Labeling[player2Choice], 0, game.getPlayer2Variables().size()); } if (maxStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2State)) { uint64_t player2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(player2State); maxPlayer2Strategy |= minPlayer1Strategy && abstractionInformation.encodePlayer2Choice(player2Labeling[player2Choice], 0, game.getPlayer2Variables().size()); } } if (maxStrategyPair.getPlayer1Strategy().hasDefinedChoice(pivotState)) { uint64_t player2State = maxStrategyPair.getPlayer1Strategy().getChoice(pivotState); STORM_LOG_ASSERT(player1Grouping[pivotState] <= player2State && player2State < player1Grouping[pivotState + 1], "Illegal choice for player 1."); maxPlayer1Strategy |= symbolicPivotState && abstractionInformation.encodePlayer1Choice(player1Labeling[player2State], abstractionInformation.getPlayer1VariableCount()); if (minStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2State)) { uint64_t player2Choice = minStrategyPair.getPlayer2Strategy().getChoice(player2State); minPlayer2Strategy |= maxPlayer1Strategy && abstractionInformation.encodePlayer2Choice(player2Labeling[player2Choice], 0, game.getPlayer2Variables().size()); } if (maxStrategyPair.getPlayer2Strategy().hasDefinedChoice(player2State)) { uint64_t player2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(player2State); maxPlayer2Strategy |= maxPlayer1Strategy && abstractionInformation.encodePlayer2Choice(player2Labeling[player2Choice], 0, game.getPlayer2Variables().size()); } } boost::optional predicates; if (useInterpolation) { predicates = derivePredicatesFromInterpolation(game, pivotStateResult, odd); } if (!predicates) { predicates = derivePredicatesFromPivotState(game, symbolicPivotState, minPlayer1Strategy, minPlayer2Strategy, maxPlayer1Strategy, maxPlayer2Strategy); } STORM_LOG_THROW(static_cast(predicates), storm::exceptions::InvalidStateException, "Predicates needed to continue."); std::vector preparedPredicates = preprocessPredicates(predicates.get().getPredicates(), predicates.get().getSource()); performRefinement(createGlobalRefinement(preparedPredicates)); return true; } template bool MenuGameRefiner::refine(storm::abstraction::MenuGame const& game, storm::dd::Bdd const& transitionMatrixBdd, SymbolicQuantitativeGameResultMinMax const& quantitativeResult) const { STORM_LOG_TRACE("Refining after quantitative check."); // Get all relevant strategies. storm::dd::Bdd minPlayer1Strategy = quantitativeResult.min.getPlayer1Strategy(); storm::dd::Bdd minPlayer2Strategy = quantitativeResult.min.getPlayer2Strategy(); storm::dd::Bdd maxPlayer1Strategy = quantitativeResult.max.getPlayer1Strategy(); storm::dd::Bdd maxPlayer2Strategy = quantitativeResult.max.getPlayer2Strategy(); // Compute all reached pivot states. PivotStateCandidatesResult pivotStateCandidatesResult = computePivotStates(game, transitionMatrixBdd, minPlayer1Strategy, minPlayer2Strategy, maxPlayer1Strategy, maxPlayer2Strategy); STORM_LOG_ASSERT(!pivotStateCandidatesResult.pivotStates.isZero(), "Unable to refine without pivot state candidates."); // Now that we have the pivot state candidates, we need to pick one. SymbolicPivotStateResult symbolicPivotStateResult = pickPivotState(pivotSelectionHeuristic, game, pivotStateCandidatesResult, boost::none, quantitativeResult); boost::optional predicates; if (useInterpolation) { predicates = derivePredicatesFromInterpolation(game, symbolicPivotStateResult, minPlayer1Strategy, minPlayer2Strategy, maxPlayer1Strategy, maxPlayer2Strategy); } if (predicates) { STORM_LOG_TRACE("Obtained predicates by interpolation."); } else { predicates = derivePredicatesFromPivotState(game, symbolicPivotStateResult.pivotState, minPlayer1Strategy, minPlayer2Strategy, maxPlayer1Strategy, maxPlayer2Strategy); } STORM_LOG_THROW(static_cast(predicates), storm::exceptions::InvalidStateException, "Predicates needed to continue."); std::vector preparedPredicates = preprocessPredicates(predicates.get().getPredicates(), predicates.get().getSource()); performRefinement(createGlobalRefinement(preparedPredicates)); return true; } struct VariableSetHash { std::size_t operator()(std::set const& set) const { return set.size(); } }; template std::vector MenuGameRefiner::preprocessPredicates(std::vector const& predicates, RefinementPredicates::Source const& source) const { bool split = source == RefinementPredicates::Source::WeakestPrecondition && splitPredicates; split |= source == RefinementPredicates::Source::Interpolation && splitPredicates; split |= splitAll; if (split) { auto start = std::chrono::high_resolution_clock::now(); AbstractionInformation const& abstractionInformation = abstractor.get().getAbstractionInformation(); std::vector cleanedAtoms; std::unordered_map, std::vector, VariableSetHash> predicateClasses; for (auto const& predicate : predicates) { // Split the predicates. std::vector atoms = splitter.split(predicate); // Put the atoms into the right class. for (auto const& atom : atoms) { std::set vars = atom.getVariables(); predicateClasses[vars].push_back(atom); } } // Now clean the classes in the sense that redundant predicates are cleaned. uint64_t checkCounter = 0; for (auto& predicateClass : predicateClasses) { std::vector cleanedAtomsOfClass; for (auto const& predicate : predicateClass.second) { bool addPredicate = true; for (auto const& atom : cleanedAtomsOfClass) { if (predicate.areSame(atom)) { addPredicate = false; break; } ++checkCounter; if (equivalenceChecker.areEquivalentModuloNegation(predicate, atom)) { addPredicate = false; break; } } if (addPredicate) { cleanedAtomsOfClass.push_back(predicate); } } predicateClass.second = std::move(cleanedAtomsOfClass); } std::unordered_map, std::vector, VariableSetHash> oldPredicateClasses; for (auto const& oldPredicate : abstractionInformation.getPredicates()) { std::set vars = oldPredicate.getVariables(); oldPredicateClasses[vars].push_back(oldPredicate); } for (auto const& predicateClass : predicateClasses) { auto oldPredicateClassIt = oldPredicateClasses.find(predicateClass.first); if (oldPredicateClassIt != oldPredicateClasses.end()) { for (auto const& newAtom : predicateClass.second) { bool addAtom = true; for (auto const& oldPredicate : oldPredicateClassIt->second) { if (newAtom.areSame(oldPredicate)) { addAtom = false; break; } ++checkCounter; if (equivalenceChecker.areEquivalentModuloNegation(newAtom, oldPredicate)) { addAtom = false; break; } } if (addAtom) { cleanedAtoms.push_back(newAtom); } } } else { cleanedAtoms.insert(cleanedAtoms.end(), predicateClass.second.begin(), predicateClass.second.end()); } } auto end = std::chrono::high_resolution_clock::now(); STORM_LOG_TRACE("Preprocessing predicates took " << std::chrono::duration_cast(end - start).count() << "ms (" << checkCounter << " checks)."); return cleanedAtoms; } else { // If no splitting of the predicates is required, just forward the refinement request to the abstractor. } return predicates; } template std::vector MenuGameRefiner::createGlobalRefinement(std::vector const& predicates) const { std::vector commands; commands.emplace_back(predicates); return commands; } template void MenuGameRefiner::performRefinement(std::vector const& refinementCommands, bool allowInjection) const { if (!refinementPredicatesToInject.empty() && allowInjection) { STORM_LOG_INFO("Refining with (injected) predicates."); for (auto const& predicate : refinementPredicatesToInject.back()) { STORM_LOG_INFO(predicate); } abstractor.get().refine(RefinementCommand(refinementPredicatesToInject.back())); refinementPredicatesToInject.pop_back(); } else { for (auto const& command : refinementCommands) { STORM_LOG_INFO("Refining with " << command.getPredicates().size() << " predicates."); for (auto const& predicate : command.getPredicates()) { STORM_LOG_INFO(predicate); } if (!command.getPredicates().empty()) { abstractor.get().refine(command); } } } STORM_LOG_TRACE("Current set of predicates:"); for (auto const& predicate : abstractor.get().getAbstractionInformation().getPredicates()) { STORM_LOG_TRACE(predicate); } } template bool MenuGameRefiner::addedAllGuards() const { return addedAllGuardsFlag; } template class MenuGameRefiner; template class MenuGameRefiner; template class MenuGameRefiner; #ifdef STORM_HAVE_CARL // Currently, this instantiation does not work. // template class MenuGameRefiner; #endif } }