#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/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) : abstractor(abstractor), useInterpolation(storm::settings::getModule().isUseInterpolationSet()), splitAll(false), splitPredicates(false), rankPredicates(false), addedAllGuardsFlag(false), 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(); equivalenceChecker.addConstraints(abstractor.getAbstractionInformation().getConstraints()); if (storm::settings::getModule().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))); addedAllGuardsFlag = true; } } template void MenuGameRefiner::refine(std::vector const& predicates) const { performRefinement(createGlobalRefinement(preprocessPredicates(predicates, RefinementPredicates::Source::Manual))); } 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 informatin 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; } } } ++orderedUpdateIndex; break; } } STORM_LOG_ASSERT(!possibleRefinementPredicates.empty(), "Expected refinement predicates."); 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 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 predicate based on lower choice."); predicates = derivePredicatesFromDifferingChoices((pivotState && minPlayer1Strategy).existsAbstract(game.getRowVariables()), lowerChoice1, lowerChoice2); } if (predicates && (!player1ChoicesDifferent || predicates.get().getSource() == RefinementPredicates::Source::Guard)) { return predicates.get(); } else { 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 predicate based on upper choice."); additionalPredicates = derivePredicatesFromDifferingChoices((pivotState && maxPlayer1Strategy).existsAbstract(game.getRowVariables()), upperChoice1, upperChoice2); } if (additionalPredicates) { if (additionalPredicates.get().getSource() == RefinementPredicates::Source::Guard) { return additionalPredicates.get(); } 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); } // 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); } // 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::map> MenuGameRefiner::buildTrace(storm::expressions::ExpressionManager& expressionManager, storm::abstraction::MenuGame const& game, ExplicitPivotStateResult const& pivotStateResult, storm::dd::Odd const& odd) const { 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 lastSubstitution; for (auto const& variable : oldToNewVariables) { lastSubstitution[variable.second] = variable.second; } std::map stepVariableToCopiedVariableMap; // The state valuation also includes the bottom state, so we need to reserve one more than the number of // predicates here. storm::storage::BitVector extendedPredicateValuation = odd.getEncoding(pivotStateResult.pivotState, abstractionInformation.getNumberOfPredicates() + 1); // Decode pivot state. predicates.emplace_back(abstractionInformation.getPredicatesExcludingBottom(extendedPredicateValuation)); for (auto& predicate : predicates.back()) { predicate = predicate.changeManager(expressionManager); } // Perform a backward traversal from the pivot state along the chosen predecessors until there is no more // predecessors. uint64_t currentState = pivotStateResult.predecessors[pivotStateResult.pivotState].first; uint64_t currentAction = pivotStateResult.predecessors[pivotStateResult.pivotState].second; while (currentState != ExplicitPivotStateResult::NO_PREDECESSOR) { // 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 variableUpdateVector = abstractor.get().getVariableUpdates(currentAction); storm::expressions::Expression allVariableUpdateExpression; for (auto const& variableUpdates : variableUpdateVector) { storm::expressions::Expression variableUpdateExpression; 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()) { variableUpdateExpression = variableUpdateExpression && storm::expressions::iff(lastSubstitution.at(newVariable), update.second.changeManager(expressionManager).substitute(substitution)); } else { variableUpdateExpression = variableUpdateExpression && lastSubstitution.at(newVariable) == update.second.changeManager(expressionManager).substitute(substitution); } } else { // Otherwise, make sure that the new variable maintains the old value. if (newVariable.hasBooleanType()) { variableUpdateExpression = variableUpdateExpression && storm::expressions::iff(lastSubstitution.at(newVariable), substitution.at(newVariable)); } else { variableUpdateExpression = variableUpdateExpression && lastSubstitution.at(newVariable) == substitution.at(newVariable); } } } allVariableUpdateExpression = allVariableUpdateExpression || variableUpdateExpression; } // Add variable update expression. predicates.back().emplace_back(allVariableUpdateExpression); // Add the guard of the choice. predicates.back().emplace_back(abstractor.get().getGuard(currentAction).changeManager(expressionManager).substitute(substitution)); // Retrieve the predicate valuation in the predecessor. extendedPredicateValuation = odd.getEncoding(currentState, abstractionInformation.getNumberOfPredicates() + 1); predicates.emplace_back(abstractionInformation.getPredicatesExcludingBottom(extendedPredicateValuation)); for (auto& predicate : predicates.back()) { predicate = predicate.changeManager(expressionManager).substitute(substitution); } // Move backwards one step. lastSubstitution = std::move(substitution); std::pair predecessorPair = pivotStateResult.predecessors[currentState]; currentState = predecessorPair.first; currentAction = predecessorPair.second; } predicates.back().push_back(initialExpression.changeManager(expressionManager).substitute(lastSubstitution)); return std::make_pair(predicates, stepVariableToCopiedVariableMap); } template boost::optional derivePredicatesFromInterpolation(storm::expressions::ExpressionManager& interpolationManager, AbstractionInformation const& abstractionInformation, std::vector> const& trace, std::map const& variableSubstitution) { 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; auto traceIt = trace.rbegin(); auto traceIte = trace.rend(); for (; traceIt != traceIte; ++traceIt) { auto iterationStart = std::chrono::high_resolution_clock::now(); auto const& step = *traceIt; interpolatingSolver.push(); interpolatingSolver.setInterpolationGroup(stepCounter); for (auto const& predicate : step) { interpolatingSolver.add(predicate); } auto iterationEnd = std::chrono::high_resolution_clock::now(); STORM_LOG_TRACE("Asserting step of trace formula took " << std::chrono::duration_cast(iterationEnd - iterationStart).count() << "ms."); ++stepCounter; } auto assertionEnd = std::chrono::high_resolution_clock::now(); STORM_LOG_TRACE("Asserting trace formula until unsatisfiability 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.isTrue() && !interpolant.isFalse()) { STORM_LOG_DEBUG("Derived new predicate (based on interpolation at step " << step << " out of " << stepCounter << "): " << interpolant); interpolants.push_back(interpolant); } } 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 storm::abstraction::derivePredicatesFromInterpolation(*interpolationManager, abstractionInformation, 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::map> traceAndVariableSubstitution = buildTrace(*interpolationManager, game, pivotStateResult, 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 storm::abstraction::derivePredicatesFromInterpolation(*interpolationManager, abstractionInformation, 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 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 state in which the // strategies for the (commonly chosen) player 1 action leads to a player 2 state in which the choices differ. ExplicitPivotStateResult result; 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(); std::vector distances(numberOfStates, inftyDistance); if (generatePredecessors) { result.predecessors.resize(numberOfStates, std::make_pair(ExplicitPivotStateResult::NO_PREDECESSOR, ExplicitPivotStateResult::NO_PREDECESSOR)); } // Use set as priority queue with unique membership; default comparison on pair works fine if distance is // the first entry. std::set, std::greater>> dijkstraQueue; for (auto initialState : initialStates) { if (!relevantStates.get(initialState)) { continue; } distances[initialState] = zeroDistance; dijkstraQueue.emplace(zeroDistance, initialState); } // 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; ValueType pivotStateDeviation = storm::utility::zero(); auto const& player2Grouping = transitionMatrix.getRowGroupIndices(); while (!dijkstraQueue.empty()) { auto distanceStatePair = *dijkstraQueue.begin(); uint64_t currentState = distanceStatePair.second; ValueType currentDistance = distanceStatePair.first; dijkstraQueue.erase(dijkstraQueue.begin()); if (foundPivotState && (probabilityDistances ? currentDistance < result.distance : currentDistance > result.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 result; } 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 result; } } // 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; } } else if (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 abort the search here. if (isPivotState) { if (considerDeviation && foundPivotState) { ValueType deviationOfCurrentState = (*upperValues)[currentState] - (*lowerValues)[currentState]; if (deviationOfCurrentState > pivotStateDeviation) { result.pivotState = currentState; 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) { result.pivotState = currentState; result.distance = distances[currentState]; foundPivotState = true; } // If there is no need to look at other deviations, stop here. if (!considerDeviation) { return result; } } // Otherwise, we explore all its relevant successors. if (minStrategyPair.getPlayer1Strategy().hasDefinedChoice(currentState)) { uint64_t minPlayer2Successor = minStrategyPair.getPlayer1Strategy().getChoice(currentState); uint64_t minPlayer2Choice = minStrategyPair.getPlayer2Strategy().getChoice(minPlayer2Successor); STORM_LOG_ASSERT(player2Grouping[minPlayer2Successor] <= minPlayer2Choice && minPlayer2Choice < player2Grouping[minPlayer2Successor + 1], "Illegal choice for player 2."); for (auto const& entry : transitionMatrix.getRow(minPlayer2Choice)) { uint64_t player1Successor = entry.getColumn(); if (!relevantStates.get(player1Successor)) { continue; } ValueType alternateDistance = probabilityDistances ? currentDistance * entry.getValue() : currentDistance + storm::utility::one(); if (probabilityDistances ? alternateDistance > distances[player1Successor] : alternateDistance < distances[player1Successor]) { distances[player1Successor] = alternateDistance; if (generatePredecessors) { result.predecessors[player1Successor] = std::make_pair(currentState, player1Labeling[minPlayer2Successor]); } dijkstraQueue.emplace(alternateDistance, player1Successor); } } } if (maxStrategyPair.getPlayer1Strategy().hasDefinedChoice(currentState)) { uint64_t maxPlayer2Successor = maxStrategyPair.getPlayer1Strategy().getChoice(currentState); uint64_t maxPlayer2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(maxPlayer2Successor); STORM_LOG_ASSERT(player2Grouping[maxPlayer2Successor] <= maxPlayer2Choice && maxPlayer2Choice < player2Grouping[maxPlayer2Successor + 1], "Illegal choice for player 2."); for (auto const& entry : transitionMatrix.getRow(maxPlayer2Choice)) { uint64_t player1Successor = entry.getColumn(); if (!relevantStates.get(player1Successor)) { continue; } ValueType alternateDistance = probabilityDistances ? currentDistance * entry.getValue() : currentDistance + storm::utility::one(); if (probabilityDistances ? alternateDistance > distances[player1Successor] : !probabilityDistances && alternateDistance < distances[player1Successor]) { distances[player1Successor] = alternateDistance; if (generatePredecessors) { result.predecessors[player1Successor] = std::make_pair(currentState, player1Labeling[maxPlayer2Successor]); } dijkstraQueue.emplace(alternateDistance, player1Successor); } } } } if (foundPivotState) { return result; } // 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. return boost::none; } 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 { // 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 { // 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. 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; } if (addPredicate && 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) { for (auto const& oldPredicate : oldPredicateClassIt->second) { bool addAtom = true; if (newAtom.areSame(oldPredicate)) { addAtom = false; break; } 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."); 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) const { for (auto const& command : refinementCommands) { STORM_LOG_TRACE("Refining with " << command.getPredicates().size() << " predicates."); for (auto const& predicate : command.getPredicates()) { STORM_LOG_TRACE(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; #ifdef STORM_HAVE_CARL // Currently, this instantiation does not work. // template class MenuGameRefiner; #endif } }