#include "storm/abstraction/jani/EdgeAbstractor.h" #include #include #include "storm/abstraction/BottomStateResult.h" #include "storm/abstraction/AbstractionInformation.h" #include "storm/storage/dd/DdManager.h" #include "storm/storage/dd/Add.h" #include "storm/storage/jani/Edge.h" #include "storm/storage/jani/EdgeDestination.h" #include "storm/utility/solver.h" #include "storm/utility/macros.h" #include "storm-config.h" #include "storm/adapters/RationalFunctionAdapter.h" namespace storm { namespace abstraction { namespace jani { template EdgeAbstractor::EdgeAbstractor(uint64_t edgeId, storm::jani::Edge const& edge, AbstractionInformation& abstractionInformation, std::shared_ptr const& smtSolverFactory, bool useDecomposition, bool debug) : smtSolver(smtSolverFactory->create(abstractionInformation.getExpressionManager())), abstractionInformation(abstractionInformation), edgeId(edgeId), edge(edge), localExpressionInformation(abstractionInformation), evaluator(abstractionInformation.getExpressionManager()), relevantPredicatesAndVariables(), cachedDd(abstractionInformation.getDdManager().getBddZero(), 0), decisionVariables(), useDecomposition(useDecomposition), skipBottomStates(false), forceRecomputation(true), abstractGuard(abstractionInformation.getDdManager().getBddZero()), bottomStateAbstractor(abstractionInformation, {!edge.getGuard()}, smtSolverFactory), debug(debug) { // Make the second component of relevant predicates have the right size. relevantPredicatesAndVariables.second.resize(edge.getNumberOfDestinations()); // Assert all constraints to enforce legal variable values. for (auto const& constraint : abstractionInformation.getConstraints()) { smtSolver->add(constraint); bottomStateAbstractor.constrain(constraint); } // Assert the guard of the command. smtSolver->add(edge.getGuard()); // Construct assigned variables. for (auto const& destination : edge.getDestinations()) { for (auto const& assignment : destination.getOrderedAssignments()) { assignedVariables.insert(assignment.getExpressionVariable()); } } } template void EdgeAbstractor::refine(std::vector const& predicates) { // Add all predicates to the variable partition. for (auto predicateIndex : predicates) { localExpressionInformation.addExpression(predicateIndex); } // Next, we check whether there is work to be done by recomputing the relevant predicates and checking // whether they changed. std::pair, std::vector>> newRelevantPredicates = this->computeRelevantPredicates(); // Check whether we need to recompute the abstraction. bool relevantPredicatesChanged = this->relevantPredicatesChanged(newRelevantPredicates); if (relevantPredicatesChanged) { addMissingPredicates(newRelevantPredicates); } forceRecomputation |= relevantPredicatesChanged; // Refine bottom state abstractor. Note that this does not trigger a recomputation yet. bottomStateAbstractor.refine(predicates); } template storm::expressions::Expression const& EdgeAbstractor::getGuard() const { return edge.get().getGuard(); } template uint64_t EdgeAbstractor::getNumberOfUpdates(uint64_t player1Choice) const { return edge.get().getNumberOfDestinations(); } template std::map EdgeAbstractor::getVariableUpdates(uint64_t auxiliaryChoice) const { return edge.get().getDestination(auxiliaryChoice).getAsVariableToExpressionMap(); } template std::set const& EdgeAbstractor::getAssignedVariables() const { return assignedVariables; } template void EdgeAbstractor::recomputeCachedBdd() { if (useDecomposition) { recomputeCachedBddWithDecomposition(); } else { recomputeCachedBddWithoutDecomposition(); } } template void EdgeAbstractor::recomputeCachedBddWithDecomposition() { STORM_LOG_TRACE("Recomputing BDD for edge with id " << edgeId << " and guard " << edge.get().getGuard() << " using the decomposition."); auto start = std::chrono::high_resolution_clock::now(); // compute a decomposition of the command // * start with all relevant blocks: blocks of assignment variables and variables in the rhs of assignments // * go through all assignments of all updates and merge relevant blocks that are related via an assignment // * repeat this until nothing changes anymore // * the resulting blocks are the decomposition // Start by constructing the relevant blocks. std::set allRelevantBlocks; std::map variableToBlockIndex; for (auto const& destination : edge.get().getDestinations()) { for (auto const& assignment : destination.getOrderedAssignments().getAllAssignments()) { allRelevantBlocks.insert(localExpressionInformation.getBlockIndexOfVariable(assignment.getExpressionVariable())); auto rhsVariableBlocks = localExpressionInformation.getBlockIndicesOfVariables(assignment.getAssignedExpression().getVariables()); allRelevantBlocks.insert(rhsVariableBlocks.begin(), rhsVariableBlocks.end()); } } STORM_LOG_TRACE("Found " << allRelevantBlocks.size() << " relevant block(s)."); // Create a block partition. std::vector> relevantBlockPartition; std::map variableToLocalBlockIndex; uint64_t index = 0; for (auto const& blockIndex : allRelevantBlocks) { relevantBlockPartition.emplace_back(std::set({blockIndex})); for (auto const& variable : localExpressionInformation.getVariableBlockWithIndex(blockIndex)) { variableToLocalBlockIndex[variable] = index; } ++index; } // Merge all blocks that are related via the right-hand side of assignments. for (auto const& destination : edge.get().getDestinations()) { for (auto const& assignment : destination.getOrderedAssignments().getAllAssignments()) { std::set rhsVariables = assignment.getAssignedExpression().getVariables(); if (!rhsVariables.empty()) { uint64_t blockToKeep = variableToLocalBlockIndex.at(*rhsVariables.begin()); for (auto const& variable : rhsVariables) { uint64_t block = variableToLocalBlockIndex.at(variable); if (block != blockToKeep) { for (auto const& blockIndex : relevantBlockPartition[block]) { for (auto const& variable : localExpressionInformation.getVariableBlockWithIndex(blockIndex)) { variableToLocalBlockIndex[variable] = blockToKeep; } } relevantBlockPartition[blockToKeep].insert(relevantBlockPartition[block].begin(), relevantBlockPartition[block].end()); relevantBlockPartition[block].clear(); } } } } } // Proceed by relating the blocks via assignment-variables and the expressions of their assigned expressions. bool changed = false; do { changed = false; for (auto const& destination : edge.get().getDestinations()) { for (auto const& assignment : destination.getOrderedAssignments().getAllAssignments()) { std::set rhsVariables = assignment.getAssignedExpression().getVariables(); if (!rhsVariables.empty()) { storm::expressions::Variable const& representativeVariable = *rhsVariables.begin(); uint64_t representativeBlock = variableToLocalBlockIndex.at(representativeVariable); uint64_t assignmentVariableBlock = variableToLocalBlockIndex.at(assignment.getExpressionVariable()); // If the blocks are different, we merge them now if (assignmentVariableBlock != representativeBlock) { changed = true; for (auto const& blockIndex : relevantBlockPartition[assignmentVariableBlock]) { for (auto const& variable : localExpressionInformation.getVariableBlockWithIndex(blockIndex)) { variableToLocalBlockIndex[variable] = representativeBlock; } } relevantBlockPartition[representativeBlock].insert(relevantBlockPartition[assignmentVariableBlock].begin(), relevantBlockPartition[assignmentVariableBlock].end()); relevantBlockPartition[assignmentVariableBlock].clear(); } } } } } while (changed); // Now remove all blocks that are empty and obtain the partition. std::vector> cleanedRelevantBlockPartition; for (auto& outerBlock : relevantBlockPartition) { if (!outerBlock.empty()) { cleanedRelevantBlockPartition.emplace_back(); for (auto const& innerBlock : outerBlock) { if (!localExpressionInformation.getExpressionBlock(innerBlock).empty()) { cleanedRelevantBlockPartition.back().insert(innerBlock); } } if (cleanedRelevantBlockPartition.back().empty()) { cleanedRelevantBlockPartition.pop_back(); } } } relevantBlockPartition = std::move(cleanedRelevantBlockPartition); STORM_LOG_TRACE("Decomposition into " << relevantBlockPartition.size() << " blocks."); if (this->debug) { uint64_t blockIndex = 0; for (auto const& block : relevantBlockPartition) { STORM_LOG_TRACE("Predicates of block " << blockIndex << ":"); std::set blockPredicateIndices; for (auto const& innerBlock : block) { blockPredicateIndices.insert(localExpressionInformation.getExpressionBlock(innerBlock).begin(), localExpressionInformation.getExpressionBlock(innerBlock).end()); } for (auto const& predicateIndex : blockPredicateIndices) { STORM_LOG_TRACE(abstractionInformation.get().getPredicateByIndex(predicateIndex)); } ++blockIndex; } } std::set variablesContainedInGuard = edge.get().getGuard().getVariables(); // Check whether we need to enumerate the guard. This is the case if the blocks related by the guard // are not contained within a single block of our decomposition. bool enumerateAbstractGuard = true; std::set guardBlocks = localExpressionInformation.getBlockIndicesOfVariables(variablesContainedInGuard); for (auto const& block : relevantBlockPartition) { bool allContained = true; for (auto const& guardBlock : guardBlocks) { if (block.find(guardBlock) == block.end()) { allContained = false; break; } } if (allContained) { enumerateAbstractGuard = false; } } uint64_t numberOfSolutions = 0; uint64_t numberOfTotalSolutions = 0; // If we need to enumerate the guard, do it only once now. if (enumerateAbstractGuard) { std::set relatedGuardPredicates = localExpressionInformation.getRelatedExpressions(variablesContainedInGuard); std::vector guardDecisionVariables; std::vector> guardVariablesAndPredicates; for (auto const& element : relevantPredicatesAndVariables.first) { if (relatedGuardPredicates.find(element.second) != relatedGuardPredicates.end()) { guardDecisionVariables.push_back(element.first); guardVariablesAndPredicates.push_back(element); } } abstractGuard = this->getAbstractionInformation().getDdManager().getBddZero(); smtSolver->allSat(guardDecisionVariables, [this,&guardVariablesAndPredicates,&numberOfSolutions] (storm::solver::SmtSolver::ModelReference const& model) { abstractGuard |= getSourceStateBdd(model, guardVariablesAndPredicates); ++numberOfSolutions; return true; }); STORM_LOG_TRACE("Enumerated " << numberOfSolutions << " solutions for abstract guard."); // Now that we have the abstract guard, we can add it as an assertion to the solver before enumerating // the other solutions. // Create a new backtracking point before adding the guard. smtSolver->push(); // Create the guard constraint. std::pair, std::unordered_map> result = abstractGuard.toExpression(this->getAbstractionInformation().getExpressionManager()); // Then add it to the solver. for (auto const& expression : result.first) { smtSolver->add(expression); } // Finally associate the level variables with the predicates. for (auto const& indexVariablePair : result.second) { smtSolver->add(storm::expressions::iff(indexVariablePair.second, this->getAbstractionInformation().getPredicateForDdVariableIndex(indexVariablePair.first))); } } // Then enumerate the solutions for each of the blocks of the decomposition uint64_t usedNondeterminismVariables = 0; uint64_t blockCounter = 0; std::vector> blockBdds; for (auto const& block : relevantBlockPartition) { std::set relevantPredicates; for (auto const& innerBlock : block) { relevantPredicates.insert(localExpressionInformation.getExpressionBlock(innerBlock).begin(), localExpressionInformation.getExpressionBlock(innerBlock).end()); } if (relevantPredicates.empty()) { STORM_LOG_TRACE("Block does not contain relevant predicates, skipping it."); continue; } std::vector transitionDecisionVariables; std::vector> sourceVariablesAndPredicates; for (auto const& element : relevantPredicatesAndVariables.first) { if (relevantPredicates.find(element.second) != relevantPredicates.end()) { transitionDecisionVariables.push_back(element.first); sourceVariablesAndPredicates.push_back(element); } } std::vector>> destinationVariablesAndPredicates; for (uint64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) { destinationVariablesAndPredicates.emplace_back(); for (auto const& assignment : edge.get().getDestination(destinationIndex).getOrderedAssignments().getAllAssignments()) { uint64_t assignmentVariableBlockIndex = localExpressionInformation.getBlockIndexOfVariable(assignment.getVariable().getExpressionVariable()); if (block.find(assignmentVariableBlockIndex) != block.end()) { std::set const& assignmentVariableBlock = localExpressionInformation.getExpressionBlock(assignmentVariableBlockIndex); for (auto const& element : relevantPredicatesAndVariables.second[destinationIndex]) { if (assignmentVariableBlock.find(element.second) != assignmentVariableBlock.end()) { destinationVariablesAndPredicates.back().push_back(element); transitionDecisionVariables.push_back(element.first); } } } } } std::unordered_map, std::vector>> sourceToDistributionsMap; numberOfSolutions = 0; smtSolver->allSat(transitionDecisionVariables, [&sourceToDistributionsMap,this,&numberOfSolutions,&sourceVariablesAndPredicates,&destinationVariablesAndPredicates] (storm::solver::SmtSolver::ModelReference const& model) { sourceToDistributionsMap[getSourceStateBdd(model, sourceVariablesAndPredicates)].push_back(getDistributionBdd(model, destinationVariablesAndPredicates)); ++numberOfSolutions; return true; }); STORM_LOG_TRACE("Enumerated " << numberOfSolutions << " solutions for block " << blockCounter << "."); numberOfTotalSolutions += numberOfSolutions; // Now we search for the maximal number of choices of player 2 to determine how many DD variables we // need to encode the nondeterminism. uint_fast64_t maximalNumberOfChoices = 0; for (auto const& sourceDistributionsPair : sourceToDistributionsMap) { maximalNumberOfChoices = std::max(maximalNumberOfChoices, static_cast(sourceDistributionsPair.second.size())); } // We now compute how many variables we need to encode the choices. We add one to the maximal number of // choices to account for a possible transition to a bottom state. uint_fast64_t numberOfVariablesNeeded = (maximalNumberOfChoices > 1) ? (static_cast(std::ceil(std::log2(maximalNumberOfChoices + (blockCounter == 0 ? 1 : 0))))) : (blockCounter == 0 ? 1 : 0); // Finally, build overall result. storm::dd::Bdd resultBdd = this->getAbstractionInformation().getDdManager().getBddZero(); for (auto const& sourceDistributionsPair : sourceToDistributionsMap) { STORM_LOG_ASSERT(!sourceDistributionsPair.first.isZero(), "The source BDD must not be empty."); STORM_LOG_ASSERT(!sourceDistributionsPair.second.empty(), "The distributions must not be empty."); // We start with the distribution index of 1, because 0 is reserved for a potential bottom choice. uint_fast64_t distributionIndex = blockCounter == 0 ? 1 : 0; storm::dd::Bdd allDistributions = this->getAbstractionInformation().getDdManager().getBddZero(); for (auto const& distribution : sourceDistributionsPair.second) { allDistributions |= distribution && this->getAbstractionInformation().encodePlayer2Choice(distributionIndex, usedNondeterminismVariables, usedNondeterminismVariables + numberOfVariablesNeeded); ++distributionIndex; STORM_LOG_ASSERT(!allDistributions.isZero(), "The BDD must not be empty."); } resultBdd |= sourceDistributionsPair.first && allDistributions; STORM_LOG_ASSERT(!resultBdd.isZero(), "The BDD must not be empty."); } usedNondeterminismVariables += numberOfVariablesNeeded; blockBdds.push_back(resultBdd); ++blockCounter; } if (enumerateAbstractGuard) { smtSolver->pop(); } // multiply the results storm::dd::Bdd resultBdd = getAbstractionInformation().getDdManager().getBddOne(); uint64_t blockIndex = 0; for (auto const& blockBdd : blockBdds) { resultBdd &= blockBdd; ++blockIndex; } // If we did not explicitly enumerate the guard, we can construct it from the result BDD. if (!enumerateAbstractGuard) { std::set allVariables(getAbstractionInformation().getSuccessorVariables()); auto player2Variables = getAbstractionInformation().getPlayer2VariableSet(usedNondeterminismVariables); allVariables.insert(player2Variables.begin(), player2Variables.end()); auto auxVariables = getAbstractionInformation().getAuxVariableSet(0, getAbstractionInformation().getAuxVariableCount()); allVariables.insert(auxVariables.begin(), auxVariables.end()); std::set variablesToAbstract; std::set_intersection(allVariables.begin(), allVariables.end(), resultBdd.getContainedMetaVariables().begin(), resultBdd.getContainedMetaVariables().end(), std::inserter(variablesToAbstract, variablesToAbstract.begin())); abstractGuard = resultBdd.existsAbstract(variablesToAbstract); } else { // Multiply the abstract guard as it can contain predicates that are not mentioned in the blocks. resultBdd &= abstractGuard; } // multiply with missing identities resultBdd &= computeMissingDestinationIdentities(); // cache and return result resultBdd &= this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount()); // Cache the result. cachedDd = GameBddResult(resultBdd, usedNondeterminismVariables); auto end = std::chrono::high_resolution_clock::now(); STORM_LOG_TRACE("Enumerated " << numberOfTotalSolutions << " solutions in " << std::chrono::duration_cast(end - start).count() << "ms."); forceRecomputation = false; } template void EdgeAbstractor::recomputeCachedBddWithoutDecomposition() { STORM_LOG_TRACE("Recomputing BDD for edge with id " << edgeId << " and guard " << edge.get().getGuard()); auto start = std::chrono::high_resolution_clock::now(); // Create a mapping from source state DDs to their distributions. std::unordered_map, std::vector>> sourceToDistributionsMap; uint64_t numberOfSolutions = 0; smtSolver->allSat(decisionVariables, [&sourceToDistributionsMap,this,&numberOfSolutions] (storm::solver::SmtSolver::ModelReference const& model) { sourceToDistributionsMap[getSourceStateBdd(model, relevantPredicatesAndVariables.first)].push_back(getDistributionBdd(model, relevantPredicatesAndVariables.second)); ++numberOfSolutions; return true; }); // Now we search for the maximal number of choices of player 2 to determine how many DD variables we // need to encode the nondeterminism. uint_fast64_t maximalNumberOfChoices = 0; for (auto const& sourceDistributionsPair : sourceToDistributionsMap) { maximalNumberOfChoices = std::max(maximalNumberOfChoices, static_cast(sourceDistributionsPair.second.size())); } // We now compute how many variables we need to encode the choices. We add one to the maximal number of // choices to account for a possible transition to a bottom state. uint_fast64_t numberOfVariablesNeeded = static_cast(std::ceil(std::log2(maximalNumberOfChoices + 1))); // Finally, build overall result. storm::dd::Bdd resultBdd = this->getAbstractionInformation().getDdManager().getBddZero(); if (!skipBottomStates) { abstractGuard = this->getAbstractionInformation().getDdManager().getBddZero(); } for (auto const& sourceDistributionsPair : sourceToDistributionsMap) { if (!skipBottomStates) { abstractGuard |= sourceDistributionsPair.first; } STORM_LOG_ASSERT(!sourceDistributionsPair.first.isZero(), "The source BDD must not be empty."); STORM_LOG_ASSERT(!sourceDistributionsPair.second.empty(), "The distributions must not be empty."); // We start with the distribution index of 1, becase 0 is reserved for a potential bottom choice. uint_fast64_t distributionIndex = 1; storm::dd::Bdd allDistributions = this->getAbstractionInformation().getDdManager().getBddZero(); for (auto const& distribution : sourceDistributionsPair.second) { allDistributions |= distribution && this->getAbstractionInformation().encodePlayer2Choice(distributionIndex, 0, numberOfVariablesNeeded); ++distributionIndex; STORM_LOG_ASSERT(!allDistributions.isZero(), "The BDD must not be empty."); } resultBdd |= sourceDistributionsPair.first && allDistributions; STORM_LOG_ASSERT(!resultBdd.isZero(), "The BDD must not be empty."); } resultBdd &= computeMissingDestinationIdentities(); resultBdd &= this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount()); STORM_LOG_ASSERT(sourceToDistributionsMap.empty() || !resultBdd.isZero(), "The BDD must not be empty, if there were distributions."); // Cache the result. cachedDd = GameBddResult(resultBdd, numberOfVariablesNeeded); auto end = std::chrono::high_resolution_clock::now(); STORM_LOG_TRACE("Enumerated " << numberOfSolutions << " solutions in " << std::chrono::duration_cast(end - start).count() << "ms."); forceRecomputation = false; } template std::pair, std::set> EdgeAbstractor::computeRelevantPredicates(storm::jani::OrderedAssignments const& assignments) const { std::pair, std::set> result; std::set assignedVariables; for (auto const& assignment : assignments.getAllAssignments()) { // Also, variables appearing on the right-hand side of an assignment are relevant for source state. auto const& rightHandSidePredicates = localExpressionInformation.getExpressionsUsingVariables(assignment.getAssignedExpression().getVariables()); result.first.insert(rightHandSidePredicates.begin(), rightHandSidePredicates.end()); // Variables that are being assigned are relevant for the successor state. storm::expressions::Variable const& assignedVariable = assignment.getExpressionVariable(); auto const& leftHandSidePredicates = localExpressionInformation.getExpressionsUsingVariable(assignedVariable); result.second.insert(leftHandSidePredicates.begin(), leftHandSidePredicates.end()); } return result; } template std::pair, std::vector>> EdgeAbstractor::computeRelevantPredicates() const { std::pair, std::vector>> result; // To start with, all predicates related to the guard are relevant source predicates. result.first = localExpressionInformation.getExpressionsUsingVariables(edge.get().getGuard().getVariables()); // Then, we add the predicates that become relevant, because of some update. for (auto const& destination : edge.get().getDestinations()) { std::pair, std::set> relevantUpdatePredicates = computeRelevantPredicates(destination.getOrderedAssignments()); result.first.insert(relevantUpdatePredicates.first.begin(), relevantUpdatePredicates.first.end()); result.second.push_back(relevantUpdatePredicates.second); } return result; } template bool EdgeAbstractor::relevantPredicatesChanged(std::pair, std::vector>> const& newRelevantPredicates) const { if (newRelevantPredicates.first.size() > relevantPredicatesAndVariables.first.size()) { return true; } for (uint_fast64_t index = 0; index < edge.get().getNumberOfDestinations(); ++index) { if (newRelevantPredicates.second[index].size() > relevantPredicatesAndVariables.second[index].size()) { return true; } } return false; } template void EdgeAbstractor::addMissingPredicates(std::pair, std::vector>> const& newRelevantPredicates) { // Determine and add new relevant source predicates. std::vector> newSourceVariables = this->getAbstractionInformation().declareNewVariables(relevantPredicatesAndVariables.first, newRelevantPredicates.first); for (auto const& element : newSourceVariables) { allRelevantPredicates.insert(element.second); smtSolver->add(storm::expressions::iff(element.first, this->getAbstractionInformation().getPredicateByIndex(element.second))); decisionVariables.push_back(element.first); } // Insert the new variables into the record of relevant source variables. relevantPredicatesAndVariables.first.insert(relevantPredicatesAndVariables.first.end(), newSourceVariables.begin(), newSourceVariables.end()); std::sort(relevantPredicatesAndVariables.first.begin(), relevantPredicatesAndVariables.first.end(), [] (std::pair const& first, std::pair const& second) { return first.second < second.second; } ); // Do the same for every update. for (uint_fast64_t index = 0; index < edge.get().getNumberOfDestinations(); ++index) { std::vector> newSuccessorVariables = this->getAbstractionInformation().declareNewVariables(relevantPredicatesAndVariables.second[index], newRelevantPredicates.second[index]); for (auto const& element : newSuccessorVariables) { allRelevantPredicates.insert(element.second); smtSolver->add(storm::expressions::iff(element.first, this->getAbstractionInformation().getPredicateByIndex(element.second).substitute(edge.get().getDestination(index).getAsVariableToExpressionMap()))); decisionVariables.push_back(element.first); } relevantPredicatesAndVariables.second[index].insert(relevantPredicatesAndVariables.second[index].end(), newSuccessorVariables.begin(), newSuccessorVariables.end()); std::sort(relevantPredicatesAndVariables.second[index].begin(), relevantPredicatesAndVariables.second[index].end(), [] (std::pair const& first, std::pair const& second) { return first.second < second.second; } ); } } template storm::dd::Bdd EdgeAbstractor::getSourceStateBdd(storm::solver::SmtSolver::ModelReference const& model, std::vector> const& variablePredicates) const { storm::dd::Bdd result = this->getAbstractionInformation().getDdManager().getBddOne(); for (auto variableIndexPairIt = variablePredicates.rbegin(), variableIndexPairIte = variablePredicates.rend(); variableIndexPairIt != variableIndexPairIte; ++variableIndexPairIt) { auto const& variableIndexPair = *variableIndexPairIt; if (model.getBooleanValue(variableIndexPair.first)) { result &= this->getAbstractionInformation().encodePredicateAsSource(variableIndexPair.second); } else { result &= !this->getAbstractionInformation().encodePredicateAsSource(variableIndexPair.second); } } STORM_LOG_ASSERT(!result.isZero(), "Source must not be empty."); return result; } template storm::dd::Bdd EdgeAbstractor::getDistributionBdd(storm::solver::SmtSolver::ModelReference const& model, std::vector>> const& variablePredicates) const { storm::dd::Bdd result = this->getAbstractionInformation().getDdManager().getBddZero(); for (uint_fast64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) { storm::dd::Bdd updateBdd = this->getAbstractionInformation().getDdManager().getBddOne(); // Translate block variables for this update into a successor block. for (auto variableIndexPairIt = variablePredicates[destinationIndex].rbegin(), variableIndexPairIte = variablePredicates[destinationIndex].rend(); variableIndexPairIt != variableIndexPairIte; ++variableIndexPairIt) { auto const& variableIndexPair = *variableIndexPairIt; if (model.getBooleanValue(variableIndexPair.first)) { updateBdd &= this->getAbstractionInformation().encodePredicateAsSuccessor(variableIndexPair.second); } else { updateBdd &= !this->getAbstractionInformation().encodePredicateAsSuccessor(variableIndexPair.second); } } updateBdd &= this->getAbstractionInformation().encodeAux(destinationIndex, 0, this->getAbstractionInformation().getAuxVariableCount()); result |= updateBdd; } STORM_LOG_ASSERT(!result.isZero(), "Distribution must not be empty."); return result; } template storm::dd::Bdd EdgeAbstractor::computeMissingDestinationIdentities() const { storm::dd::Bdd result = this->getAbstractionInformation().getDdManager().getBddZero(); for (uint_fast64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) { // Compute the identities that are missing for this update. auto updateRelevantIt = relevantPredicatesAndVariables.second[destinationIndex].rbegin(); auto updateRelevantIte = relevantPredicatesAndVariables.second[destinationIndex].rend(); storm::dd::Bdd updateIdentity = this->getAbstractionInformation().getDdManager().getBddOne(); for (uint_fast64_t predicateIndex = this->getAbstractionInformation().getNumberOfPredicates() - 1;; --predicateIndex) { if (updateRelevantIt == updateRelevantIte || updateRelevantIt->second != predicateIndex) { updateIdentity &= this->getAbstractionInformation().getPredicateIdentity(predicateIndex); } else { ++updateRelevantIt; } if (predicateIndex == 0) { break; } } result |= updateIdentity && this->getAbstractionInformation().encodeAux(destinationIndex, 0, this->getAbstractionInformation().getAuxVariableCount()); } return result; } template GameBddResult EdgeAbstractor::abstract() { if (forceRecomputation) { this->recomputeCachedBdd(); } else { cachedDd.bdd &= computeMissingDestinationIdentities(); } STORM_LOG_TRACE("Edge produces " << cachedDd.bdd.getNonZeroCount() << " transitions."); return cachedDd; } template BottomStateResult EdgeAbstractor::getBottomStateTransitions(storm::dd::Bdd const& reachableStates, uint_fast64_t numberOfPlayer2Variables, boost::optional> const& locationVariables) { STORM_LOG_TRACE("Computing bottom state transitions of edge with index " << edgeId << "."); BottomStateResult result(this->getAbstractionInformation().getDdManager().getBddZero(), this->getAbstractionInformation().getDdManager().getBddZero()); // If the guard of this command is a predicate, there are not bottom states/transitions. if (skipBottomStates) { STORM_LOG_TRACE("Skipping bottom state computation for this edge."); return result; } storm::dd::Bdd reachableStatesWithEdge = reachableStates && abstractGuard; // needed? // if (locationVariables) { // reachableStatesWithEdge = (reachableStates && abstractGuard && this->getAbstractionInformation().encodeLocation(locationVariables.get().first, edge.get().getSourceLocationIndex())).existsAbstract(this->getAbstractionInformation().getSourceLocationVariables()); // } else { // reachableStatesWithEdge = (reachableStates && abstractGuard).existsAbstract(this->getAbstractionInformation().getSourceLocationVariables()); // } // Use the state abstractor to compute the set of abstract states that has this command enabled but // still has a transition to a bottom state. bottomStateAbstractor.constrain(reachableStatesWithEdge); if (locationVariables) { result.states = bottomStateAbstractor.getAbstractStates() && reachableStatesWithEdge && this->getAbstractionInformation().encodeLocation(locationVariables.get().first, edge.get().getSourceLocationIndex()); } else { result.states = bottomStateAbstractor.getAbstractStates() && reachableStatesWithEdge; } // If the result is empty one time, we can skip the bottom state computation from now on. if (result.states.isZero()) { skipBottomStates = true; } // Now equip all these states with an actual transition to a bottom state. result.transitions = result.states && this->getAbstractionInformation().getAllPredicateIdentities() && this->getAbstractionInformation().getBottomStateBdd(false, false); // Mark the states as bottom states. result.states &= this->getAbstractionInformation().getBottomStateBdd(true, false); // Add the command encoding and the next free player 2 encoding. result.transitions &= this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount()) && this->getAbstractionInformation().encodePlayer2Choice(0, 0, numberOfPlayer2Variables) && this->getAbstractionInformation().encodeAux(0, 0, this->getAbstractionInformation().getAuxVariableCount()); return result; } template storm::dd::Add EdgeAbstractor::getEdgeDecoratorAdd(boost::optional> const& locationVariables) const { storm::dd::Add result = this->getAbstractionInformation().getDdManager().template getAddZero(); for (uint_fast64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) { storm::dd::Add tmp = this->getAbstractionInformation().encodeAux(destinationIndex, 0, this->getAbstractionInformation().getAuxVariableCount()).template toAdd() * this->getAbstractionInformation().getDdManager().getConstant(evaluator.asRational(edge.get().getDestination(destinationIndex).getProbability())); if (locationVariables) { tmp *= this->getAbstractionInformation().encodeLocation(locationVariables.get().second, edge.get().getDestination(destinationIndex).getLocationIndex()).template toAdd(); } result += tmp; } storm::dd::Add tmp = this->getAbstractionInformation().getDdManager().template getAddOne(); if (locationVariables) { tmp *= this->getAbstractionInformation().encodeLocation(locationVariables.get().first, edge.get().getSourceLocationIndex()).template toAdd(); } tmp *= this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount()).template toAdd(); result *= tmp; return result; } template storm::jani::Edge const& EdgeAbstractor::getConcreteEdge() const { return edge.get(); } template AbstractionInformation const& EdgeAbstractor::getAbstractionInformation() const { return abstractionInformation.get(); } template AbstractionInformation& EdgeAbstractor::getAbstractionInformation() { return abstractionInformation.get(); } template void EdgeAbstractor::notifyGuardIsPredicate() { skipBottomStates = true; } template class EdgeAbstractor; template class EdgeAbstractor; #ifdef STORM_HAVE_CARL template class EdgeAbstractor; #endif } } }