#include "storm/modelchecker/prctl/helper/SparseMdpPrctlHelper.h" #include #include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "storm/modelchecker/hints/ExplicitModelCheckerHint.h" #include "storm/modelchecker/prctl/helper/DsMpiUpperRewardBoundsComputer.h" #include "storm/modelchecker/prctl/helper/BaierUpperRewardBoundsComputer.h" #include "storm/modelchecker/prctl/helper/SparseMdpEndComponentInformation.h" #include "storm/models/sparse/StandardRewardModel.h" #include "storm/storage/MaximalEndComponentDecomposition.h" #include "storm/utility/macros.h" #include "storm/utility/vector.h" #include "storm/utility/graph.h" #include "storm/storage/expressions/Variable.h" #include "storm/storage/expressions/Expression.h" #include "storm/storage/Scheduler.h" #include "storm/solver/MinMaxLinearEquationSolver.h" #include "storm/solver/Multiplier.h" #include "storm/solver/LpSolver.h" #include "storm/settings/SettingsManager.h" #include "storm/settings/modules/ModelCheckerSettings.h" #include "storm/settings/modules/GeneralSettings.h" #include "storm/settings/modules/CoreSettings.h" #include "storm/settings/modules/IOSettings.h" #include "storm/utility/Stopwatch.h" #include "storm/utility/ProgressMeasurement.h" #include "storm/utility/SignalHandler.h" #include "storm/utility/export.h" #include "storm/utility/NumberTraits.h" #include "storm/transformer/EndComponentEliminator.h" #include "storm/environment/solver/MinMaxSolverEnvironment.h" #include "storm/environment/solver/LongRunAverageSolverEnvironment.h" #include "storm/exceptions/InvalidStateException.h" #include "storm/exceptions/InvalidPropertyException.h" #include "storm/exceptions/InvalidSettingsException.h" #include "storm/exceptions/IllegalFunctionCallException.h" #include "storm/exceptions/IllegalArgumentException.h" #include "storm/exceptions/UncheckedRequirementException.h" #include "storm/exceptions/NotSupportedException.h" namespace storm { namespace modelchecker { namespace helper { template std::vector SparseMdpPrctlHelper::computeStepBoundedUntilProbabilities(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, uint_fast64_t stepBound, ModelCheckerHint const& hint) { std::vector result(transitionMatrix.getRowGroupCount(), storm::utility::zero()); // Determine the states that have 0 probability of reaching the target states. storm::storage::BitVector maybeStates; if (hint.isExplicitModelCheckerHint() && hint.template asExplicitModelCheckerHint().getComputeOnlyMaybeStates()) { maybeStates = hint.template asExplicitModelCheckerHint().getMaybeStates(); } else { if (goal.minimize()) { maybeStates = storm::utility::graph::performProbGreater0A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, phiStates, psiStates, true, stepBound); } else { maybeStates = storm::utility::graph::performProbGreater0E(backwardTransitions, phiStates, psiStates, true, stepBound); } maybeStates &= ~psiStates; } STORM_LOG_INFO("Preprocessing: " << maybeStates.getNumberOfSetBits() << " non-target states with probability greater 0."); if (!maybeStates.empty()) { // We can eliminate the rows and columns from the original transition probability matrix that have probability 0. storm::storage::SparseMatrix submatrix = transitionMatrix.getSubmatrix(true, maybeStates, maybeStates, false); std::vector b = transitionMatrix.getConstrainedRowGroupSumVector(maybeStates, psiStates); // Create the vector with which to multiply. std::vector subresult(maybeStates.getNumberOfSetBits()); auto multiplier = storm::solver::MultiplierFactory().create(env, submatrix); multiplier->repeatedMultiplyAndReduce(env, goal.direction(), subresult, &b, stepBound); // Set the values of the resulting vector accordingly. storm::utility::vector::setVectorValues(result, maybeStates, subresult); } storm::utility::vector::setVectorValues(result, psiStates, storm::utility::one()); return result; } template std::map SparseMdpPrctlHelper::computeRewardBoundedValues(Environment const& env, OptimizationDirection dir, rewardbounded::MultiDimensionalRewardUnfolding& rewardUnfolding, storm::storage::BitVector const& initialStates) { storm::utility::Stopwatch swAll(true), swBuild, swCheck; // Get lower and upper bounds for the solution. auto lowerBound = rewardUnfolding.getLowerObjectiveBound(); auto upperBound = rewardUnfolding.getUpperObjectiveBound(); // Initialize epoch models auto initEpoch = rewardUnfolding.getStartEpoch(); auto epochOrder = rewardUnfolding.getEpochComputationOrder(initEpoch); // initialize data that will be needed for each epoch std::vector x, b; std::unique_ptr> minMaxSolver; ValueType precision = rewardUnfolding.getRequiredEpochModelPrecision(initEpoch, storm::utility::convertNumber(storm::settings::getModule().getPrecision())); Environment preciseEnv = env; preciseEnv.solver().minMax().setPrecision(storm::utility::convertNumber(precision)); // In case of cdf export we store the necessary data. std::vector> cdfData; storm::utility::ProgressMeasurement progress("epochs"); progress.setMaxCount(epochOrder.size()); progress.startNewMeasurement(0); uint64_t numCheckedEpochs = 0; for (auto const& epoch : epochOrder) { swBuild.start(); auto& epochModel = rewardUnfolding.setCurrentEpoch(epoch); swBuild.stop(); swCheck.start(); rewardUnfolding.setSolutionForCurrentEpoch(epochModel.analyzeSingleObjective(preciseEnv, dir, x, b, minMaxSolver, lowerBound, upperBound)); swCheck.stop(); if (storm::settings::getModule().isExportCdfSet() && !rewardUnfolding.getEpochManager().hasBottomDimension(epoch)) { std::vector cdfEntry; for (uint64_t i = 0; i < rewardUnfolding.getEpochManager().getDimensionCount(); ++i) { uint64_t offset = rewardUnfolding.getDimension(i).boundType == helper::rewardbounded::DimensionBoundType::LowerBound ? 1 : 0; cdfEntry.push_back(storm::utility::convertNumber(rewardUnfolding.getEpochManager().getDimensionOfEpoch(epoch, i) + offset) * rewardUnfolding.getDimension(i).scalingFactor); } cdfEntry.push_back(rewardUnfolding.getInitialStateResult(epoch)); cdfData.push_back(std::move(cdfEntry)); } ++numCheckedEpochs; progress.updateProgress(numCheckedEpochs); if (storm::utility::resources::isTerminate()) { break; } } std::map result; for (auto const& initState : initialStates) { result[initState] = rewardUnfolding.getInitialStateResult(initEpoch, initState); } swAll.stop(); if (storm::settings::getModule().isExportCdfSet()) { std::vector headers; for (uint64_t i = 0; i < rewardUnfolding.getEpochManager().getDimensionCount(); ++i) { headers.push_back(rewardUnfolding.getDimension(i).formula->toString()); } headers.push_back("Result"); storm::utility::exportDataToCSVFile(storm::settings::getModule().getExportCdfDirectory() + "cdf.csv", cdfData, headers); } if (storm::settings::getModule().isShowStatisticsSet()) { STORM_PRINT_AND_LOG("---------------------------------" << std::endl); STORM_PRINT_AND_LOG("Statistics:" << std::endl); STORM_PRINT_AND_LOG("---------------------------------" << std::endl); STORM_PRINT_AND_LOG(" #checked epochs: " << epochOrder.size() << "." << std::endl); STORM_PRINT_AND_LOG(" overall Time: " << swAll << "." << std::endl); STORM_PRINT_AND_LOG("Epoch Model building Time: " << swBuild << "." << std::endl); STORM_PRINT_AND_LOG("Epoch Model checking Time: " << swCheck << "." << std::endl); STORM_PRINT_AND_LOG("---------------------------------" << std::endl); } return result; } template std::vector SparseMdpPrctlHelper::computeNextProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::BitVector const& nextStates) { // Create the vector with which to multiply and initialize it correctly. std::vector result(transitionMatrix.getRowGroupCount()); storm::utility::vector::setVectorValues(result, nextStates, storm::utility::one()); auto multiplier = storm::solver::MultiplierFactory().create(env, transitionMatrix); multiplier->multiplyAndReduce(env, dir, result, nullptr, result); return result; } template std::vector computeValidSchedulerHint(Environment const& env, SolutionType const& type, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& maybeStates, storm::storage::BitVector const& filterStates, storm::storage::BitVector const& targetStates) { storm::storage::Scheduler validScheduler(maybeStates.size()); if (type == SolutionType::UntilProbabilities) { storm::utility::graph::computeSchedulerProbGreater0E(transitionMatrix, backwardTransitions, filterStates, targetStates, validScheduler, boost::none); } else if (type == SolutionType::ExpectedRewards) { storm::utility::graph::computeSchedulerProb1E(maybeStates | targetStates, transitionMatrix, backwardTransitions, filterStates, targetStates, validScheduler); } else { STORM_LOG_ASSERT(false, "Unexpected equation system type."); } // Extract the relevant parts of the scheduler for the solver. std::vector schedulerHint(maybeStates.getNumberOfSetBits()); auto maybeIt = maybeStates.begin(); for (auto& choice : schedulerHint) { choice = validScheduler.getChoice(*maybeIt).getDeterministicChoice(); ++maybeIt; } return schedulerHint; } template struct SparseMdpHintType { SparseMdpHintType() : eliminateEndComponents(false), computeUpperBounds(false), uniqueSolution(false), noEndComponents(false) { // Intentionally left empty. } bool hasSchedulerHint() const { return static_cast(schedulerHint); } bool hasValueHint() const { return static_cast(valueHint); } bool hasLowerResultBound() const { return static_cast(lowerResultBound); } ValueType const& getLowerResultBound() const { return lowerResultBound.get(); } bool hasUpperResultBound() const { return static_cast(upperResultBound); } bool hasUpperResultBounds() const { return static_cast(upperResultBounds); } ValueType const& getUpperResultBound() const { return upperResultBound.get(); } std::vector& getUpperResultBounds() { return upperResultBounds.get(); } std::vector const& getUpperResultBounds() const { return upperResultBounds.get(); } std::vector& getSchedulerHint() { return schedulerHint.get(); } std::vector& getValueHint() { return valueHint.get(); } bool getEliminateEndComponents() const { return eliminateEndComponents; } bool getComputeUpperBounds() { return computeUpperBounds; } bool hasUniqueSolution() const { return uniqueSolution; } bool hasNoEndComponents() const { return noEndComponents; } boost::optional> schedulerHint; boost::optional> valueHint; boost::optional lowerResultBound; boost::optional upperResultBound; boost::optional> upperResultBounds; bool eliminateEndComponents; bool computeUpperBounds; bool uniqueSolution; bool noEndComponents; }; template void extractValueAndSchedulerHint(SparseMdpHintType& hintStorage, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& maybeStates, boost::optional const& selectedChoices, ModelCheckerHint const& hint, bool skipECWithinMaybeStatesCheck) { // Deal with scheduler hint. if (hint.isExplicitModelCheckerHint() && hint.template asExplicitModelCheckerHint().hasSchedulerHint()) { if (hintStorage.hasSchedulerHint()) { STORM_LOG_WARN("A scheduler hint was provided, but the solver requires a specific one. The provided scheduler hint will be ignored."); } else { auto const& schedulerHint = hint.template asExplicitModelCheckerHint().getSchedulerHint(); std::vector hintChoices; // The scheduler hint is only applicable if it induces no BSCC consisting of maybe states. bool hintApplicable; if (!skipECWithinMaybeStatesCheck) { hintChoices.reserve(maybeStates.size()); for (uint_fast64_t state = 0; state < maybeStates.size(); ++state) { hintChoices.push_back(schedulerHint.getChoice(state).getDeterministicChoice()); } hintApplicable = storm::utility::graph::performProb1(transitionMatrix.transposeSelectedRowsFromRowGroups(hintChoices), maybeStates, ~maybeStates).full(); } else { hintApplicable = true; } if (hintApplicable) { // Compute the hint w.r.t. the given subsystem. hintChoices.clear(); hintChoices.reserve(maybeStates.getNumberOfSetBits()); for (auto const& state : maybeStates) { uint_fast64_t hintChoice = schedulerHint.getChoice(state).getDeterministicChoice(); if (selectedChoices) { uint_fast64_t firstChoice = transitionMatrix.getRowGroupIndices()[state]; uint_fast64_t lastChoice = firstChoice + hintChoice; hintChoice = 0; for (uint_fast64_t choice = selectedChoices->getNextSetIndex(firstChoice); choice < lastChoice; choice = selectedChoices->getNextSetIndex(choice + 1)) { ++hintChoice; } } hintChoices.push_back(hintChoice); } hintStorage.schedulerHint = std::move(hintChoices); } } } // Deal with solution value hint. Only applicable if there are no End Components consisting of maybe states. if (hint.isExplicitModelCheckerHint() && hint.template asExplicitModelCheckerHint().hasResultHint() && (skipECWithinMaybeStatesCheck || hintStorage.hasSchedulerHint() || storm::utility::graph::performProb1A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, maybeStates, ~maybeStates).full())) { hintStorage.valueHint = storm::utility::vector::filterVector(hint.template asExplicitModelCheckerHint().getResultHint(), maybeStates); } } template SparseMdpHintType computeHints(Environment const& env, SolutionType const& type, ModelCheckerHint const& hint, storm::OptimizationDirection const& dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& maybeStates, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& targetStates, bool produceScheduler, boost::optional const& selectedChoices = boost::none) { SparseMdpHintType result; // There are no end components if we minimize until probabilities or // maximize reachability rewards or if the hint tells us so. result.noEndComponents = (dir == storm::solver::OptimizationDirection::Minimize && type == SolutionType::UntilProbabilities) || (dir == storm::solver::OptimizationDirection::Maximize && type == SolutionType::ExpectedRewards) || (hint.isExplicitModelCheckerHint() && hint.asExplicitModelCheckerHint().getNoEndComponentsInMaybeStates()); // If there are no end components, the solution is unique. (Note that the other direction does not hold, // e.g., end components in which infinite reward is collected. result.uniqueSolution = result.hasNoEndComponents(); // Check for requirements of the solver. bool hasSchedulerHint = hint.isExplicitModelCheckerHint() && hint.template asExplicitModelCheckerHint().hasSchedulerHint(); storm::solver::GeneralMinMaxLinearEquationSolverFactory minMaxLinearEquationSolverFactory; storm::solver::MinMaxLinearEquationSolverRequirements requirements = minMaxLinearEquationSolverFactory.getRequirements(env, result.uniqueSolution, result.noEndComponents, dir, hasSchedulerHint, produceScheduler); if (requirements.hasEnabledRequirement()) { // If the solver still requires no end-components, we have to eliminate them later. if (requirements.uniqueSolution()) { STORM_LOG_ASSERT(!result.hasUniqueSolution(), "The solver requires to eliminate the end components although the solution is already assumed to be unique."); STORM_LOG_DEBUG("Scheduling EC elimination, because the solver requires a unique solution."); result.eliminateEndComponents = true; // If end components have been eliminated we can assume a unique solution. result.uniqueSolution = true; requirements.clearUniqueSolution(); // If we compute until probabilities, we can even assume the absence of end components. // Note that in the case of minimizing expected rewards there might still be end components in which reward is collected. result.noEndComponents = (type == SolutionType::UntilProbabilities); } // If the solver requires an initial scheduler, compute one now. Note that any scheduler is valid if there are no end components. if (requirements.validInitialScheduler() && !result.noEndComponents) { STORM_LOG_DEBUG("Computing valid scheduler, because the solver requires it."); result.schedulerHint = computeValidSchedulerHint(env, type, transitionMatrix, backwardTransitions, maybeStates, phiStates, targetStates); requirements.clearValidInitialScheduler(); } // Finally, we have information on the bounds depending on the problem type. if (type == SolutionType::UntilProbabilities) { requirements.clearBounds(); } else if (type == SolutionType::ExpectedRewards) { requirements.clearLowerBounds(); } if (requirements.upperBounds()) { result.computeUpperBounds = true; requirements.clearUpperBounds(); } STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException, "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked."); } else { STORM_LOG_DEBUG("Solver has no requirements."); } // Only if there is no end component decomposition that we will need to do later, we use value and scheduler // hints from the provided hint. if (!result.eliminateEndComponents) { extractValueAndSchedulerHint(result, transitionMatrix, backwardTransitions, maybeStates, selectedChoices, hint, result.uniqueSolution); } else { STORM_LOG_WARN_COND(hint.isEmpty(), "A non-empty hint was provided, but its information will be disregarded."); } // Only set bounds if we did not obtain them from the hint. if (!result.hasLowerResultBound()) { result.lowerResultBound = storm::utility::zero(); } if (!result.hasUpperResultBound() && type == SolutionType::UntilProbabilities) { result.upperResultBound = storm::utility::one(); } // If we received an upper bound, we can drop the requirement to compute one. if (result.hasUpperResultBound()) { result.computeUpperBounds = false; } return result; } template struct MaybeStateResult { MaybeStateResult(std::vector&& values) : values(std::move(values)) { // Intentionally left empty. } bool hasScheduler() const { return static_cast(scheduler); } std::vector const& getScheduler() const { return scheduler.get(); } std::vector const& getValues() const { return values; } std::vector values; boost::optional> scheduler; }; template MaybeStateResult computeValuesForMaybeStates(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix&& submatrix, std::vector const& b, bool produceScheduler, SparseMdpHintType& hint) { // Initialize the solution vector. std::vector x = hint.hasValueHint() ? std::move(hint.getValueHint()) : std::vector(submatrix.getRowGroupCount(), hint.hasLowerResultBound() ? hint.getLowerResultBound() : storm::utility::zero()); // Set up the solver. storm::solver::GeneralMinMaxLinearEquationSolverFactory minMaxLinearEquationSolverFactory; std::unique_ptr> solver = storm::solver::configureMinMaxLinearEquationSolver(env, std::move(goal), minMaxLinearEquationSolverFactory, std::move(submatrix)); solver->setRequirementsChecked(); solver->setHasUniqueSolution(hint.hasUniqueSolution()); solver->setHasNoEndComponents(hint.hasNoEndComponents()); if (hint.hasLowerResultBound()) { solver->setLowerBound(hint.getLowerResultBound()); } if (hint.hasUpperResultBound()) { solver->setUpperBound(hint.getUpperResultBound()); } if (hint.hasUpperResultBounds()) { solver->setUpperBounds(std::move(hint.getUpperResultBounds())); } if (hint.hasSchedulerHint()) { solver->setInitialScheduler(std::move(hint.getSchedulerHint())); } solver->setTrackScheduler(produceScheduler); // Solve the corresponding system of equations. solver->solveEquations(env, x, b); #ifndef NDEBUG // As a sanity check, make sure our local upper bounds were in fact correct. if (solver->hasUpperBound(storm::solver::AbstractEquationSolver::BoundType::Local)) { auto resultIt = x.begin(); for (auto const& entry : solver->getUpperBounds()) { STORM_LOG_ASSERT(*resultIt <= entry + env.solver().minMax().getPrecision(), "Expecting result value for state " << std::distance(x.begin(), resultIt) << " to be <= " << entry << ", but got " << *resultIt << "."); ++resultIt; } } #endif // Create result. MaybeStateResult result(std::move(x)); // If requested, return the requested scheduler. if (produceScheduler) { result.scheduler = std::move(solver->getSchedulerChoices()); } return result; } struct QualitativeStateSetsUntilProbabilities { storm::storage::BitVector maybeStates; storm::storage::BitVector statesWithProbability0; storm::storage::BitVector statesWithProbability1; }; template QualitativeStateSetsUntilProbabilities getQualitativeStateSetsUntilProbabilitiesFromHint(ModelCheckerHint const& hint) { QualitativeStateSetsUntilProbabilities result; result.maybeStates = hint.template asExplicitModelCheckerHint().getMaybeStates(); // Treat the states with probability zero/one. std::vector const& resultsForNonMaybeStates = hint.template asExplicitModelCheckerHint().getResultHint(); result.statesWithProbability1 = storm::storage::BitVector(result.maybeStates.size()); result.statesWithProbability0 = storm::storage::BitVector(result.maybeStates.size()); storm::storage::BitVector nonMaybeStates = ~result.maybeStates; for (auto const& state : nonMaybeStates) { if (storm::utility::isOne(resultsForNonMaybeStates[state])) { result.statesWithProbability1.set(state, true); } else { STORM_LOG_THROW(storm::utility::isZero(resultsForNonMaybeStates[state]), storm::exceptions::IllegalArgumentException, "Expected that the result hint specifies probabilities in {0,1} for non-maybe states"); result.statesWithProbability0.set(state, true); } } return result; } template QualitativeStateSetsUntilProbabilities computeQualitativeStateSetsUntilProbabilities(storm::solver::SolveGoal const& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) { QualitativeStateSetsUntilProbabilities result; // Get all states that have probability 0 and 1 of satisfying the until-formula. std::pair statesWithProbability01; if (goal.minimize()) { statesWithProbability01 = storm::utility::graph::performProb01Min(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, phiStates, psiStates); } else { statesWithProbability01 = storm::utility::graph::performProb01Max(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, phiStates, psiStates); } result.statesWithProbability0 = std::move(statesWithProbability01.first); result.statesWithProbability1 = std::move(statesWithProbability01.second); result.maybeStates = ~(result.statesWithProbability0 | result.statesWithProbability1); return result; } template QualitativeStateSetsUntilProbabilities getQualitativeStateSetsUntilProbabilities(storm::solver::SolveGoal const& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, ModelCheckerHint const& hint) { if (hint.isExplicitModelCheckerHint() && hint.template asExplicitModelCheckerHint().getComputeOnlyMaybeStates()) { return getQualitativeStateSetsUntilProbabilitiesFromHint(hint); } else { return computeQualitativeStateSetsUntilProbabilities(goal, transitionMatrix, backwardTransitions, phiStates, psiStates); } } template void extractSchedulerChoices(storm::storage::Scheduler& scheduler, std::vector const& subChoices, storm::storage::BitVector const& maybeStates) { auto subChoiceIt = subChoices.begin(); for (auto maybeState : maybeStates) { scheduler.setChoice(*subChoiceIt, maybeState); ++subChoiceIt; } assert(subChoiceIt == subChoices.end()); } template void extendScheduler(storm::storage::Scheduler& scheduler, storm::solver::SolveGoal const& goal, QualitativeStateSetsUntilProbabilities const& qualitativeStateSets, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) { // Finally, if we need to produce a scheduler, we also need to figure out the parts of the scheduler for // the states with probability 1 or 0 (depending on whether we maximize or minimize). // We also need to define some arbitrary choice for the remaining states to obtain a fully defined scheduler. if (goal.minimize()) { storm::utility::graph::computeSchedulerProb0E(qualitativeStateSets.statesWithProbability0, transitionMatrix, scheduler); for (auto const& prob1State : qualitativeStateSets.statesWithProbability1) { scheduler.setChoice(0, prob1State); } } else { storm::utility::graph::computeSchedulerProb1E(qualitativeStateSets.statesWithProbability1, transitionMatrix, backwardTransitions, phiStates, psiStates, scheduler); for (auto const& prob0State : qualitativeStateSets.statesWithProbability0) { scheduler.setChoice(0, prob0State); } } } template void computeFixedPointSystemUntilProbabilities(storm::solver::SolveGoal& goal, storm::storage::SparseMatrix const& transitionMatrix, QualitativeStateSetsUntilProbabilities const& qualitativeStateSets, storm::storage::SparseMatrix& submatrix, std::vector& b) { // First, we can eliminate the rows and columns from the original transition probability matrix for states // whose probabilities are already known. submatrix = transitionMatrix.getSubmatrix(true, qualitativeStateSets.maybeStates, qualitativeStateSets.maybeStates, false); // Prepare the right-hand side of the equation system. For entry i this corresponds to // the accumulated probability of going from state i to some state that has probability 1. b = transitionMatrix.getConstrainedRowGroupSumVector(qualitativeStateSets.maybeStates, qualitativeStateSets.statesWithProbability1); // If the solve goal has relevant values, we need to adjust them. goal.restrictRelevantValues(qualitativeStateSets.maybeStates); } template boost::optional> computeFixedPointSystemUntilProbabilitiesEliminateEndComponents(storm::solver::SolveGoal& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, QualitativeStateSetsUntilProbabilities const& qualitativeStateSets, storm::storage::SparseMatrix& submatrix, std::vector& b) { // Get the set of states that (under some scheduler) can stay in the set of maybestates forever storm::storage::BitVector candidateStates = storm::utility::graph::performProb0E(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, qualitativeStateSets.maybeStates, ~qualitativeStateSets.maybeStates); bool doDecomposition = !candidateStates.empty(); storm::storage::MaximalEndComponentDecomposition endComponentDecomposition; if (doDecomposition) { // Compute the states that are in MECs. endComponentDecomposition = storm::storage::MaximalEndComponentDecomposition(transitionMatrix, backwardTransitions, candidateStates); } // Only do more work if there are actually end-components. if (doDecomposition && !endComponentDecomposition.empty()) { STORM_LOG_DEBUG("Eliminating " << endComponentDecomposition.size() << " EC(s)."); SparseMdpEndComponentInformation result = SparseMdpEndComponentInformation::eliminateEndComponents(endComponentDecomposition, transitionMatrix, qualitativeStateSets.maybeStates, &qualitativeStateSets.statesWithProbability1, nullptr, nullptr, submatrix, &b, nullptr); // If the solve goal has relevant values, we need to adjust them. if (goal.hasRelevantValues()) { storm::storage::BitVector newRelevantValues(submatrix.getRowGroupCount()); for (auto state : goal.relevantValues()) { if (qualitativeStateSets.maybeStates.get(state)) { newRelevantValues.set(result.getRowGroupAfterElimination(state)); } } if (!newRelevantValues.empty()) { goal.setRelevantValues(std::move(newRelevantValues)); } } return result; } else { STORM_LOG_DEBUG("Not eliminating ECs as there are none."); computeFixedPointSystemUntilProbabilities(goal, transitionMatrix, qualitativeStateSets, submatrix, b); return boost::none; } } template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeUntilProbabilities(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, bool qualitative, bool produceScheduler, ModelCheckerHint const& hint) { STORM_LOG_THROW(!qualitative || !produceScheduler, storm::exceptions::InvalidSettingsException, "Cannot produce scheduler when performing qualitative model checking only."); // Prepare resulting vector. std::vector result(transitionMatrix.getRowGroupCount(), storm::utility::zero()); // We need to identify the maybe states (states which have a probability for satisfying the until formula // that is strictly between 0 and 1) and the states that satisfy the formula with probablity 1 and 0, respectively. QualitativeStateSetsUntilProbabilities qualitativeStateSets = getQualitativeStateSetsUntilProbabilities(goal, transitionMatrix, backwardTransitions, phiStates, psiStates, hint); STORM_LOG_INFO("Preprocessing: " << qualitativeStateSets.statesWithProbability1.getNumberOfSetBits() << " states with probability 1, " << qualitativeStateSets.statesWithProbability0.getNumberOfSetBits() << " with probability 0 (" << qualitativeStateSets.maybeStates.getNumberOfSetBits() << " states remaining)."); // Set values of resulting vector that are known exactly. storm::utility::vector::setVectorValues(result, qualitativeStateSets.statesWithProbability1, storm::utility::one()); // If requested, we will produce a scheduler. std::unique_ptr> scheduler; if (produceScheduler) { scheduler = std::make_unique>(transitionMatrix.getRowGroupCount()); } // Check whether we need to compute exact probabilities for some states. if (qualitative) { // Set the values for all maybe-states to 0.5 to indicate that their probability values are neither 0 nor 1. storm::utility::vector::setVectorValues(result, qualitativeStateSets.maybeStates, storm::utility::convertNumber(0.5)); } else { if (!qualitativeStateSets.maybeStates.empty()) { // In this case we have have to compute the remaining probabilities. // Obtain proper hint information either from the provided hint or from requirements of the solver. SparseMdpHintType hintInformation = computeHints(env, SolutionType::UntilProbabilities, hint, goal.direction(), transitionMatrix, backwardTransitions, qualitativeStateSets.maybeStates, phiStates, qualitativeStateSets.statesWithProbability1, produceScheduler); // Declare the components of the equation system we will solve. storm::storage::SparseMatrix submatrix; std::vector b; // If the hint information tells us that we have to eliminate MECs, we do so now. boost::optional> ecInformation; if (hintInformation.getEliminateEndComponents()) { ecInformation = computeFixedPointSystemUntilProbabilitiesEliminateEndComponents(goal, transitionMatrix, backwardTransitions, qualitativeStateSets, submatrix, b); // Make sure we are not supposed to produce a scheduler if we actually eliminate end components. STORM_LOG_THROW(!ecInformation || !ecInformation.get().getEliminatedEndComponents() || !produceScheduler, storm::exceptions::NotSupportedException, "Producing schedulers is not supported if end-components need to be eliminated for the solver."); } else { // Otherwise, we compute the standard equations. computeFixedPointSystemUntilProbabilities(goal, transitionMatrix, qualitativeStateSets, submatrix, b); } // Now compute the results for the maybe states. MaybeStateResult resultForMaybeStates = computeValuesForMaybeStates(env, std::move(goal), std::move(submatrix), b, produceScheduler, hintInformation); // If we eliminated end components, we need to extract the result differently. if (ecInformation && ecInformation.get().getEliminatedEndComponents()) { ecInformation.get().setValues(result, qualitativeStateSets.maybeStates, resultForMaybeStates.getValues()); } else { // Set values of resulting vector according to result. storm::utility::vector::setVectorValues(result, qualitativeStateSets.maybeStates, resultForMaybeStates.getValues()); } if (produceScheduler) { extractSchedulerChoices(*scheduler, resultForMaybeStates.getScheduler(), qualitativeStateSets.maybeStates); } } } // Extend scheduler with choices for the states in the qualitative state sets. if (produceScheduler) { extendScheduler(*scheduler, goal, qualitativeStateSets, transitionMatrix, backwardTransitions, phiStates, psiStates); } // Sanity check for created scheduler. STORM_LOG_ASSERT(!produceScheduler || scheduler, "Expected that a scheduler was obtained."); STORM_LOG_ASSERT((!produceScheduler && !scheduler) || !scheduler->isPartialScheduler(), "Expected a fully defined scheduler"); STORM_LOG_ASSERT((!produceScheduler && !scheduler) || scheduler->isDeterministicScheduler(), "Expected a deterministic scheduler"); STORM_LOG_ASSERT((!produceScheduler && !scheduler) || scheduler->isMemorylessScheduler(), "Expected a memoryless scheduler"); // Return result. return MDPSparseModelCheckingHelperReturnType(std::move(result), std::move(scheduler)); } template std::vector SparseMdpPrctlHelper::computeGloballyProbabilities(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& psiStates, bool qualitative, bool useMecBasedTechnique) { if (useMecBasedTechnique) { storm::storage::MaximalEndComponentDecomposition mecDecomposition(transitionMatrix, backwardTransitions, psiStates); storm::storage::BitVector statesInPsiMecs(transitionMatrix.getRowGroupCount()); for (auto const& mec : mecDecomposition) { for (auto const& stateActionsPair : mec) { statesInPsiMecs.set(stateActionsPair.first, true); } } return std::move(computeUntilProbabilities(env, std::move(goal), transitionMatrix, backwardTransitions, psiStates, statesInPsiMecs, qualitative, false).values); } else { goal.oneMinus(); std::vector result = computeUntilProbabilities(env, std::move(goal), transitionMatrix, backwardTransitions, storm::storage::BitVector(transitionMatrix.getRowGroupCount(), true), ~psiStates, qualitative, false).values; for (auto& element : result) { element = storm::utility::one() - element; } return std::move(result); } } template template std::vector SparseMdpPrctlHelper::computeInstantaneousRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, RewardModelType const& rewardModel, uint_fast64_t stepCount) { // Only compute the result if the model has a state-based reward this->getModel(). STORM_LOG_THROW(rewardModel.hasStateRewards(), storm::exceptions::InvalidPropertyException, "Missing reward model for formula. Skipping formula."); // Initialize result to state rewards of the this->getModel(). std::vector result(rewardModel.getStateRewardVector()); auto multiplier = storm::solver::MultiplierFactory().create(env, transitionMatrix); multiplier->repeatedMultiplyAndReduce(env, goal.direction(), result, nullptr, stepCount); return result; } template template std::vector SparseMdpPrctlHelper::computeCumulativeRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, RewardModelType const& rewardModel, uint_fast64_t stepBound) { // Only compute the result if the model has at least one reward this->getModel(). STORM_LOG_THROW(!rewardModel.empty(), storm::exceptions::InvalidPropertyException, "Missing reward model for formula. Skipping formula."); // Compute the reward vector to add in each step based on the available reward models. std::vector totalRewardVector = rewardModel.getTotalRewardVector(transitionMatrix); // Initialize result to the zero vector. std::vector result(transitionMatrix.getRowGroupCount(), storm::utility::zero()); auto multiplier = storm::solver::MultiplierFactory().create(env, transitionMatrix); multiplier->repeatedMultiplyAndReduce(env, goal.direction(), result, &totalRewardVector, stepBound); return result; } template template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeTotalRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, RewardModelType const& rewardModel, bool qualitative, bool produceScheduler, ModelCheckerHint const& hint) { // Reduce to reachability rewards if (goal.minimize()) { STORM_LOG_ERROR_COND(!produceScheduler, "Can not produce scheduler for this property (functionality not implemented"); // Identify the states from which no reward can be collected under some scheduler storm::storage::BitVector choicesWithoutReward = rewardModel.getChoicesWithZeroReward(transitionMatrix); storm::storage::BitVector statesWithZeroRewardChoice(transitionMatrix.getRowGroupCount(), false); for (uint64_t state = 0; state < transitionMatrix.getRowGroupCount(); ++state) { if (choicesWithoutReward.getNextSetIndex(transitionMatrix.getRowGroupIndices()[state])< transitionMatrix.getRowGroupIndices()[state + 1]) { statesWithZeroRewardChoice.set(state); } } storm::storage::BitVector rew0EStates = storm::utility::graph::performProbGreater0A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, statesWithZeroRewardChoice, ~statesWithZeroRewardChoice, false, 0, choicesWithoutReward); rew0EStates.complement(); return computeReachabilityRewards(env, std::move(goal), transitionMatrix, backwardTransitions, rewardModel, rew0EStates, qualitative, false, hint); } else { // Identify the states from which only states with zero reward are reachable. storm::storage::BitVector statesWithoutReward = rewardModel.getStatesWithZeroReward(transitionMatrix); storm::storage::BitVector rew0AStates = storm::utility::graph::performProbGreater0E(backwardTransitions, statesWithoutReward, ~statesWithoutReward); rew0AStates.complement(); // There might be end components that consists only of states/choices with zero rewards. The reachability reward semantics would assign such // end components reward infinity. To avoid this, we potentially need to eliminate such end components storm::storage::BitVector trueStates(transitionMatrix.getRowGroupCount(), true); if (storm::utility::graph::performProb1A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, trueStates, rew0AStates).full()) { return computeReachabilityRewards(env, std::move(goal), transitionMatrix, backwardTransitions, rewardModel, rew0AStates, qualitative, produceScheduler, hint); } else { // The transformation of schedulers for the ec-eliminated system back to the original one is not implemented. STORM_LOG_ERROR_COND(!produceScheduler, "Can not produce scheduler for this property (functionality not implemented"); storm::storage::BitVector choicesWithoutReward = rewardModel.getChoicesWithZeroReward(transitionMatrix); auto ecElimResult = storm::transformer::EndComponentEliminator::transform(transitionMatrix, storm::storage::BitVector(transitionMatrix.getRowGroupCount(), true), choicesWithoutReward, rew0AStates, true); storm::storage::BitVector newRew0AStates(ecElimResult.matrix.getRowGroupCount(), false); for (auto const& oldRew0AState : rew0AStates) { newRew0AStates.set(ecElimResult.oldToNewStateMapping[oldRew0AState]); } MDPSparseModelCheckingHelperReturnType result = computeReachabilityRewardsHelper(env, std::move(goal), ecElimResult.matrix, ecElimResult.matrix.transpose(true), [&] (uint_fast64_t rowCount, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::BitVector const& maybeStates) { std::vector result; std::vector oldChoiceRewards = rewardModel.getTotalRewardVector(transitionMatrix); result.reserve(rowCount); for (uint64_t newState : maybeStates) { for (uint64_t newChoice = transitionMatrix.getRowGroupIndices()[newState]; newChoice < transitionMatrix.getRowGroupIndices()[newState + 1]; ++newChoice) { uint64_t oldChoice = ecElimResult.newToOldRowMapping[newChoice]; result.push_back(oldChoiceRewards[oldChoice]); } } STORM_LOG_ASSERT(result.size() == rowCount, "Unexpected size of reward vector."); return result; }, newRew0AStates, qualitative, false, [&] () { storm::storage::BitVector newStatesWithoutReward(ecElimResult.matrix.getRowGroupCount(), false); for (auto const& oldStateWithoutRew : statesWithoutReward) { newStatesWithoutReward.set(ecElimResult.oldToNewStateMapping[oldStateWithoutRew]); } return newStatesWithoutReward; }, [&] () { storm::storage::BitVector newChoicesWithoutReward(ecElimResult.matrix.getRowGroupCount(), false); for (uint64_t newChoice = 0; newChoice < ecElimResult.matrix.getRowCount(); ++newChoice) { if (choicesWithoutReward.get(ecElimResult.newToOldRowMapping[newChoice])) { newChoicesWithoutReward.set(newChoice); } } return newChoicesWithoutReward; }); std::vector resultInEcQuotient = std::move(result.values); result.values.resize(ecElimResult.oldToNewStateMapping.size()); storm::utility::vector::selectVectorValues(result.values, ecElimResult.oldToNewStateMapping, resultInEcQuotient); return result; } } } template template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeReachabilityRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, RewardModelType const& rewardModel, storm::storage::BitVector const& targetStates, bool qualitative, bool produceScheduler, ModelCheckerHint const& hint) { // Only compute the result if the model has at least one reward this->getModel(). STORM_LOG_THROW(!rewardModel.empty(), storm::exceptions::InvalidPropertyException, "Reward model for formula is empty. Skipping formula."); return computeReachabilityRewardsHelper(env, std::move(goal), transitionMatrix, backwardTransitions, [&rewardModel] (uint_fast64_t rowCount, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::BitVector const& maybeStates) { return rewardModel.getTotalRewardVector(rowCount, transitionMatrix, maybeStates); }, targetStates, qualitative, produceScheduler, [&] () { return rewardModel.getStatesWithZeroReward(transitionMatrix); }, [&] () { return rewardModel.getChoicesWithZeroReward(transitionMatrix); }, hint); } template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeReachabilityTimes(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& targetStates, bool qualitative, bool produceScheduler, ModelCheckerHint const& hint) { return computeReachabilityRewardsHelper(env, std::move(goal), transitionMatrix, backwardTransitions, [] (uint_fast64_t rowCount, storm::storage::SparseMatrix const&, storm::storage::BitVector const&) { return std::vector(rowCount, storm::utility::one()); }, targetStates, qualitative, produceScheduler, [&] () { return storm::storage::BitVector(transitionMatrix.getRowGroupCount(), false); }, [&] () { return storm::storage::BitVector(transitionMatrix.getRowCount(), false); }, hint); } #ifdef STORM_HAVE_CARL template std::vector SparseMdpPrctlHelper::computeReachabilityRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::models::sparse::StandardRewardModel const& intervalRewardModel, bool lowerBoundOfIntervals, storm::storage::BitVector const& targetStates, bool qualitative) { // Only compute the result if the reward model is not empty. STORM_LOG_THROW(!intervalRewardModel.empty(), storm::exceptions::InvalidPropertyException, "Missing reward model for formula. Skipping formula."); return computeReachabilityRewardsHelper(env, std::move(goal), transitionMatrix, backwardTransitions, \ [&] (uint_fast64_t rowCount, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::BitVector const& maybeStates) { std::vector result; result.reserve(rowCount); std::vector subIntervalVector = intervalRewardModel.getTotalRewardVector(rowCount, transitionMatrix, maybeStates); for (auto const& interval : subIntervalVector) { result.push_back(lowerBoundOfIntervals ? interval.lower() : interval.upper()); } return result; }, targetStates, qualitative, false, [&] () { return intervalRewardModel.getStatesWithFilter(transitionMatrix, [&](storm::Interval const& i) {return storm::utility::isZero(lowerBoundOfIntervals ? i.lower() : i.upper());}); }, [&] () { return intervalRewardModel.getChoicesWithFilter(transitionMatrix, [&](storm::Interval const& i) {return storm::utility::isZero(lowerBoundOfIntervals ? i.lower() : i.upper());}); }).values; } template<> std::vector SparseMdpPrctlHelper::computeReachabilityRewards(Environment const& env, storm::solver::SolveGoal&&, storm::storage::SparseMatrix const&, storm::storage::SparseMatrix const&, storm::models::sparse::StandardRewardModel const&, bool, storm::storage::BitVector const&, bool) { STORM_LOG_THROW(false, storm::exceptions::IllegalFunctionCallException, "Computing reachability rewards is unsupported for this data type."); } #endif struct QualitativeStateSetsReachabilityRewards { storm::storage::BitVector maybeStates; storm::storage::BitVector infinityStates; storm::storage::BitVector rewardZeroStates; }; template QualitativeStateSetsReachabilityRewards getQualitativeStateSetsReachabilityRewardsFromHint(ModelCheckerHint const& hint, storm::storage::BitVector const& targetStates) { QualitativeStateSetsReachabilityRewards result; result.maybeStates = hint.template asExplicitModelCheckerHint().getMaybeStates(); // Treat the states with reward zero/infinity. std::vector const& resultsForNonMaybeStates = hint.template asExplicitModelCheckerHint().getResultHint(); result.infinityStates = storm::storage::BitVector(result.maybeStates.size()); result.rewardZeroStates = storm::storage::BitVector(result.maybeStates.size()); storm::storage::BitVector nonMaybeStates = ~result.maybeStates; for (auto const& state : nonMaybeStates) { if (storm::utility::isZero(resultsForNonMaybeStates[state])) { result.rewardZeroStates.set(state, true); } else { STORM_LOG_THROW(storm::utility::isInfinity(resultsForNonMaybeStates[state]), storm::exceptions::IllegalArgumentException, "Expected that the result hint specifies probabilities in {0,infinity} for non-maybe states"); result.infinityStates.set(state, true); } } return result; } template QualitativeStateSetsReachabilityRewards computeQualitativeStateSetsReachabilityRewards(storm::solver::SolveGoal const& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& targetStates, std::function const& zeroRewardStatesGetter, std::function const& zeroRewardChoicesGetter) { QualitativeStateSetsReachabilityRewards result; storm::storage::BitVector trueStates(transitionMatrix.getRowGroupCount(), true); if (goal.minimize()) { result.infinityStates = storm::utility::graph::performProb1E(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, trueStates, targetStates); } else { result.infinityStates = storm::utility::graph::performProb1A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, trueStates, targetStates); } result.infinityStates.complement(); if (storm::settings::getModule().isFilterRewZeroSet()) { if (goal.minimize()) { result.rewardZeroStates = storm::utility::graph::performProb1E(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, trueStates, targetStates, zeroRewardChoicesGetter()); } else { result.rewardZeroStates = storm::utility::graph::performProb1A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, zeroRewardStatesGetter(), targetStates); } } else { result.rewardZeroStates = targetStates; } result.maybeStates = ~(result.rewardZeroStates | result.infinityStates); return result; } template QualitativeStateSetsReachabilityRewards getQualitativeStateSetsReachabilityRewards(storm::solver::SolveGoal const& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& targetStates, ModelCheckerHint const& hint, std::function const& zeroRewardStatesGetter, std::function const& zeroRewardChoicesGetter) { if (hint.isExplicitModelCheckerHint() && hint.template asExplicitModelCheckerHint().getComputeOnlyMaybeStates()) { return getQualitativeStateSetsReachabilityRewardsFromHint(hint, targetStates); } else { return computeQualitativeStateSetsReachabilityRewards(goal, transitionMatrix, backwardTransitions, targetStates, zeroRewardStatesGetter, zeroRewardChoicesGetter); } } template void extendScheduler(storm::storage::Scheduler& scheduler, storm::solver::SolveGoal const& goal, QualitativeStateSetsReachabilityRewards const& qualitativeStateSets, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& targetStates, std::function const& zeroRewardChoicesGetter) { // Finally, if we need to produce a scheduler, we also need to figure out the parts of the scheduler for // the states with reward zero/infinity. if (goal.minimize()) { storm::utility::graph::computeSchedulerProb1E(qualitativeStateSets.rewardZeroStates, transitionMatrix, backwardTransitions, qualitativeStateSets.rewardZeroStates, targetStates, scheduler, zeroRewardChoicesGetter()); for (auto const& state : qualitativeStateSets.infinityStates) { scheduler.setChoice(0, state); } } else { storm::utility::graph::computeSchedulerRewInf(qualitativeStateSets.infinityStates, transitionMatrix, scheduler); for (auto const& state : qualitativeStateSets.rewardZeroStates) { scheduler.setChoice(0, state); } } } template void extractSchedulerChoices(storm::storage::Scheduler& scheduler, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& subChoices, storm::storage::BitVector const& maybeStates, boost::optional const& selectedChoices) { auto subChoiceIt = subChoices.begin(); if (selectedChoices) { for (auto maybeState : maybeStates) { // find the rowindex that corresponds to the selected row of the submodel uint_fast64_t firstRowIndex = transitionMatrix.getRowGroupIndices()[maybeState]; uint_fast64_t selectedRowIndex = selectedChoices->getNextSetIndex(firstRowIndex); for (uint_fast64_t choice = 0; choice < *subChoiceIt; ++choice) { selectedRowIndex = selectedChoices->getNextSetIndex(selectedRowIndex + 1); } scheduler.setChoice(selectedRowIndex - firstRowIndex, maybeState); ++subChoiceIt; } } else { for (auto maybeState : maybeStates) { scheduler.setChoice(*subChoiceIt, maybeState); ++subChoiceIt; } } assert(subChoiceIt == subChoices.end()); } template void computeFixedPointSystemReachabilityRewards(storm::solver::SolveGoal& goal, storm::storage::SparseMatrix const& transitionMatrix, QualitativeStateSetsReachabilityRewards const& qualitativeStateSets, boost::optional const& selectedChoices, std::function(uint_fast64_t, storm::storage::SparseMatrix const&, storm::storage::BitVector const&)> const& totalStateRewardVectorGetter, storm::storage::SparseMatrix& submatrix, std::vector& b, std::vector* oneStepTargetProbabilities = nullptr) { // Remove rows and columns from the original transition probability matrix for states whose reward values are already known. // If there are infinity states, we additionally have to remove choices of maybeState that lead to infinity. if (qualitativeStateSets.infinityStates.empty()) { submatrix = transitionMatrix.getSubmatrix(true, qualitativeStateSets.maybeStates, qualitativeStateSets.maybeStates, false); b = totalStateRewardVectorGetter(submatrix.getRowCount(), transitionMatrix, qualitativeStateSets.maybeStates); if (oneStepTargetProbabilities) { (*oneStepTargetProbabilities) = transitionMatrix.getConstrainedRowGroupSumVector(qualitativeStateSets.maybeStates, qualitativeStateSets.rewardZeroStates); } } else { submatrix = transitionMatrix.getSubmatrix(false, *selectedChoices, qualitativeStateSets.maybeStates, false); b = totalStateRewardVectorGetter(transitionMatrix.getRowCount(), transitionMatrix, storm::storage::BitVector(transitionMatrix.getRowGroupCount(), true)); storm::utility::vector::filterVectorInPlace(b, *selectedChoices); if (oneStepTargetProbabilities) { (*oneStepTargetProbabilities) = transitionMatrix.getConstrainedRowSumVector(*selectedChoices, qualitativeStateSets.rewardZeroStates); } } // If the solve goal has relevant values, we need to adjust them. goal.restrictRelevantValues(qualitativeStateSets.maybeStates); } template boost::optional> computeFixedPointSystemReachabilityRewardsEliminateEndComponents(storm::solver::SolveGoal& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, QualitativeStateSetsReachabilityRewards const& qualitativeStateSets, boost::optional const& selectedChoices, std::function(uint_fast64_t, storm::storage::SparseMatrix const&, storm::storage::BitVector const&)> const& totalStateRewardVectorGetter, storm::storage::SparseMatrix& submatrix, std::vector& b, boost::optional>& oneStepTargetProbabilities) { // Start by computing the choices with reward 0, as we only want ECs within this fragment. storm::storage::BitVector zeroRewardChoices(transitionMatrix.getRowCount()); // Get the rewards of all choices. std::vector rewardVector = totalStateRewardVectorGetter(transitionMatrix.getRowCount(), transitionMatrix, storm::storage::BitVector(transitionMatrix.getRowGroupCount(), true)); uint64_t index = 0; for (auto const& e : rewardVector) { if (storm::utility::isZero(e)) { zeroRewardChoices.set(index); } ++index; } // Compute the states that have some zero reward choice. storm::storage::BitVector candidateStates(qualitativeStateSets.maybeStates); for (auto state : qualitativeStateSets.maybeStates) { bool keepState = false; for (auto row = transitionMatrix.getRowGroupIndices()[state], rowEnd = transitionMatrix.getRowGroupIndices()[state + 1]; row < rowEnd; ++row) { if (zeroRewardChoices.get(row)) { keepState = true; break; } } if (!keepState) { candidateStates.set(state, false); } } // Only keep the candidate states that (under some scheduler) can stay in the set of candidates forever candidateStates = storm::utility::graph::performProb0E(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, candidateStates, ~candidateStates); bool doDecomposition = !candidateStates.empty(); storm::storage::MaximalEndComponentDecomposition endComponentDecomposition; if (doDecomposition) { // Then compute the states that are in MECs with zero reward. endComponentDecomposition = storm::storage::MaximalEndComponentDecomposition(transitionMatrix, backwardTransitions, candidateStates, zeroRewardChoices); } // Only do more work if there are actually end-components. if (doDecomposition && !endComponentDecomposition.empty()) { STORM_LOG_DEBUG("Eliminating " << endComponentDecomposition.size() << " ECs."); SparseMdpEndComponentInformation result = SparseMdpEndComponentInformation::eliminateEndComponents(endComponentDecomposition, transitionMatrix, qualitativeStateSets.maybeStates, oneStepTargetProbabilities ? &qualitativeStateSets.rewardZeroStates : nullptr, selectedChoices ? &selectedChoices.get() : nullptr, &rewardVector, submatrix, oneStepTargetProbabilities ? &oneStepTargetProbabilities.get() : nullptr, &b); // If the solve goal has relevant values, we need to adjust them. if (goal.hasRelevantValues()) { storm::storage::BitVector newRelevantValues(submatrix.getRowGroupCount()); for (auto state : goal.relevantValues()) { if (qualitativeStateSets.maybeStates.get(state)) { newRelevantValues.set(result.getRowGroupAfterElimination(state)); } } if (!newRelevantValues.empty()) { goal.setRelevantValues(std::move(newRelevantValues)); } } return result; } else { STORM_LOG_DEBUG("Not eliminating ECs as there are none."); computeFixedPointSystemReachabilityRewards(goal, transitionMatrix, qualitativeStateSets, selectedChoices, totalStateRewardVectorGetter, submatrix, b, oneStepTargetProbabilities ? &oneStepTargetProbabilities.get() : nullptr); return boost::none; } } template void computeUpperRewardBounds(SparseMdpHintType& hintInformation, storm::OptimizationDirection const& direction, storm::storage::SparseMatrix const& submatrix, std::vector const& choiceRewards, std::vector const& oneStepTargetProbabilities) { // For the min-case, we use DS-MPI, for the max-case variant 2 of the Baier et al. paper (CAV'17). if (direction == storm::OptimizationDirection::Minimize) { DsMpiMdpUpperRewardBoundsComputer dsmpi(submatrix, choiceRewards, oneStepTargetProbabilities); hintInformation.upperResultBounds = dsmpi.computeUpperBounds(); } else { BaierUpperRewardBoundsComputer baier(submatrix, choiceRewards, oneStepTargetProbabilities); hintInformation.upperResultBound = baier.computeUpperBound(); } } template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeReachabilityRewardsHelper(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::function(uint_fast64_t, storm::storage::SparseMatrix const&, storm::storage::BitVector const&)> const& totalStateRewardVectorGetter, storm::storage::BitVector const& targetStates, bool qualitative, bool produceScheduler, std::function const& zeroRewardStatesGetter, std::function const& zeroRewardChoicesGetter, ModelCheckerHint const& hint) { // Prepare resulting vector. std::vector result(transitionMatrix.getRowGroupCount(), storm::utility::zero()); // Determine which states have a reward that is infinity or less than infinity. QualitativeStateSetsReachabilityRewards qualitativeStateSets = getQualitativeStateSetsReachabilityRewards(goal, transitionMatrix, backwardTransitions, targetStates, hint, zeroRewardStatesGetter, zeroRewardChoicesGetter); STORM_LOG_INFO("Preprocessing: " << qualitativeStateSets.infinityStates.getNumberOfSetBits() << " states with reward infinity, " << qualitativeStateSets.rewardZeroStates.getNumberOfSetBits() << " states with reward zero (" << qualitativeStateSets.maybeStates.getNumberOfSetBits() << " states remaining)."); storm::utility::vector::setVectorValues(result, qualitativeStateSets.infinityStates, storm::utility::infinity()); // If requested, we will produce a scheduler. std::unique_ptr> scheduler; if (produceScheduler) { scheduler = std::make_unique>(transitionMatrix.getRowGroupCount()); } // Check whether we need to compute exact rewards for some states. if (qualitative) { STORM_LOG_INFO("The rewards for the initial states were determined in a preprocessing step. No exact rewards were computed."); // Set the values for all maybe-states to 1 to indicate that their reward values // are neither 0 nor infinity. storm::utility::vector::setVectorValues(result, qualitativeStateSets.maybeStates, storm::utility::one()); } else { if (!qualitativeStateSets.maybeStates.empty()) { // In this case we have to compute the reward values for the remaining states. // Store the choices that lead to non-infinity values. If none, all choices im maybe states can be selected. boost::optional selectedChoices; if (!qualitativeStateSets.infinityStates.empty()) { selectedChoices = transitionMatrix.getRowFilter(qualitativeStateSets.maybeStates, ~qualitativeStateSets.infinityStates); } // Obtain proper hint information either from the provided hint or from requirements of the solver. SparseMdpHintType hintInformation = computeHints(env, SolutionType::ExpectedRewards, hint, goal.direction(), transitionMatrix, backwardTransitions, qualitativeStateSets.maybeStates, ~qualitativeStateSets.rewardZeroStates, qualitativeStateSets.rewardZeroStates, produceScheduler, selectedChoices); // Declare the components of the equation system we will solve. storm::storage::SparseMatrix submatrix; std::vector b; // If we need to compute upper bounds on the reward values, we need the one step probabilities // to a target state. boost::optional> oneStepTargetProbabilities; if (hintInformation.getComputeUpperBounds()) { oneStepTargetProbabilities = std::vector(); } // If the hint information tells us that we have to eliminate MECs, we do so now. boost::optional> ecInformation; if (hintInformation.getEliminateEndComponents()) { ecInformation = computeFixedPointSystemReachabilityRewardsEliminateEndComponents(goal, transitionMatrix, backwardTransitions, qualitativeStateSets, selectedChoices, totalStateRewardVectorGetter, submatrix, b, oneStepTargetProbabilities); // Make sure we are not supposed to produce a scheduler if we actually eliminate end components. STORM_LOG_THROW(!ecInformation || !ecInformation.get().getEliminatedEndComponents() || !produceScheduler, storm::exceptions::NotSupportedException, "Producing schedulers is not supported if end-components need to be eliminated for the solver."); } else { // Otherwise, we compute the standard equations. computeFixedPointSystemReachabilityRewards(goal, transitionMatrix, qualitativeStateSets, selectedChoices, totalStateRewardVectorGetter, submatrix, b, oneStepTargetProbabilities ? &oneStepTargetProbabilities.get() : nullptr); } // If we need to compute upper bounds, do so now. if (hintInformation.getComputeUpperBounds()) { STORM_LOG_ASSERT(oneStepTargetProbabilities, "Expecting one step target probability vector to be available."); computeUpperRewardBounds(hintInformation, goal.direction(), submatrix, b, oneStepTargetProbabilities.get()); } // Now compute the results for the maybe states. MaybeStateResult resultForMaybeStates = computeValuesForMaybeStates(env, std::move(goal), std::move(submatrix), b, produceScheduler, hintInformation); // If we eliminated end components, we need to extract the result differently. if (ecInformation && ecInformation.get().getEliminatedEndComponents()) { ecInformation.get().setValues(result, qualitativeStateSets.maybeStates, resultForMaybeStates.getValues()); } else { // Set values of resulting vector according to result. storm::utility::vector::setVectorValues(result, qualitativeStateSets.maybeStates, resultForMaybeStates.getValues()); } if (produceScheduler) { extractSchedulerChoices(*scheduler, transitionMatrix, resultForMaybeStates.getScheduler(), qualitativeStateSets.maybeStates, selectedChoices); } } } // Extend scheduler with choices for the states in the qualitative state sets. if (produceScheduler) { extendScheduler(*scheduler, goal, qualitativeStateSets, transitionMatrix, backwardTransitions, targetStates, zeroRewardChoicesGetter); } // Sanity check for created scheduler. STORM_LOG_ASSERT(!produceScheduler || scheduler, "Expected that a scheduler was obtained."); STORM_LOG_ASSERT((!produceScheduler && !scheduler) || !scheduler->isPartialScheduler(), "Expected a fully defined scheduler"); STORM_LOG_ASSERT((!produceScheduler && !scheduler) || scheduler->isDeterministicScheduler(), "Expected a deterministic scheduler"); STORM_LOG_ASSERT((!produceScheduler && !scheduler) || scheduler->isMemorylessScheduler(), "Expected a memoryless scheduler"); return MDPSparseModelCheckingHelperReturnType(std::move(result), std::move(scheduler)); } template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeLongRunAverageProbabilities(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& psiStates, bool produceScheduler) { // If there are no goal states, we avoid the computation and directly return zero. if (psiStates.empty()) { return std::vector(transitionMatrix.getRowGroupCount(), storm::utility::zero()); } // Likewise, if all bits are set, we can avoid the computation and set. if (psiStates.full()) { return std::vector(transitionMatrix.getRowGroupCount(), storm::utility::one()); } // Reduce long run average probabilities to long run average rewards by // building a reward model assigning one reward to every psi state std::vector stateRewards(psiStates.size(), storm::utility::zero()); storm::utility::vector::setVectorValues(stateRewards, psiStates, storm::utility::one()); storm::models::sparse::StandardRewardModel rewardModel(std::move(stateRewards)); return computeLongRunAverageRewards(env, std::move(goal), transitionMatrix, backwardTransitions, rewardModel, produceScheduler); } template template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeLongRunAverageRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, RewardModelType const& rewardModel, bool produceScheduler) { uint64_t numberOfStates = transitionMatrix.getRowGroupCount(); std::unique_ptr> scheduler; if (produceScheduler) { scheduler = std::make_unique>(numberOfStates); } // Start by decomposing the MDP into its MECs. storm::storage::MaximalEndComponentDecomposition mecDecomposition(transitionMatrix, backwardTransitions); // Get some data members for convenience. std::vector const& nondeterministicChoiceIndices = transitionMatrix.getRowGroupIndices(); ValueType zero = storm::utility::zero(); //first calculate LRA for the Maximal End Components. storm::storage::BitVector statesInMecs(numberOfStates); std::vector stateToMecIndexMap(transitionMatrix.getColumnCount()); std::vector lraValuesForEndComponents(mecDecomposition.size(), zero); auto underlyingSolverEnvironment = env; if (env.solver().isForceSoundness()) { // For sound computations, the error in the MECS plus the error in the remaining system should be less than the user defined precsion. underlyingSolverEnvironment.solver().lra().setPrecision(env.solver().lra().getPrecision() / storm::utility::convertNumber(2)); underlyingSolverEnvironment.solver().minMax().setPrecision(env.solver().lra().getPrecision() / storm::utility::convertNumber(2)); underlyingSolverEnvironment.solver().minMax().setRelativeTerminationCriterion(env.solver().lra().getRelativeTerminationCriterion()); } for (uint_fast64_t currentMecIndex = 0; currentMecIndex < mecDecomposition.size(); ++currentMecIndex) { storm::storage::MaximalEndComponent const& mec = mecDecomposition[currentMecIndex]; lraValuesForEndComponents[currentMecIndex] = computeLraForMaximalEndComponent(underlyingSolverEnvironment, goal.direction(), transitionMatrix, rewardModel, mec, scheduler); // Gather information for later use. for (auto const& stateChoicesPair : mec) { statesInMecs.set(stateChoicesPair.first); stateToMecIndexMap[stateChoicesPair.first] = currentMecIndex; } } // For fast transition rewriting, we build some auxiliary data structures. storm::storage::BitVector statesNotContainedInAnyMec = ~statesInMecs; uint_fast64_t firstAuxiliaryStateIndex = statesNotContainedInAnyMec.getNumberOfSetBits(); uint_fast64_t lastStateNotInMecs = 0; uint_fast64_t numberOfStatesNotInMecs = 0; std::vector statesNotInMecsBeforeIndex; statesNotInMecsBeforeIndex.reserve(numberOfStates); for (auto state : statesNotContainedInAnyMec) { while (lastStateNotInMecs <= state) { statesNotInMecsBeforeIndex.push_back(numberOfStatesNotInMecs); ++lastStateNotInMecs; } ++numberOfStatesNotInMecs; } // Finally, we are ready to create the SSP matrix and right-hand side of the SSP. std::vector b; uint64_t numberOfSspStates = numberOfStatesNotInMecs + mecDecomposition.size(); typename storm::storage::SparseMatrixBuilder sspMatrixBuilder(0, numberOfSspStates, 0, false, true, numberOfSspStates); // If the source state is not contained in any MEC, we copy its choices (and perform the necessary modifications). uint_fast64_t currentChoice = 0; for (auto state : statesNotContainedInAnyMec) { sspMatrixBuilder.newRowGroup(currentChoice); for (uint_fast64_t choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice, ++currentChoice) { std::vector auxiliaryStateToProbabilityMap(mecDecomposition.size()); b.push_back(storm::utility::zero()); for (auto element : transitionMatrix.getRow(choice)) { if (statesNotContainedInAnyMec.get(element.getColumn())) { // If the target state is not contained in an MEC, we can copy over the entry. sspMatrixBuilder.addNextValue(currentChoice, statesNotInMecsBeforeIndex[element.getColumn()], element.getValue()); } else { // If the target state is contained in MEC i, we need to add the probability to the corresponding field in the vector // so that we are able to write the cumulative probability to the MEC into the matrix. auxiliaryStateToProbabilityMap[stateToMecIndexMap[element.getColumn()]] += element.getValue(); } } // Now insert all (cumulative) probability values that target an MEC. for (uint_fast64_t mecIndex = 0; mecIndex < auxiliaryStateToProbabilityMap.size(); ++mecIndex) { if (!storm::utility::isZero(auxiliaryStateToProbabilityMap[mecIndex])) { sspMatrixBuilder.addNextValue(currentChoice, firstAuxiliaryStateIndex + mecIndex, auxiliaryStateToProbabilityMap[mecIndex]); } } } } std::vector> sspMecChoicesToOriginalMap; // for scheduler extraction // Now we are ready to construct the choices for the auxiliary states. for (uint_fast64_t mecIndex = 0; mecIndex < mecDecomposition.size(); ++mecIndex) { storm::storage::MaximalEndComponent const& mec = mecDecomposition[mecIndex]; sspMatrixBuilder.newRowGroup(currentChoice); for (auto const& stateChoicesPair : mec) { uint_fast64_t state = stateChoicesPair.first; storm::storage::FlatSet const& choicesInMec = stateChoicesPair.second; for (uint_fast64_t choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) { // If the choice is not contained in the MEC itself, we have to add a similar distribution to the auxiliary state. if (choicesInMec.find(choice) == choicesInMec.end()) { std::vector auxiliaryStateToProbabilityMap(mecDecomposition.size()); b.push_back(storm::utility::zero()); for (auto element : transitionMatrix.getRow(choice)) { if (statesNotContainedInAnyMec.get(element.getColumn())) { // If the target state is not contained in an MEC, we can copy over the entry. sspMatrixBuilder.addNextValue(currentChoice, statesNotInMecsBeforeIndex[element.getColumn()], element.getValue()); } else { // If the target state is contained in MEC i, we need to add the probability to the corresponding field in the vector // so that we are able to write the cumulative probability to the MEC into the matrix. auxiliaryStateToProbabilityMap[stateToMecIndexMap[element.getColumn()]] += element.getValue(); } } // Now insert all (cumulative) probability values that target an MEC. for (uint_fast64_t targetMecIndex = 0; targetMecIndex < auxiliaryStateToProbabilityMap.size(); ++targetMecIndex) { if (!storm::utility::isZero(auxiliaryStateToProbabilityMap[targetMecIndex])) { sspMatrixBuilder.addNextValue(currentChoice, firstAuxiliaryStateIndex + targetMecIndex, auxiliaryStateToProbabilityMap[targetMecIndex]); } } if (produceScheduler) { sspMecChoicesToOriginalMap.emplace_back(state, choice - nondeterministicChoiceIndices[state]); } ++currentChoice; } } } // For each auxiliary state, there is the option to achieve the reward value of the LRA associated with the MEC. ++currentChoice; b.push_back(lraValuesForEndComponents[mecIndex]); if (produceScheduler) { // Insert some invalid values sspMecChoicesToOriginalMap.emplace_back(std::numeric_limits::max(), std::numeric_limits::max()); } } // Finalize the matrix and solve the corresponding system of equations. storm::storage::SparseMatrix sspMatrix = sspMatrixBuilder.build(currentChoice, numberOfSspStates, numberOfSspStates); // Check for requirements of the solver. storm::solver::GeneralMinMaxLinearEquationSolverFactory minMaxLinearEquationSolverFactory; storm::solver::MinMaxLinearEquationSolverRequirements requirements = minMaxLinearEquationSolverFactory.getRequirements(underlyingSolverEnvironment, true, true, goal.direction(), false, produceScheduler); requirements.clearBounds(); STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException, "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked."); std::vector sspResult(numberOfSspStates); goal.restrictRelevantValues(statesNotContainedInAnyMec); std::unique_ptr> solver = storm::solver::configureMinMaxLinearEquationSolver(underlyingSolverEnvironment, std::move(goal), minMaxLinearEquationSolverFactory, sspMatrix); solver->setLowerBound(storm::utility::zero()); solver->setUpperBound(*std::max_element(lraValuesForEndComponents.begin(), lraValuesForEndComponents.end())); solver->setHasUniqueSolution(); solver->setHasNoEndComponents(); solver->setTrackScheduler(produceScheduler); solver->setRequirementsChecked(); solver->solveEquations(underlyingSolverEnvironment, sspResult, b); // Prepare result vector. std::vector result(numberOfStates, zero); // Set the values for states not contained in MECs. storm::utility::vector::setVectorValues(result, statesNotContainedInAnyMec, sspResult); // Set the values for all states in MECs. for (auto state : statesInMecs) { result[state] = sspResult[firstAuxiliaryStateIndex + stateToMecIndexMap[state]]; } if (produceScheduler && solver->hasScheduler()) { // Translate result for ssp matrix to original model auto const& sspChoices = solver->getSchedulerChoices(); uint64_t sspState = 0; for (auto state : statesNotContainedInAnyMec) { scheduler->setChoice(sspChoices[sspState], state); ++sspState; } // The other sspStates correspond to MECS in the original system. uint_fast64_t rowOffset = sspMatrix.getRowGroupIndices()[sspState]; for (uint_fast64_t mecIndex = 0; mecIndex < mecDecomposition.size(); ++mecIndex) { // Obtain the state and choice of the original model to which the selected choice corresponds. auto const& originalStateChoice = sspMecChoicesToOriginalMap[sspMatrix.getRowGroupIndices()[sspState] + sspChoices[sspState] - rowOffset]; // Check if the best choice is to stay in this MEC if (originalStateChoice.first == std::numeric_limits::max()) { STORM_LOG_ASSERT(sspMatrix.getRow(sspState, sspChoices[sspState]).getNumberOfEntries() == 0, "Expected empty row at choice that stays in MEC."); // In this case, no further operations are necessary. The scheduler has already been set to the optimal choices during the call of computeLraForMaximalEndComponent. } else { // The best choice is to leave this MEC via the selected state and choice. scheduler->setChoice(originalStateChoice.second, originalStateChoice.first); // The remaining states in this MEC need to reach this state with probability 1. storm::storage::BitVector exitStateAsBitVector(transitionMatrix.getRowGroupCount(), false); exitStateAsBitVector.set(originalStateChoice.first, true); storm::storage::BitVector otherStatesAsBitVector(transitionMatrix.getRowGroupCount(), false); for (auto const& stateChoices : mecDecomposition[mecIndex]) { if (stateChoices.first != originalStateChoice.first) { otherStatesAsBitVector.set(stateChoices.first, true); } } storm::utility::graph::computeSchedulerProb1E(otherStatesAsBitVector, transitionMatrix, backwardTransitions, otherStatesAsBitVector, exitStateAsBitVector, *scheduler); } ++sspState; } assert(sspState == sspMatrix.getRowGroupCount()); } else { STORM_LOG_ERROR_COND(!produceScheduler, "Requested to produce a scheduler, but no scheduler was generated."); } return MDPSparseModelCheckingHelperReturnType(std::move(result), std::move(scheduler)); } template template ValueType SparseMdpPrctlHelper::computeLraForMaximalEndComponent(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, RewardModelType const& rewardModel, storm::storage::MaximalEndComponent const& mec, std::unique_ptr>& scheduler) { // If the mec only consists of a single state, we compute the LRA value directly if (++mec.begin() == mec.end()) { uint64_t state = mec.begin()->first; auto choiceIt = mec.begin()->second.begin(); ValueType result = rewardModel.getTotalStateActionReward(state, *choiceIt, transitionMatrix); uint_fast64_t bestChoice = *choiceIt; for (++choiceIt; choiceIt != mec.begin()->second.end(); ++choiceIt) { ValueType choiceValue = rewardModel.getTotalStateActionReward(state, *choiceIt, transitionMatrix); if (storm::solver::minimize(dir)) { if (result > choiceValue) { result = std::move(choiceValue); bestChoice = *choiceIt; } } else { if (result < choiceValue) { result = std::move(choiceValue); bestChoice = *choiceIt; } } } if (scheduler) { scheduler->setChoice(bestChoice - transitionMatrix.getRowGroupIndices()[state], state); } return result; } // Solve MEC with the method specified in the settings storm::solver::LraMethod method = env.solver().lra().getNondetLraMethod(); if ((storm::NumberTraits::IsExact || env.solver().isForceExact()) && env.solver().lra().isNondetLraMethodSetFromDefault() && method != storm::solver::LraMethod::LinearProgramming) { STORM_LOG_INFO("Selecting 'LP' as the solution technique for long-run properties to guarantee exact results. If you want to override this, please explicitly specify a different LRA method."); method = storm::solver::LraMethod::LinearProgramming; } else if (env.solver().isForceSoundness() && env.solver().lra().isNondetLraMethodSetFromDefault() && method != storm::solver::LraMethod::ValueIteration) { STORM_LOG_INFO("Selecting 'VI' as the solution technique for long-run properties to guarantee sound results. If you want to override this, please explicitly specify a different LRA method."); method = storm::solver::LraMethod::ValueIteration; } STORM_LOG_ERROR_COND(scheduler == nullptr || method == storm::solver::LraMethod::ValueIteration, "Scheduler generation not supported for the chosen LRA method. Try value-iteration."); if (method == storm::solver::LraMethod::LinearProgramming) { return computeLraForMaximalEndComponentLP(env, dir, transitionMatrix, rewardModel, mec); } else if (method == storm::solver::LraMethod::ValueIteration) { return computeLraForMaximalEndComponentVI(env, dir, transitionMatrix, rewardModel, mec, scheduler); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "Unsupported technique."); } } template template ValueType SparseMdpPrctlHelper::computeLraForMaximalEndComponentVI(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, RewardModelType const& rewardModel, storm::storage::MaximalEndComponent const& mec, std::unique_ptr>& scheduler) { // Initialize data about the mec storm::storage::BitVector mecStates(transitionMatrix.getRowGroupCount(), false); storm::storage::BitVector mecChoices(transitionMatrix.getRowCount(), false); for (auto const& stateChoicesPair : mec) { mecStates.set(stateChoicesPair.first); for (auto const& choice : stateChoicesPair.second) { mecChoices.set(choice); } } boost::container::flat_map toSubModelStateMapping; uint64_t currState = 0; toSubModelStateMapping.reserve(mecStates.getNumberOfSetBits()); for (auto const& mecState : mecStates) { toSubModelStateMapping.insert(std::pair(mecState, currState)); ++currState; } // Get a transition matrix that only considers the states and choices within the MEC storm::storage::SparseMatrixBuilder mecTransitionBuilder(mecChoices.getNumberOfSetBits(), mecStates.getNumberOfSetBits(), 0, true, true, mecStates.getNumberOfSetBits()); std::vector choiceRewards; choiceRewards.reserve(mecChoices.getNumberOfSetBits()); uint64_t currRow = 0; ValueType selfLoopProb = storm::utility::convertNumber(env.solver().lra().getAperiodicFactor()); ValueType scalingFactor = storm::utility::one() - selfLoopProb; for (auto const& mecState : mecStates) { mecTransitionBuilder.newRowGroup(currRow); uint64_t groupStart = transitionMatrix.getRowGroupIndices()[mecState]; uint64_t groupEnd = transitionMatrix.getRowGroupIndices()[mecState + 1]; for (uint64_t choice = mecChoices.getNextSetIndex(groupStart); choice < groupEnd; choice = mecChoices.getNextSetIndex(choice + 1)) { bool insertedDiagElement = false; for (auto const& entry : transitionMatrix.getRow(choice)) { uint64_t column = toSubModelStateMapping[entry.getColumn()]; if (!insertedDiagElement && entry.getColumn() > mecState) { mecTransitionBuilder.addNextValue(currRow, toSubModelStateMapping[mecState], selfLoopProb); insertedDiagElement = true; } if (!insertedDiagElement && entry.getColumn() == mecState) { mecTransitionBuilder.addNextValue(currRow, column, selfLoopProb + scalingFactor * entry.getValue()); insertedDiagElement = true; } else { mecTransitionBuilder.addNextValue(currRow, column, scalingFactor * entry.getValue()); } } if (!insertedDiagElement) { mecTransitionBuilder.addNextValue(currRow, toSubModelStateMapping[mecState], selfLoopProb); } // Compute the rewards obtained for this choice choiceRewards.push_back(scalingFactor * rewardModel.getTotalStateActionReward(mecState, choice, transitionMatrix)); ++currRow; } } auto mecTransitions = mecTransitionBuilder.build(); STORM_LOG_ASSERT(mecTransitions.isProbabilistic(), "The MEC-Matrix is not probabilistic."); // start the iterations ValueType precision = storm::utility::convertNumber(env.solver().lra().getPrecision()) / scalingFactor; bool relative = env.solver().lra().getRelativeTerminationCriterion(); std::vector x(mecTransitions.getRowGroupCount(), storm::utility::zero()); std::vector xPrime = x; auto multiplier = storm::solver::MultiplierFactory().create(env, mecTransitions); ValueType maxDiff, minDiff; uint64_t iter = 0; boost::optional maxIter; if (env.solver().lra().isMaximalIterationCountSet()) { maxIter = env.solver().lra().getMaximalIterationCount(); } while (!maxIter.is_initialized() || iter < maxIter.get()) { ++iter; // Compute the obtained rewards for the next step multiplier->multiplyAndReduce(env, dir, x, &choiceRewards, x); // update xPrime and check for convergence // to avoid large (and numerically unstable) x-values, we substract a reference value. auto xIt = x.begin(); auto xPrimeIt = xPrime.begin(); ValueType refVal = *xIt; maxDiff = *xIt - *xPrimeIt; minDiff = maxDiff; *xIt -= refVal; *xPrimeIt = *xIt; for (++xIt, ++xPrimeIt; xIt != x.end(); ++xIt, ++xPrimeIt) { ValueType diff = *xIt - *xPrimeIt; maxDiff = std::max(maxDiff, diff); minDiff = std::min(minDiff, diff); *xIt -= refVal; *xPrimeIt = *xIt; } if ((maxDiff - minDiff) <= (relative ? (precision * minDiff) : precision)) { break; } } if (maxIter.is_initialized() && iter == maxIter.get()) { STORM_LOG_WARN("LRA computation did not converge within " << iter << " iterations."); } else { STORM_LOG_TRACE("LRA computation converged after " << iter << " iterations."); } if (scheduler) { std::vector localMecChoices(mecTransitions.getRowGroupCount(), 0); multiplier->multiplyAndReduce(env, dir, x, &choiceRewards, x, &localMecChoices); auto localMecChoiceIt = localMecChoices.begin(); for (auto const& mecState : mecStates) { // Get the choice index of the selected mec choice with respect to the global transition matrix. uint_fast64_t globalChoice = mecChoices.getNextSetIndex(transitionMatrix.getRowGroupIndices()[mecState]); for (uint_fast64_t i = 0; i < *localMecChoiceIt; ++i) { globalChoice = mecChoices.getNextSetIndex(globalChoice + 1); } STORM_LOG_ASSERT(globalChoice < transitionMatrix.getRowGroupIndices()[mecState + 1], "Invalid global choice for mec state."); scheduler->setChoice(globalChoice - transitionMatrix.getRowGroupIndices()[mecState], mecState); ++localMecChoiceIt; } } return (maxDiff + minDiff) / (storm::utility::convertNumber(2.0) * scalingFactor); } template template ValueType SparseMdpPrctlHelper::computeLraForMaximalEndComponentLP(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, RewardModelType const& rewardModel, storm::storage::MaximalEndComponent const& mec) { std::shared_ptr> solver = storm::utility::solver::getLpSolver("LRA for MEC"); solver->setOptimizationDirection(invert(dir)); // First, we need to create the variables for the problem. std::map stateToVariableMap; for (auto const& stateChoicesPair : mec) { std::string variableName = "h" + std::to_string(stateChoicesPair.first); stateToVariableMap[stateChoicesPair.first] = solver->addUnboundedContinuousVariable(variableName); } storm::expressions::Variable lambda = solver->addUnboundedContinuousVariable("L", 1); solver->update(); // Now we encode the problem as constraints. for (auto const& stateChoicesPair : mec) { uint_fast64_t state = stateChoicesPair.first; // Now, based on the type of the state, create a suitable constraint. for (auto choice : stateChoicesPair.second) { storm::expressions::Expression constraint = -lambda; for (auto element : transitionMatrix.getRow(choice)) { constraint = constraint + stateToVariableMap.at(element.getColumn()) * solver->getConstant(element.getValue()); } typename RewardModelType::ValueType r = rewardModel.getTotalStateActionReward(state, choice, transitionMatrix); constraint = solver->getConstant(r) + constraint; if (dir == OptimizationDirection::Minimize) { constraint = stateToVariableMap.at(state) <= constraint; } else { constraint = stateToVariableMap.at(state) >= constraint; } solver->addConstraint("state" + std::to_string(state) + "," + std::to_string(choice), constraint); } } solver->optimize(); return solver->getContinuousValue(lambda); } template std::unique_ptr SparseMdpPrctlHelper::computeConditionalProbabilities(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& targetStates, storm::storage::BitVector const& conditionStates) { std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); // For the max-case, we can simply take the given target states. For the min-case, however, we need to // find the MECs of non-target states and make them the new target states. storm::storage::BitVector fixedTargetStates; if (!goal.minimize()) { fixedTargetStates = targetStates; } else { fixedTargetStates = storm::storage::BitVector(targetStates.size()); storm::storage::MaximalEndComponentDecomposition mecDecomposition(transitionMatrix, backwardTransitions, ~targetStates); for (auto const& mec : mecDecomposition) { for (auto const& stateActionsPair : mec) { fixedTargetStates.set(stateActionsPair.first); } } } storm::storage::BitVector allStates(fixedTargetStates.size(), true); // Extend the target states by computing all states that have probability 1 to go to a target state // under *all* schedulers. fixedTargetStates = storm::utility::graph::performProb1A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, allStates, fixedTargetStates); // We solve the max-case and later adjust the result if the optimization direction was to minimize. storm::storage::BitVector initialStatesBitVector = goal.relevantValues(); STORM_LOG_THROW(initialStatesBitVector.getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException, "Computing conditional probabilities in MDPs is only supported for models with exactly one initial state."); storm::storage::sparse::state_type initialState = *initialStatesBitVector.begin(); // Extend the condition states by computing all states that have probability 1 to go to a condition state // under *all* schedulers. storm::storage::BitVector extendedConditionStates = storm::utility::graph::performProb1A(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, allStates, conditionStates); STORM_LOG_DEBUG("Computing probabilities to satisfy condition."); std::chrono::high_resolution_clock::time_point conditionStart = std::chrono::high_resolution_clock::now(); std::vector conditionProbabilities = std::move(computeUntilProbabilities(env, OptimizationDirection::Maximize, transitionMatrix, backwardTransitions, allStates, extendedConditionStates, false, false).values); std::chrono::high_resolution_clock::time_point conditionEnd = std::chrono::high_resolution_clock::now(); STORM_LOG_DEBUG("Computed probabilities to satisfy for condition in " << std::chrono::duration_cast(conditionEnd - conditionStart).count() << "ms."); // If the conditional probability is undefined for the initial state, we return directly. if (storm::utility::isZero(conditionProbabilities[initialState])) { return std::unique_ptr(new ExplicitQuantitativeCheckResult(initialState, storm::utility::infinity())); } STORM_LOG_DEBUG("Computing probabilities to reach target."); std::chrono::high_resolution_clock::time_point targetStart = std::chrono::high_resolution_clock::now(); std::vector targetProbabilities = std::move(computeUntilProbabilities(env, OptimizationDirection::Maximize, transitionMatrix, backwardTransitions, allStates, fixedTargetStates, false, false).values); std::chrono::high_resolution_clock::time_point targetEnd = std::chrono::high_resolution_clock::now(); STORM_LOG_DEBUG("Computed probabilities to reach target in " << std::chrono::duration_cast(targetEnd - targetStart).count() << "ms."); storm::storage::BitVector statesWithProbabilityGreater0E(transitionMatrix.getRowGroupCount(), true); storm::storage::sparse::state_type state = 0; for (auto const& element : conditionProbabilities) { if (storm::utility::isZero(element)) { statesWithProbabilityGreater0E.set(state, false); } ++state; } // Determine those states that need to be equipped with a restart mechanism. STORM_LOG_DEBUG("Computing problematic states."); storm::storage::BitVector pureResetStates = storm::utility::graph::performProb0A(backwardTransitions, allStates, extendedConditionStates); storm::storage::BitVector problematicStates = storm::utility::graph::performProb0E(transitionMatrix, transitionMatrix.getRowGroupIndices(), backwardTransitions, allStates, extendedConditionStates | fixedTargetStates); // Otherwise, we build the transformed MDP. storm::storage::BitVector relevantStates = storm::utility::graph::getReachableStates(transitionMatrix, initialStatesBitVector, allStates, extendedConditionStates | fixedTargetStates | pureResetStates); STORM_LOG_TRACE("Found " << relevantStates.getNumberOfSetBits() << " relevant states for conditional probability computation."); std::vector numberOfStatesBeforeRelevantStates = relevantStates.getNumberOfSetBitsBeforeIndices(); storm::storage::sparse::state_type newGoalState = relevantStates.getNumberOfSetBits(); storm::storage::sparse::state_type newStopState = newGoalState + 1; storm::storage::sparse::state_type newFailState = newStopState + 1; // Build the transitions of the (relevant) states of the original model. storm::storage::SparseMatrixBuilder builder(0, newFailState + 1, 0, true, true); uint_fast64_t currentRow = 0; for (auto state : relevantStates) { builder.newRowGroup(currentRow); if (fixedTargetStates.get(state)) { if (!storm::utility::isZero(conditionProbabilities[state])) { builder.addNextValue(currentRow, newGoalState, conditionProbabilities[state]); } if (!storm::utility::isOne(conditionProbabilities[state])) { builder.addNextValue(currentRow, newFailState, storm::utility::one() - conditionProbabilities[state]); } ++currentRow; } else if (extendedConditionStates.get(state)) { if (!storm::utility::isZero(targetProbabilities[state])) { builder.addNextValue(currentRow, newGoalState, targetProbabilities[state]); } if (!storm::utility::isOne(targetProbabilities[state])) { builder.addNextValue(currentRow, newStopState, storm::utility::one() - targetProbabilities[state]); } ++currentRow; } else if (pureResetStates.get(state)) { builder.addNextValue(currentRow, numberOfStatesBeforeRelevantStates[initialState], storm::utility::one()); ++currentRow; } else { for (uint_fast64_t row = transitionMatrix.getRowGroupIndices()[state]; row < transitionMatrix.getRowGroupIndices()[state + 1]; ++row) { for (auto const& successorEntry : transitionMatrix.getRow(row)) { builder.addNextValue(currentRow, numberOfStatesBeforeRelevantStates[successorEntry.getColumn()], successorEntry.getValue()); } ++currentRow; } if (problematicStates.get(state)) { builder.addNextValue(currentRow, numberOfStatesBeforeRelevantStates[initialState], storm::utility::one()); ++currentRow; } } } // Now build the transitions of the newly introduced states. builder.newRowGroup(currentRow); builder.addNextValue(currentRow, newGoalState, storm::utility::one()); ++currentRow; builder.newRowGroup(currentRow); builder.addNextValue(currentRow, newStopState, storm::utility::one()); ++currentRow; builder.newRowGroup(currentRow); builder.addNextValue(currentRow, numberOfStatesBeforeRelevantStates[initialState], storm::utility::one()); ++currentRow; std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now(); STORM_LOG_DEBUG("Computed transformed model in " << std::chrono::duration_cast(end - start).count() << "ms."); // Finally, build the matrix and dispatch the query as a reachability query. STORM_LOG_DEBUG("Computing conditional probabilties."); storm::storage::BitVector newGoalStates(newFailState + 1); newGoalStates.set(newGoalState); storm::storage::SparseMatrix newTransitionMatrix = builder.build(); STORM_LOG_DEBUG("Transformed model has " << newTransitionMatrix.getRowGroupCount() << " states and " << newTransitionMatrix.getNonzeroEntryCount() << " transitions."); storm::storage::SparseMatrix newBackwardTransitions = newTransitionMatrix.transpose(true); storm::solver::OptimizationDirection dir = goal.direction(); if (goal.minimize()) { goal.oneMinus(); } std::chrono::high_resolution_clock::time_point conditionalStart = std::chrono::high_resolution_clock::now(); std::vector goalProbabilities = std::move(computeUntilProbabilities(env, std::move(goal), newTransitionMatrix, newBackwardTransitions, storm::storage::BitVector(newFailState + 1, true), newGoalStates, false, false).values); std::chrono::high_resolution_clock::time_point conditionalEnd = std::chrono::high_resolution_clock::now(); STORM_LOG_DEBUG("Computed conditional probabilities in transformed model in " << std::chrono::duration_cast(conditionalEnd - conditionalStart).count() << "ms."); return std::unique_ptr(new ExplicitQuantitativeCheckResult(initialState, dir == OptimizationDirection::Maximize ? goalProbabilities[numberOfStatesBeforeRelevantStates[initialState]] : storm::utility::one() - goalProbabilities[numberOfStatesBeforeRelevantStates[initialState]])); } template class SparseMdpPrctlHelper; template std::vector SparseMdpPrctlHelper::computeInstantaneousRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::models::sparse::StandardRewardModel const& rewardModel, uint_fast64_t stepCount); template std::vector SparseMdpPrctlHelper::computeCumulativeRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::models::sparse::StandardRewardModel const& rewardModel, uint_fast64_t stepBound); template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeReachabilityRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::BitVector const& targetStates, bool qualitative, bool produceScheduler, ModelCheckerHint const& hint); template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeTotalRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::models::sparse::StandardRewardModel const& rewardModel, bool qualitative, bool produceScheduler, ModelCheckerHint const& hint); template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeLongRunAverageRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::models::sparse::StandardRewardModel const& rewardModel, bool produceScheduler); template double SparseMdpPrctlHelper::computeLraForMaximalEndComponent(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec, std::unique_ptr>& scheduler); template double SparseMdpPrctlHelper::computeLraForMaximalEndComponentVI(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec, std::unique_ptr>& scheduler); template double SparseMdpPrctlHelper::computeLraForMaximalEndComponentLP(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec); #ifdef STORM_HAVE_CARL template class SparseMdpPrctlHelper; template std::vector SparseMdpPrctlHelper::computeInstantaneousRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::models::sparse::StandardRewardModel const& rewardModel, uint_fast64_t stepCount); template std::vector SparseMdpPrctlHelper::computeCumulativeRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::models::sparse::StandardRewardModel const& rewardModel, uint_fast64_t stepBound); template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeReachabilityRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::BitVector const& targetStates, bool qualitative, bool produceScheduler, ModelCheckerHint const& hint); template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeTotalRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::models::sparse::StandardRewardModel const& rewardModel, bool qualitative, bool produceScheduler, ModelCheckerHint const& hint); template MDPSparseModelCheckingHelperReturnType SparseMdpPrctlHelper::computeLongRunAverageRewards(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::models::sparse::StandardRewardModel const& rewardModel, bool produceScheduler); template storm::RationalNumber SparseMdpPrctlHelper::computeLraForMaximalEndComponent(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec, std::unique_ptr>& scheduler); template storm::RationalNumber SparseMdpPrctlHelper::computeLraForMaximalEndComponentVI(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec, std::unique_ptr>& scheduler); template storm::RationalNumber SparseMdpPrctlHelper::computeLraForMaximalEndComponentLP(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec); #endif } } }