#include "storm/modelchecker/csl/helper/SparseMarkovAutomatonCslHelper.h" #include "storm/modelchecker/prctl/helper/SparseMdpPrctlHelper.h" #include "storm/models/sparse/StandardRewardModel.h" #include "storm/storage/StronglyConnectedComponentDecomposition.h" #include "storm/storage/MaximalEndComponentDecomposition.h" #include "storm/settings/SettingsManager.h" #include "storm/settings/modules/GeneralSettings.h" #include "storm/settings/modules/MinMaxEquationSolverSettings.h" #include "storm/environment/Environment.h" #include "storm/environment/solver/MinMaxSolverEnvironment.h" #include "storm/environment/solver/TopologicalSolverEnvironment.h" #include "storm/environment/solver/LongRunAverageSolverEnvironment.h" #include "storm/environment/solver/EigenSolverEnvironment.h" #include "storm/environment/solver/TimeBoundedSolverEnvironment.h" #include "storm/utility/macros.h" #include "storm/utility/vector.h" #include "storm/utility/graph.h" #include "storm/utility/NumberTraits.h" #include "storm/storage/expressions/Variable.h" #include "storm/storage/expressions/Expression.h" #include "storm/storage/expressions/ExpressionManager.h" #include "storm/solver/Multiplier.h" #include "storm/solver/MinMaxLinearEquationSolver.h" #include "storm/solver/LpSolver.h" #include "storm/exceptions/InvalidStateException.h" #include "storm/exceptions/InvalidPropertyException.h" #include "storm/exceptions/InvalidOperationException.h" #include "storm/exceptions/UncheckedRequirementException.h" namespace storm { namespace modelchecker { namespace helper { template class UnifPlusHelper { public: UnifPlusHelper(storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates) : transitionMatrix(transitionMatrix), exitRateVector(exitRateVector), markovianStates(markovianStates) { // Intentionally left empty } std::vector computeBoundedUntilProbabilities(storm::Environment const& env, OptimizationDirection dir, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, ValueType const& upperTimeBound) { // Since there is no lower time bound, we can treat the psiStates as if they are absorbing. // Compute some important subsets of states storm::storage::BitVector maybeStates = ~(getProb0States(dir, phiStates, psiStates) | psiStates); storm::storage::BitVector markovianMaybeStates = markovianStates & maybeStates; storm::storage::BitVector probabilisticMaybeStates = ~markovianStates & maybeStates; // Catch the case where this is query can be solved by solving the untimed variant instead. // This is the case if there is no Markovian maybe state (e.g. if the initial state is already a psi state) of if the time bound is infinity. if (markovianMaybeStates.empty() || storm::utility::isInfinity(upperTimeBound)) { return SparseMarkovAutomatonCslHelper::computeUntilProbabilities(env, dir, transitionMatrix, transitionMatrix.transpose(true), phiStates, psiStates, false, false).values; } // Split the transitions into various part // Transitions from Markovian maybe states to all other maybe states. Insert Diagonal entries to apply uniformization later. storm::storage::SparseMatrix markovianToMaybeTransitions = transitionMatrix.getSubmatrix(true, markovianMaybeStates, maybeStates, true); // The probabilities to go from a Markovian state to a psi state in one step std::vector> markovianToPsiProbabilities = getSparseOneStepProbabilities(markovianMaybeStates, psiStates); // Transitions from probabilistic maybe states to probabilistic maybe states. storm::storage::SparseMatrix probabilisticToProbabilisticTransitions = transitionMatrix.getSubmatrix(true, probabilisticMaybeStates, probabilisticMaybeStates, false); // Transitions from probabilistic maybe states to Markovian maybe states. storm::storage::SparseMatrix probabilisticToMarkovianTransitions = transitionMatrix.getSubmatrix(true, probabilisticMaybeStates, markovianMaybeStates, false); // The probabilities to go from a probabilistic state to a psi state in one step std::vector> probabilisticToPsiProbabilities = getSparseOneStepProbabilities(probabilisticMaybeStates, psiStates); // Get the exit rates restricted to only markovian maybe states. std::vector markovianExitRates = storm::utility::vector::filterVector(exitRateVector, markovianMaybeStates); // Obtain parameters of the algorithm // Truncation error ValueType kappa = storm::utility::convertNumber(env.solver().timeBounded().getUnifPlusKappa()); // Precision to be achieved ValueType epsilon = storm::utility::convertNumber(env.solver().timeBounded().getPrecision()); bool relativePrecision = env.solver().timeBounded().getRelativeTerminationCriterion(); // Uniformization rate ValueType lambda = *std::max_element(markovianExitRates.begin(), markovianExitRates.end()); STORM_LOG_DEBUG("Initial lambda is " << lambda << "."); // Uniformize the Markovian transitions for the first time uniformize(markovianToMaybeTransitions, markovianToPsiProbabilities, markovianExitRates, lambda); // Set up a solver for the transitions between probabilistic states (if there are some) auto solver = setUpProbabilisticStatesSolver(env, dir, probabilisticToProbabilisticTransitions); // Allocate auxiliary memory that can be used during the iterations std::vector maybeStatesValuesLower(maybeStates.getNumberOfSetBits(), storm::utility::zero()); // should be zero initially std::vector maybeStatesValuesWeightedUpper(maybeStates.getNumberOfSetBits(), storm::utility::zero()); // should be zero initially std::vector maybeStatesValuesUpper(maybeStates.getNumberOfSetBits(), storm::utility::zero()); // should be zero initially std::vector nextMarkovianStateValues = std::move(markovianExitRates); // At this point, the markovianExitRates are no longer needed, so we 'move' them away instead of allocating new memory std::vector nextProbabilisticStateValues(probabilisticToProbabilisticTransitions.getRowGroupCount()); std::vector eqSysRhs(probabilisticToProbabilisticTransitions.getRowCount()); // Start the outer iterations which increase the uniformization rate until lower and upper bound on the result vector is sufficiently small storm::utility::ProgressMeasurement progressIterations("iterations"); uint64_t iteration = 0; progressIterations.startNewMeasurement(iteration); bool converged = false; while (!converged) { // Maximal step size uint64_t N = storm::utility::ceil(lambda * upperTimeBound * std::exp(2) - storm::utility::log(kappa * epsilon)); // Compute poisson distribution. // The division by 8 is similar to what is done for CTMCs (probably to reduce numerical impacts?) auto foxGlynnResult = storm::utility::numerical::foxGlynn(lambda * upperTimeBound, epsilon * kappa / storm::utility::convertNumber(8.0)); // Scale the weights so they sum to one. storm::utility::vector::scaleVectorInPlace(foxGlynnResult.weights, storm::utility::one() / foxGlynnResult.totalWeight); // Set up multiplier auto markovianToMaybeMultiplier = storm::solver::MultiplierFactory().create(env, markovianToMaybeTransitions); auto probabilisticToMarkovianMultiplier = storm::solver::MultiplierFactory().create(env, probabilisticToMarkovianTransitions); //Perform inner iterations // Iteration k = N will be performed by implicitly assuming value 0 for all states. STORM_LOG_ASSERT(!storm::utility::vector::hasNonZeroEntry(maybeStatesValuesUpper), "Current values need to be initialized with zero."); // Iterations k < N for (bool computeLowerBound : {false, true}) { ValueType targetValue = computeLowerBound ? storm::utility::zero() : storm::utility::one(); storm::utility::ProgressMeasurement progressSteps("steps in iteration " + std::to_string(iteration) + " for " + std::string(computeLowerBound ? "lower" : "upper") + " bounds."); progressSteps.setMaxCount(N); progressSteps.startNewMeasurement(0); for (int64_t k = N - 1; k >= 0; --k) { auto& maybeStatesValues = computeLowerBound ? maybeStatesValuesLower : maybeStatesValuesWeightedUpper; // Compute the values at Markovian maybe states. if (static_cast(k) == N - 1) { // If we are in the very first (inner) iteration, we have to set set all values to zero, since we are in the 'last' time epoch before the bound is exceeded. std::fill(nextMarkovianStateValues.begin(), nextMarkovianStateValues.end(), storm::utility::zero()); } else { markovianToMaybeMultiplier->multiply(env, maybeStatesValues, nullptr, nextMarkovianStateValues); for (auto const& oneStepProb : markovianToPsiProbabilities) { nextMarkovianStateValues[oneStepProb.first] += oneStepProb.second * targetValue; } } // Update the value when reaching a psi state. // This has to be done after updating the Markovian state values since we needed the 'old' target value above. if (computeLowerBound && static_cast(k) >= foxGlynnResult.left && static_cast(k) <=foxGlynnResult.right) { targetValue += foxGlynnResult.weights[k - foxGlynnResult.left]; } // Compute the values at probabilistic states. probabilisticToMarkovianMultiplier->multiply(env, nextMarkovianStateValues, nullptr, eqSysRhs); for (auto const& oneStepProb : probabilisticToPsiProbabilities) { eqSysRhs[oneStepProb.first] += oneStepProb.second * targetValue; } if (solver) { solver->solveEquations(env, dir, nextProbabilisticStateValues, eqSysRhs); } else { storm::utility::vector::reduceVectorMinOrMax(dir, eqSysRhs, nextMarkovianStateValues, probabilisticToProbabilisticTransitions.getRowGroupIndices()); } // Create the new values for the maybestates // Fuse the results together storm::utility::vector::setVectorValues(maybeStatesValues, markovianMaybeStates, nextMarkovianStateValues); storm::utility::vector::setVectorValues(maybeStatesValues, probabilisticMaybeStates, nextProbabilisticStateValues); if (!computeLowerBound) { // Add the scaled values to the actual result vector uint64_t i = N-1-k; if (i >= foxGlynnResult.left && i <= foxGlynnResult.right) { ValueType const& weight = foxGlynnResult.weights[i - foxGlynnResult.left]; storm::utility::vector::addScaledVector(maybeStatesValuesUpper, maybeStatesValuesWeightedUpper, weight); } } progressSteps.updateProgress(N-k); } // Check if the lower and upper bound are sufficiently close to each other // TODO: apply this only to relevant values? converged = checkConvergence(maybeStatesValuesLower, maybeStatesValuesUpper, boost::none, epsilon, relativePrecision, kappa); if (converged) { break; } } if (!converged) { // Increase the uniformization rate and prepare the next run // Double lambda. ValueType oldLambda = lambda; lambda *= storm::utility::convertNumber(2.0); STORM_LOG_DEBUG("Increased lambda to " << lambda << "."); if (relativePrecision) { // Reduce kappa a bit ValueType minValue = *std::min_element(maybeStatesValuesUpper.begin(), maybeStatesValuesUpper.end()); kappa *= std::max(storm::utility::convertNumber("1/10"), minValue); } // Apply uniformization with new rate uniformize(markovianToMaybeTransitions, markovianToPsiProbabilities, oldLambda, lambda); // Reset the values of the maybe states to zero. std::fill(maybeStatesValuesUpper.begin(), maybeStatesValuesUpper.end(), storm::utility::zero()); } progressIterations.updateProgress(++iteration); } // We take the average of the lower and upper bounds auto two = storm::utility::convertNumber(2.0); storm::utility::vector::applyPointwise(maybeStatesValuesLower, maybeStatesValuesUpper, maybeStatesValuesLower, [&two] (ValueType const& a, ValueType const& b) -> ValueType { return (a + b) / two; }); std::vector result(transitionMatrix.getRowGroupCount(), storm::utility::zero()); storm::utility::vector::setVectorValues(result, psiStates, storm::utility::one()); storm::utility::vector::setVectorValues(result, maybeStates, maybeStatesValuesLower); return result; } private: bool checkConvergence(std::vector const& lower, std::vector const& upper, boost::optional const& relevantValues, ValueType const& epsilon, bool relative, ValueType& kappa) { if (!relative) { if (relevantValues) { return storm::utility::vector::equalModuloPrecision(lower, upper, relevantValues.get(), epsilon * (storm::utility::one() - kappa), false); } else { return storm::utility::vector::equalModuloPrecision(lower, upper, epsilon * (storm::utility::one() - kappa), false); } } ValueType truncationError = epsilon * kappa; ValueType twoTimestruncationError = storm::utility::convertNumber(2.0) * truncationError; for (uint64_t i = 0; i < lower.size(); ++i) { if (lower[i] == upper[i]) { continue; } if (lower[i] <= truncationError) { return false; } ValueType absDiff = upper[i] - lower[i] + twoTimestruncationError; ValueType relDiff = absDiff / (lower[i] - truncationError); if (relDiff > epsilon) { return false; } STORM_LOG_ASSERT(absDiff > storm::utility::zero(), "Upper bound " << upper[i] << " is smaller than lower bound " << lower[i] << "."); } return true; } void uniformize(storm::storage::SparseMatrix& matrix, std::vector>& oneSteps, std::vector const& oldRates, ValueType uniformizationRate) { for (uint64_t row = 0; row < matrix.getRowCount(); ++row) { ValueType const& oldExitRate = oldRates[row]; if (oldExitRate == uniformizationRate) { // Already uniformized. continue; } for (auto& v : matrix.getRow(row)) { if (v.getColumn() == row) { ValueType newSelfLoop = uniformizationRate - oldExitRate + v.getValue() * oldExitRate; v.setValue(newSelfLoop / uniformizationRate); } else { v.setValue(v.getValue() * oldExitRate / uniformizationRate); } } } for (auto& oneStep : oneSteps) { oneStep.second *= oldRates[oneStep.first] / uniformizationRate; } } void uniformize(storm::storage::SparseMatrix& matrix, std::vector>& oneSteps, ValueType oldUniformizationRate, ValueType newUniformizationRate) { if (oldUniformizationRate != newUniformizationRate) { assert(oldUniformizationRate < newUniformizationRate); ValueType rateDiff = newUniformizationRate - oldUniformizationRate; ValueType rateFraction = oldUniformizationRate / newUniformizationRate; for (uint64_t row = 0; row < matrix.getRowCount(); ++row) { for (auto& v : matrix.getRow(row)) { if (v.getColumn() == row) { ValueType newSelfLoop = rateDiff + v.getValue() * oldUniformizationRate; v.setValue(newSelfLoop / newUniformizationRate); } else { v.setValue(v.getValue() * rateFraction); } } } for (auto& oneStep : oneSteps) { oneStep.second *= rateFraction; } } } std::unique_ptr> setUpProbabilisticStatesSolver(storm::Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitions) const { std::unique_ptr> solver; if (transitions.getNonzeroEntryCount() > 0) { storm::solver::GeneralMinMaxLinearEquationSolverFactory factory; solver = factory.create(env, transitions); solver->setHasUniqueSolution(true); // Assume non-zeno MA solver->setHasNoEndComponents(true); // assume non-zeno MA solver->setLowerBound(storm::utility::zero()); solver->setUpperBound(storm::utility::one()); solver->setCachingEnabled(true); solver->setRequirementsChecked(true); auto req = solver->getRequirements(env, dir); req.clearBounds(); req.clearUniqueSolution(); STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException, "The solver requirement " << req.getEnabledRequirementsAsString() << " has not been checked."); } return solver; } storm::storage::BitVector getProb0States(OptimizationDirection dir, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) const { if (dir == storm::solver::OptimizationDirection::Maximize) { return storm::utility::graph::performProb0A(transitionMatrix.transpose(true), phiStates, psiStates); } else { return storm::utility::graph::performProb0E(transitionMatrix, transitionMatrix.getRowGroupIndices(), transitionMatrix.transpose(true), phiStates, psiStates); } } /*! * Returns a vector with pairs of state indices and non-zero probabilities to move from the corresponding state to a target state. * The state indices are with respect to the number of states satisfying the sourceStateConstraint, i.e. the indices are in the range [0, sourceStateConstraint.getNumberOfSetBits()) */ std::vector> getSparseOneStepProbabilities(storm::storage::BitVector const& sourceStateConstraint, storm::storage::BitVector const& targetStateConstraint) const { auto denseResult = transitionMatrix.getConstrainedRowGroupSumVector(sourceStateConstraint, targetStateConstraint); std::vector> sparseResult; for (uint64 i = 0; i < denseResult.size(); ++i) { auto const& val = denseResult[i]; if (!storm::utility::isZero(val)) { sparseResult.emplace_back(i, val); } } return sparseResult; } void performMarkovianStep(storm::Environment const& env, storm::storage::SparseMatrix const& markovianTransitions, std::vector> const& oneStepToGoalProbabilities, std::vector const& currentMaybeStatesValues, ValueType const& currentGoalValue) const { // Set up a multiplier for the transitions emerging at Markovian states auto multiplier = storm::solver::MultiplierFactory().create(env, markovianTransitions); } storm::storage::SparseMatrix const& transitionMatrix; std::vector const& exitRateVector; storm::storage::BitVector const& markovianStates; }; /** * Data structure holding result vectors (vLower, vUpper, wUpper) for Unif+. */ template struct UnifPlusVectors { UnifPlusVectors() { // Intentionally empty } /** * Initialize results vectors. vLowerOld, vUpperOld and wUpper[k=N] are initialized with zeros. */ UnifPlusVectors(uint64_t steps, uint64_t noStates) : numberOfStates(noStates), steps(steps), resLowerOld(numberOfStates, storm::utility::zero()), resLowerNew(numberOfStates, -1), resUpper(numberOfStates, storm::utility::zero()), wUpperOld(numberOfStates, storm::utility::zero()), wUpperNew(numberOfStates, -1) { // Intentionally left empty } /** * Prepare new iteration by setting the new result vectors as old result vectors, and initializing the new result vectors with -1 again. */ void prepareNewIteration() { resLowerOld.swap(resLowerNew); std::fill(resLowerNew.begin(), resLowerNew.end(), -1); wUpperOld.swap(wUpperNew); std::fill(wUpperNew.begin(), wUpperNew.end(), -1); } uint64_t numberOfStates; uint64_t steps; std::vector resLowerOld; std::vector resLowerNew; std::vector resUpper; std::vector wUpperOld; std::vector wUpperNew; }; template void calculateUnifPlusVector(Environment const& env, uint64_t k, uint64_t state, bool calcLower, ValueType lambda, uint64_t numberOfProbabilisticChoices, std::vector> const & relativeReachability, OptimizationDirection dir, UnifPlusVectors& unifVectors, storm::storage::SparseMatrix const& fullTransitionMatrix, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& psiStates, std::unique_ptr> const& solver, storm::utility::numerical::FoxGlynnResult const& poisson, bool cycleFree) { // Set reference to acutal vector std::vector& resVectorOld = calcLower ? unifVectors.resLowerOld : unifVectors.wUpperOld; std::vector& resVectorNew = calcLower ? unifVectors.resLowerNew : unifVectors.wUpperNew; if (resVectorNew[state] != -1) { // Result already calculated. return; } auto numberOfStates = fullTransitionMatrix.getRowGroupCount(); uint64_t N = unifVectors.steps; auto const& rowGroupIndices = fullTransitionMatrix.getRowGroupIndices(); ValueType res; // First case, k==N, independent from kind of state. if (k == N) { STORM_LOG_ASSERT(false, "Result for k=N was already calculated."); resVectorNew[state] = storm::utility::zero(); return; } // Goal state, independent from kind of state. if (psiStates[state]) { if (calcLower) { // v lower res = storm::utility::zero(); for (uint64_t i = k; i < N; ++i){ if (i >= poisson.left && i <= poisson.right) { res += poisson.weights[i - poisson.left]; } } resVectorNew[state] = res; } else { // w upper resVectorNew[state] = storm::utility::one(); } return; } // Markovian non-goal state. if (markovianStates[state]) { res = storm::utility::zero(); for (auto const& element : fullTransitionMatrix.getRow(rowGroupIndices[state])) { uint64_t successor = element.getColumn(); if (resVectorOld[successor] == -1) { STORM_LOG_ASSERT(false, "Need to calculate previous result."); calculateUnifPlusVector(env, k+1, successor, calcLower, lambda, numberOfProbabilisticChoices, relativeReachability, dir, unifVectors, fullTransitionMatrix, markovianStates, psiStates, solver, poisson, cycleFree); } res += element.getValue() * resVectorOld[successor]; } resVectorNew[state]=res; return; } // Probabilistic non-goal state. if (cycleFree) { // If the model is cycle free, do "slight value iteration". (What is that?) res = -1; for (uint64_t i = rowGroupIndices[state]; i < rowGroupIndices[state + 1]; ++i) { auto row = fullTransitionMatrix.getRow(i); ValueType between = storm::utility::zero(); for (auto const& element : row) { uint64_t successor = element.getColumn(); // This should never happen, right? The model has no cycles, and therefore also no self-loops. if (successor == state) { continue; } if (resVectorNew[successor] == -1) { calculateUnifPlusVector(env, k, successor, calcLower, lambda, numberOfProbabilisticChoices, relativeReachability, dir, unifVectors, fullTransitionMatrix, markovianStates, psiStates, solver, poisson, cycleFree); } between += element.getValue() * resVectorNew[successor]; } if (maximize(dir)) { res = storm::utility::max(res, between); } else { if (res != -1) { res = storm::utility::min(res, between); } else { res = between; } } } resVectorNew[state] = res; return; } // If we arrived at this point, the model is not cycle free. Use the solver to solve the underlying equation system. uint64_t numberOfProbabilisticStates = numberOfStates - markovianStates.getNumberOfSetBits(); std::vector b(numberOfProbabilisticChoices, storm::utility::zero()); std::vector x(numberOfProbabilisticStates, storm::utility::zero()); // Compute right-hand side vector b. uint64_t row = 0; for (uint64_t i = 0; i < numberOfStates; ++i) { if (markovianStates[i]) { continue; } for (auto j = rowGroupIndices[i]; j < rowGroupIndices[i + 1]; j++) { uint64_t stateCount = 0; res = storm::utility::zero(); for (auto const& element : fullTransitionMatrix.getRow(j)) { auto successor = element.getColumn(); if (!markovianStates[successor]) { continue; } if (resVectorNew[successor] == -1) { calculateUnifPlusVector(env, k, successor, calcLower, lambda, numberOfProbabilisticChoices, relativeReachability, dir, unifVectors, fullTransitionMatrix, markovianStates, psiStates, solver, poisson, cycleFree); } res += relativeReachability[j][stateCount] * resVectorNew[successor]; ++stateCount; } b[row] = res; ++row; } } // Solve the equation system. solver->solveEquations(env, dir, x, b); // Expand the solution for the probabilistic states to all states. storm::utility::vector::setVectorValues(resVectorNew, ~markovianStates, x); } template void eliminateProbabilisticSelfLoops(storm::storage::SparseMatrix& transitionMatrix, storm::storage::BitVector const& markovianStates) { auto const& rowGroupIndices = transitionMatrix.getRowGroupIndices(); for (uint64_t i = 0; i < transitionMatrix.getRowGroupCount(); ++i) { if (markovianStates[i]) { continue; } for (uint64_t j = rowGroupIndices[i]; j < rowGroupIndices[i + 1]; j++) { ValueType selfLoop = storm::utility::zero(); for (auto const& element: transitionMatrix.getRow(j)){ if (element.getColumn() == i) { selfLoop += element.getValue(); } } if (storm::utility::isZero(selfLoop)) { continue; } for (auto& element : transitionMatrix.getRow(j)) { if (element.getColumn() != i) { if (!storm::utility::isOne(selfLoop)) { element.setValue(element.getValue() / (storm::utility::one() - selfLoop)); } } else { element.setValue(storm::utility::zero()); } } } } } template std::vector computeBoundedUntilProbabilitiesUnifPlus(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& psiStates, std::pair const& boundsPair) { STORM_LOG_TRACE("Using UnifPlus to compute bounded until probabilities."); // Obtain bit vectors to identify different kind of states. storm::storage::BitVector allStates(markovianStates.size(), true); storm::storage::BitVector probabilisticStates = ~markovianStates; // Searching for SCCs in probabilistic fragment to decide which algorithm is applied. bool cycleFree = !storm::utility::graph::hasCycle(transitionMatrix, probabilisticStates); // Vectors to store computed vectors. UnifPlusVectors unifVectors; // Transitions from goal states will be ignored. However, we mark them as non-probabilistic to make sure // we do not apply the MDP algorithm to them. storm::storage::BitVector markovianAndGoalStates = markovianStates | psiStates; probabilisticStates &= ~psiStates; std::vector mutableExitRates = exitRateVector; // Extend the transition matrix with diagonal entries so we can change them easily during the uniformization step. typename storm::storage::SparseMatrix fullTransitionMatrix = transitionMatrix.getSubmatrix(true, allStates, allStates, true); // Eliminate self-loops of probabilistic states. Is this really needed for the "slight value iteration" process? eliminateProbabilisticSelfLoops(fullTransitionMatrix, markovianAndGoalStates); typename storm::storage::SparseMatrix probMatrix; uint64_t numberOfProbabilisticChoices = 0; if (!probabilisticStates.empty()) { probMatrix = fullTransitionMatrix.getSubmatrix(true, probabilisticStates, probabilisticStates, true); numberOfProbabilisticChoices = probMatrix.getRowCount(); } // Get row grouping of transition matrix. auto const& rowGroupIndices = fullTransitionMatrix.getRowGroupIndices(); // (1) define/declare horizon, epsilon, kappa, N, lambda, maxNorm uint64_t numberOfStates = fullTransitionMatrix.getRowGroupCount(); // 'Unpack' the bounds to make them more easily accessible. double lowerBound = boundsPair.first; double upperBound = boundsPair.second; // Lower bound > 0 is not implemented! STORM_LOG_THROW(lowerBound == 0, storm::exceptions::NotImplementedException, "Support for lower bound > 0 not implemented in Unif+."); // Truncation error // TODO: make kappa a parameter. ValueType kappa = storm::utility::one() / 10; // Approximation error ValueType epsilon = storm::settings::getModule().getPrecision(); // Lambda is largest exit rate ValueType lambda = exitRateVector[0]; for (ValueType const& rate : exitRateVector) { lambda = std::max(rate, lambda); } STORM_LOG_DEBUG("Initial lambda is " << lambda << "."); // Compute the relative reachability vectors and create solver for models with SCCs. std::vector> relativeReachabilities(transitionMatrix.getRowCount()); std::unique_ptr> solver; if (!cycleFree) { for (uint64_t i = 0; i < numberOfStates; i++) { if (markovianAndGoalStates[i]) { continue; } for (auto j = rowGroupIndices[i]; j < rowGroupIndices[i + 1]; ++j) { for (auto const& element : fullTransitionMatrix.getRow(j)) { if (markovianAndGoalStates[element.getColumn()]) { relativeReachabilities[j].push_back(element.getValue()); } } } } // Create solver. storm::solver::GeneralMinMaxLinearEquationSolverFactory minMaxLinearEquationSolverFactory; storm::solver::MinMaxLinearEquationSolverRequirements requirements = minMaxLinearEquationSolverFactory.getRequirements(env, true, true, dir); requirements.clearBounds(); STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException, "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked."); if (numberOfProbabilisticChoices > 0) { solver = minMaxLinearEquationSolverFactory.create(env, probMatrix); solver->setHasUniqueSolution(); solver->setHasNoEndComponents(); solver->setBounds(storm::utility::zero(), storm::utility::one()); solver->setRequirementsChecked(); solver->setCachingEnabled(true); } } ValueType maxNorm = storm::utility::zero(); // Maximal step size uint64_t N; storm::utility::ProgressMeasurement progressIterations("iterations"); size_t iteration = 0; progressIterations.startNewMeasurement(iteration); // Loop until result is within precision bound. do { // (2) update parameter N = storm::utility::ceil(lambda * upperBound * std::exp(2) - storm::utility::log(kappa * epsilon)); // (3) uniform - just applied to Markovian states. for (uint64_t i = 0; i < numberOfStates; i++) { if (!markovianAndGoalStates[i] || psiStates[i]) { continue; } // As the current state is Markovian, its branching probabilities are stored within one row. uint64_t markovianRowIndex = rowGroupIndices[i]; if (mutableExitRates[i] == lambda) { // Already uniformized. continue; } auto markovianRow = fullTransitionMatrix.getRow(markovianRowIndex); ValueType oldExitRate = mutableExitRates[i]; ValueType newExitRate = lambda; for (auto& v : markovianRow) { if (v.getColumn() == i) { ValueType newSelfLoop = newExitRate - oldExitRate + v.getValue() * oldExitRate; ValueType newRate = newSelfLoop / newExitRate; v.setValue(newRate); } else { ValueType oldProbability = v.getValue(); ValueType newProbability = oldProbability * oldExitRate / newExitRate; v.setValue(newProbability); } } mutableExitRates[i] = newExitRate; } // Compute poisson distribution. storm::utility::numerical::FoxGlynnResult foxGlynnResult = storm::utility::numerical::foxGlynn(lambda * upperBound, epsilon * kappa / 100); // Scale the weights so they sum to one. for (auto& element : foxGlynnResult.weights) { element /= foxGlynnResult.totalWeight; } // (4) Define vectors/matrices. // Initialize result vectors and already insert zeros for iteration N unifVectors = UnifPlusVectors(N, numberOfStates); // (5) Compute vectors and maxNorm. // Iteration k = N was already performed by initializing with zeros. // Iterations k < N storm::utility::ProgressMeasurement progressSteps("steps in iteration " + std::to_string(iteration)); progressSteps.setMaxCount(N); progressSteps.startNewMeasurement(0); for (int64_t k = N-1; k >= 0; --k) { if (k < (int64_t)(N-1)) { unifVectors.prepareNewIteration(); } for (uint64_t state = 0; state < numberOfStates; ++state) { // Calculate results for lower bound and wUpper calculateUnifPlusVector(env, k, state, true, lambda, numberOfProbabilisticChoices, relativeReachabilities, dir, unifVectors, fullTransitionMatrix, markovianAndGoalStates, psiStates, solver, foxGlynnResult, cycleFree); calculateUnifPlusVector(env, k, state, false, lambda, numberOfProbabilisticChoices, relativeReachabilities, dir, unifVectors, fullTransitionMatrix, markovianAndGoalStates, psiStates, solver, foxGlynnResult, cycleFree); // Calculate result for upper bound uint64_t index = N-1-k; if (index >= foxGlynnResult.left && index <= foxGlynnResult.right) { STORM_LOG_ASSERT(unifVectors.wUpperNew[state] != -1, "wUpper was not computed before."); unifVectors.resUpper[state] += foxGlynnResult.weights[index - foxGlynnResult.left] * unifVectors.wUpperNew[state]; } } progressSteps.updateProgress(N-k); } // Only iterate over result vector, as the results can only get more precise. maxNorm = storm::utility::zero(); for (uint64_t i = 0; i < numberOfStates; i++){ ValueType diff = storm::utility::abs(unifVectors.resUpper[i] - unifVectors.resLowerNew[i]); maxNorm = std::max(maxNorm, diff); } // (6) Double lambda. lambda *= 2; STORM_LOG_DEBUG("Increased lambda to " << lambda << ", max diff is " << maxNorm << "."); progressIterations.updateProgress(++iteration); } while (maxNorm > epsilon * (1 - kappa)); return unifVectors.resLowerNew; } template void computeBoundedReachabilityProbabilitiesImca(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRates, storm::storage::BitVector const& goalStates, storm::storage::BitVector const& markovianNonGoalStates, storm::storage::BitVector const& probabilisticNonGoalStates, std::vector& markovianNonGoalValues, std::vector& probabilisticNonGoalValues, ValueType delta, uint64_t numberOfSteps) { // Start by computing four sparse matrices: // * a matrix aMarkovian with all (discretized) transitions from Markovian non-goal states to all Markovian non-goal states. // * a matrix aMarkovianToProbabilistic with all (discretized) transitions from Markovian non-goal states to all probabilistic non-goal states. // * a matrix aProbabilistic with all (non-discretized) transitions from probabilistic non-goal states to other probabilistic non-goal states. // * a matrix aProbabilisticToMarkovian with all (non-discretized) transitions from probabilistic non-goal states to all Markovian non-goal states. typename storm::storage::SparseMatrix aMarkovian = transitionMatrix.getSubmatrix(true, markovianNonGoalStates, markovianNonGoalStates, true); bool existProbabilisticStates = !probabilisticNonGoalStates.empty(); typename storm::storage::SparseMatrix aMarkovianToProbabilistic; typename storm::storage::SparseMatrix aProbabilistic; typename storm::storage::SparseMatrix aProbabilisticToMarkovian; if (existProbabilisticStates) { aMarkovianToProbabilistic = transitionMatrix.getSubmatrix(true, markovianNonGoalStates, probabilisticNonGoalStates); aProbabilistic = transitionMatrix.getSubmatrix(true, probabilisticNonGoalStates, probabilisticNonGoalStates); aProbabilisticToMarkovian = transitionMatrix.getSubmatrix(true, probabilisticNonGoalStates, markovianNonGoalStates); } // The matrices with transitions from Markovian states need to be digitized. // Digitize aMarkovian. Based on whether the transition is a self-loop or not, we apply the two digitization rules. uint64_t rowIndex = 0; for (auto state : markovianNonGoalStates) { for (auto& element : aMarkovian.getRow(rowIndex)) { ValueType eTerm = std::exp(-exitRates[state] * delta); if (element.getColumn() == rowIndex) { element.setValue((storm::utility::one() - eTerm) * element.getValue() + eTerm); } else { element.setValue((storm::utility::one() - eTerm) * element.getValue()); } } ++rowIndex; } // Digitize aMarkovianToProbabilistic. As there are no self-loops in this case, we only need to apply the digitization formula for regular successors. if (existProbabilisticStates) { rowIndex = 0; for (auto state : markovianNonGoalStates) { for (auto& element : aMarkovianToProbabilistic.getRow(rowIndex)) { element.setValue((1 - std::exp(-exitRates[state] * delta)) * element.getValue()); } ++rowIndex; } } // Initialize the two vectors that hold the variable one-step probabilities to all target states for probabilistic and Markovian (non-goal) states. std::vector bProbabilistic(existProbabilisticStates ? aProbabilistic.getRowCount() : 0); std::vector bMarkovian(markovianNonGoalStates.getNumberOfSetBits()); // Compute the two fixed right-hand side vectors, one for Markovian states and one for the probabilistic ones. std::vector bProbabilisticFixed; if (existProbabilisticStates) { bProbabilisticFixed = transitionMatrix.getConstrainedRowGroupSumVector(probabilisticNonGoalStates, goalStates); } std::vector bMarkovianFixed; bMarkovianFixed.reserve(markovianNonGoalStates.getNumberOfSetBits()); for (auto state : markovianNonGoalStates) { bMarkovianFixed.push_back(storm::utility::zero()); for (auto& element : transitionMatrix.getRowGroup(state)) { if (goalStates.get(element.getColumn())) { bMarkovianFixed.back() += (1 - std::exp(-exitRates[state] * delta)) * element.getValue(); } } } // Check for requirements of the solver. // The min-max system has no end components as we assume non-zeno MAs. storm::solver::GeneralMinMaxLinearEquationSolverFactory minMaxLinearEquationSolverFactory; storm::solver::MinMaxLinearEquationSolverRequirements requirements = minMaxLinearEquationSolverFactory.getRequirements(env, true, true, dir); requirements.clearBounds(); STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException, "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked."); std::unique_ptr> solver = minMaxLinearEquationSolverFactory.create(env, aProbabilistic); solver->setHasUniqueSolution(); solver->setHasNoEndComponents(); solver->setBounds(storm::utility::zero(), storm::utility::one()); solver->setRequirementsChecked(); solver->setCachingEnabled(true); // Perform the actual value iteration // * loop until the step bound has been reached // * in the loop: // * perform value iteration using A_PSwG, v_PS and the vector b where b = (A * 1_G)|PS + A_PStoMS * v_MS // and 1_G being the characteristic vector for all goal states. // * perform one timed-step using v_MS := A_MSwG * v_MS + A_MStoPS * v_PS + (A * 1_G)|MS std::vector markovianNonGoalValuesSwap(markovianNonGoalValues); for (uint64_t currentStep = 0; currentStep < numberOfSteps; ++currentStep) { if (existProbabilisticStates) { // Start by (re-)computing bProbabilistic = bProbabilisticFixed + aProbabilisticToMarkovian * vMarkovian. aProbabilisticToMarkovian.multiplyWithVector(markovianNonGoalValues, bProbabilistic); storm::utility::vector::addVectors(bProbabilistic, bProbabilisticFixed, bProbabilistic); // Now perform the inner value iteration for probabilistic states. solver->solveEquations(env, dir, probabilisticNonGoalValues, bProbabilistic); // (Re-)compute bMarkovian = bMarkovianFixed + aMarkovianToProbabilistic * vProbabilistic. aMarkovianToProbabilistic.multiplyWithVector(probabilisticNonGoalValues, bMarkovian); storm::utility::vector::addVectors(bMarkovian, bMarkovianFixed, bMarkovian); } aMarkovian.multiplyWithVector(markovianNonGoalValues, markovianNonGoalValuesSwap); std::swap(markovianNonGoalValues, markovianNonGoalValuesSwap); if (existProbabilisticStates) { storm::utility::vector::addVectors(markovianNonGoalValues, bMarkovian, markovianNonGoalValues); } else { storm::utility::vector::addVectors(markovianNonGoalValues, bMarkovianFixed, markovianNonGoalValues); } } if (existProbabilisticStates) { // After the loop, perform one more step of the value iteration for PS states. aProbabilisticToMarkovian.multiplyWithVector(markovianNonGoalValues, bProbabilistic); storm::utility::vector::addVectors(bProbabilistic, bProbabilisticFixed, bProbabilistic); solver->solveEquations(env, dir, probabilisticNonGoalValues, bProbabilistic); } } template std::vector computeBoundedUntilProbabilitiesImca(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& psiStates, std::pair const& boundsPair) { STORM_LOG_TRACE("Using IMCA's technique to compute bounded until probabilities."); uint64_t numberOfStates = transitionMatrix.getRowGroupCount(); // 'Unpack' the bounds to make them more easily accessible. double lowerBound = boundsPair.first; double upperBound = boundsPair.second; // (1) Compute the accuracy we need to achieve the required error bound. ValueType maxExitRate = 0; for (auto value : exitRateVector) { maxExitRate = std::max(maxExitRate, value); } ValueType delta = (2 * storm::settings::getModule().getPrecision()) / (upperBound * maxExitRate * maxExitRate); // (2) Compute the number of steps we need to make for the interval. uint64_t numberOfSteps = static_cast(std::ceil((upperBound - lowerBound) / delta)); STORM_LOG_INFO("Performing " << numberOfSteps << " iterations (delta=" << delta << ") for interval [" << lowerBound << ", " << upperBound << "]." << std::endl); // (3) Compute the non-goal states and initialize two vectors // * vProbabilistic holds the probability values of probabilistic non-goal states. // * vMarkovian holds the probability values of Markovian non-goal states. storm::storage::BitVector const& markovianNonGoalStates = markovianStates & ~psiStates; storm::storage::BitVector const& probabilisticNonGoalStates = ~markovianStates & ~psiStates; std::vector vProbabilistic(probabilisticNonGoalStates.getNumberOfSetBits()); std::vector vMarkovian(markovianNonGoalStates.getNumberOfSetBits()); computeBoundedReachabilityProbabilitiesImca(env, dir, transitionMatrix, exitRateVector, psiStates, markovianNonGoalStates, probabilisticNonGoalStates, vMarkovian, vProbabilistic, delta, numberOfSteps); // (4) If the lower bound of interval was non-zero, we need to take the current values as the starting values for a subsequent value iteration. if (lowerBound != storm::utility::zero()) { std::vector vAllProbabilistic((~markovianStates).getNumberOfSetBits()); std::vector vAllMarkovian(markovianStates.getNumberOfSetBits()); // Create the starting value vectors for the next value iteration based on the results of the previous one. storm::utility::vector::setVectorValues(vAllProbabilistic, psiStates % ~markovianStates, storm::utility::one()); storm::utility::vector::setVectorValues(vAllProbabilistic, ~psiStates % ~markovianStates, vProbabilistic); storm::utility::vector::setVectorValues(vAllMarkovian, psiStates % markovianStates, storm::utility::one()); storm::utility::vector::setVectorValues(vAllMarkovian, ~psiStates % markovianStates, vMarkovian); // Compute the number of steps to reach the target interval. numberOfSteps = static_cast(std::ceil(lowerBound / delta)); STORM_LOG_INFO("Performing " << numberOfSteps << " iterations (delta=" << delta << ") for interval [0, " << lowerBound << "]." << std::endl); // Compute the bounded reachability for interval [0, b-a]. computeBoundedReachabilityProbabilitiesImca(env, dir, transitionMatrix, exitRateVector, storm::storage::BitVector(numberOfStates), markovianStates, ~markovianStates, vAllMarkovian, vAllProbabilistic, delta, numberOfSteps); // Create the result vector out of vAllProbabilistic and vAllMarkovian and return it. std::vector result(numberOfStates, storm::utility::zero()); storm::utility::vector::setVectorValues(result, ~markovianStates, vAllProbabilistic); storm::utility::vector::setVectorValues(result, markovianStates, vAllMarkovian); return result; } else { // Create the result vector out of 1_G, vProbabilistic and vMarkovian and return it. std::vector result(numberOfStates); storm::utility::vector::setVectorValues(result, psiStates, storm::utility::one()); storm::utility::vector::setVectorValues(result, probabilisticNonGoalStates, vProbabilistic); storm::utility::vector::setVectorValues(result, markovianNonGoalStates, vMarkovian); return result; } } template ::SupportsExponential, int>::type> std::vector SparseMarkovAutomatonCslHelper::computeBoundedUntilProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, std::pair const& boundsPair) { // Choose the applicable method auto method = env.solver().timeBounded().getMaMethod(); if (method == storm::solver::MaBoundedReachabilityMethod::Imca) { if (!phiStates.full()) { STORM_LOG_WARN("Using Unif+ method because IMCA method does not support (phi Until psi) for non-trivial phi"); method = storm::solver::MaBoundedReachabilityMethod::UnifPlus; } } else { STORM_LOG_ASSERT(method == storm::solver::MaBoundedReachabilityMethod::UnifPlus, "Unknown solution method."); if (!storm::utility::isZero(boundsPair.first)) { STORM_LOG_WARN("Using IMCA method because Unif+ does not support a lower bound > 0."); method = storm::solver::MaBoundedReachabilityMethod::Imca; } } if (method == storm::solver::MaBoundedReachabilityMethod::Imca) { return computeBoundedUntilProbabilitiesImca(env, dir, transitionMatrix, exitRateVector, markovianStates, psiStates, boundsPair); } else { UnifPlusHelper helper(transitionMatrix, exitRateVector, markovianStates); return helper.computeBoundedUntilProbabilities(env, dir, phiStates, psiStates, boundsPair.second); } } template ::SupportsExponential, int>::type> std::vector SparseMarkovAutomatonCslHelper::computeBoundedUntilProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, std::pair const& boundsPair) { STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Computing bounded until probabilities is unsupported for this value type."); } template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeUntilProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, bool qualitative, bool produceScheduler) { return storm::modelchecker::helper::SparseMdpPrctlHelper::computeUntilProbabilities(env, dir, transitionMatrix, backwardTransitions, phiStates, psiStates, qualitative, produceScheduler); } template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeTotalRewards(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, RewardModelType const& rewardModel, bool produceScheduler) { // Get a reward model where the state rewards are scaled accordingly std::vector stateRewardWeights(transitionMatrix.getRowGroupCount(), storm::utility::zero()); for (auto const markovianState : markovianStates) { stateRewardWeights[markovianState] = storm::utility::one() / exitRateVector[markovianState]; } std::vector totalRewardVector = rewardModel.getTotalActionRewardVector(transitionMatrix, stateRewardWeights); RewardModelType scaledRewardModel(boost::none, std::move(totalRewardVector)); return SparseMdpPrctlHelper::computeTotalRewards(env, dir, transitionMatrix, backwardTransitions, scaledRewardModel, false, produceScheduler); } template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeReachabilityRewards(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, RewardModelType const& rewardModel, storm::storage::BitVector const& psiStates, bool produceScheduler) { // Get a reward model where the state rewards are scaled accordingly std::vector stateRewardWeights(transitionMatrix.getRowGroupCount(), storm::utility::zero()); for (auto const markovianState : markovianStates) { stateRewardWeights[markovianState] = storm::utility::one() / exitRateVector[markovianState]; } std::vector totalRewardVector = rewardModel.getTotalActionRewardVector(transitionMatrix, stateRewardWeights); RewardModelType scaledRewardModel(boost::none, std::move(totalRewardVector)); return SparseMdpPrctlHelper::computeReachabilityRewards(env, dir, transitionMatrix, backwardTransitions, scaledRewardModel, psiStates, false, produceScheduler); } template std::vector SparseMarkovAutomatonCslHelper::computeLongRunAverageProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& psiStates) { uint64_t numberOfStates = transitionMatrix.getRowGroupCount(); // If there are no goal states, we avoid the computation and directly return zero. if (psiStates.empty()) { return std::vector(numberOfStates, storm::utility::zero()); } // Likewise, if all bits are set, we can avoid the computation and set. if (psiStates.full()) { return std::vector(numberOfStates, storm::utility::one()); } // Otherwise, reduce the long run average probabilities to long run average rewards. // Every Markovian goal state gets reward one. std::vector stateRewards(transitionMatrix.getRowGroupCount(), storm::utility::zero()); storm::utility::vector::setVectorValues(stateRewards, markovianStates & psiStates, storm::utility::one()); storm::models::sparse::StandardRewardModel rewardModel(std::move(stateRewards)); return computeLongRunAverageRewards(env, dir, transitionMatrix, backwardTransitions, exitRateVector, markovianStates, rewardModel); } template std::vector SparseMarkovAutomatonCslHelper::computeLongRunAverageRewards(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, RewardModelType const& rewardModel) { uint64_t numberOfStates = transitionMatrix.getRowGroupCount(); // Start by decomposing the Markov automaton into its MECs. storm::storage::MaximalEndComponentDecomposition mecDecomposition(transitionMatrix, backwardTransitions); // Get some data members for convenience. std::vector const& nondeterministicChoiceIndices = transitionMatrix.getRowGroupIndices(); // Now start with compute the long-run average for all end components in isolation. std::vector lraValuesForEndComponents; // While doing so, we already gather some information for the following steps. std::vector stateToMecIndexMap(numberOfStates); storm::storage::BitVector statesInMecs(numberOfStates); 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 then the user defined precsion. underlyingSolverEnvironment.solver().minMax().setPrecision(env.solver().lra().getPrecision() / storm::utility::convertNumber(2)); underlyingSolverEnvironment.solver().minMax().setRelativeTerminationCriterion(env.solver().lra().getRelativeTerminationCriterion()); underlyingSolverEnvironment.solver().lra().setPrecision(env.solver().lra().getPrecision() / storm::utility::convertNumber(2)); } for (uint64_t currentMecIndex = 0; currentMecIndex < mecDecomposition.size(); ++currentMecIndex) { storm::storage::MaximalEndComponent const& mec = mecDecomposition[currentMecIndex]; // Gather information for later use. for (auto const& stateChoicesPair : mec) { uint64_t state = stateChoicesPair.first; statesInMecs.set(state); stateToMecIndexMap[state] = currentMecIndex; } // Compute the LRA value for the current MEC. lraValuesForEndComponents.push_back(computeLraForMaximalEndComponent(underlyingSolverEnvironment, dir, transitionMatrix, exitRateVector, markovianStates, rewardModel, mec)); } // For fast transition rewriting, we build some auxiliary data structures. storm::storage::BitVector statesNotContainedInAnyMec = ~statesInMecs; uint64_t firstAuxiliaryStateIndex = statesNotContainedInAnyMec.getNumberOfSetBits(); uint64_t lastStateNotInMecs = 0; uint64_t numberOfStatesNotInMecs = 0; std::vector statesNotInMecsBeforeIndex; statesNotInMecsBeforeIndex.reserve(numberOfStates); for (auto state : statesNotContainedInAnyMec) { while (lastStateNotInMecs <= state) { statesNotInMecsBeforeIndex.push_back(numberOfStatesNotInMecs); ++lastStateNotInMecs; } ++numberOfStatesNotInMecs; } uint64_t numberOfSspStates = numberOfStatesNotInMecs + mecDecomposition.size(); // Finally, we are ready to create the SSP matrix and right-hand side of the SSP. std::vector b; 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). uint64_t currentChoice = 0; for (auto state : statesNotContainedInAnyMec) { sspMatrixBuilder.newRowGroup(currentChoice); for (uint64_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 (uint64_t mecIndex = 0; mecIndex < auxiliaryStateToProbabilityMap.size(); ++mecIndex) { if (auxiliaryStateToProbabilityMap[mecIndex] != 0) { sspMatrixBuilder.addNextValue(currentChoice, firstAuxiliaryStateIndex + mecIndex, auxiliaryStateToProbabilityMap[mecIndex]); } } } } // Now we are ready to construct the choices for the auxiliary states. for (uint64_t mecIndex = 0; mecIndex < mecDecomposition.size(); ++mecIndex) { storm::storage::MaximalEndComponent const& mec = mecDecomposition[mecIndex]; sspMatrixBuilder.newRowGroup(currentChoice); for (auto const& stateChoicesPair : mec) { uint64_t state = stateChoicesPair.first; storm::storage::FlatSet const& choicesInMec = stateChoicesPair.second; for (uint64_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 (uint64_t targetMecIndex = 0; targetMecIndex < auxiliaryStateToProbabilityMap.size(); ++targetMecIndex) { if (auxiliaryStateToProbabilityMap[targetMecIndex] != 0) { sspMatrixBuilder.addNextValue(currentChoice, firstAuxiliaryStateIndex + targetMecIndex, auxiliaryStateToProbabilityMap[targetMecIndex]); } } ++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]); } // Finalize the matrix and solve the corresponding system of equations. storm::storage::SparseMatrix sspMatrix = sspMatrixBuilder.build(currentChoice, numberOfSspStates, numberOfSspStates); std::vector x(numberOfSspStates); // Check for requirements of the solver. storm::solver::GeneralMinMaxLinearEquationSolverFactory minMaxLinearEquationSolverFactory; storm::solver::MinMaxLinearEquationSolverRequirements requirements = minMaxLinearEquationSolverFactory.getRequirements(underlyingSolverEnvironment, true, true, dir); requirements.clearBounds(); STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException, "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked."); std::unique_ptr> solver = minMaxLinearEquationSolverFactory.create(underlyingSolverEnvironment, sspMatrix); solver->setHasUniqueSolution(); solver->setHasNoEndComponents(); solver->setLowerBound(storm::utility::zero()); solver->setUpperBound(*std::max_element(lraValuesForEndComponents.begin(), lraValuesForEndComponents.end())); solver->setRequirementsChecked(); solver->solveEquations(underlyingSolverEnvironment, dir, x, b); // Prepare result vector. std::vector result(numberOfStates); // Set the values for states not contained in MECs. storm::utility::vector::setVectorValues(result, statesNotContainedInAnyMec, x); // Set the values for all states in MECs. for (auto state : statesInMecs) { result[state] = x[firstAuxiliaryStateIndex + stateToMecIndexMap[state]]; } return result; } template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeReachabilityTimes(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& psiStates, bool produceScheduler) { // Get a reward model representing expected sojourn times std::vector rewardValues(transitionMatrix.getRowCount(), storm::utility::zero()); for (auto const markovianState : markovianStates) { rewardValues[transitionMatrix.getRowGroupIndices()[markovianState]] = storm::utility::one() / exitRateVector[markovianState]; } storm::models::sparse::StandardRewardModel rewardModel(boost::none, std::move(rewardValues)); return SparseMdpPrctlHelper::computeReachabilityRewards(env, dir, transitionMatrix, backwardTransitions, rewardModel, psiStates, false, produceScheduler); } template ValueType SparseMarkovAutomatonCslHelper::computeLraForMaximalEndComponent(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, RewardModelType const& rewardModel, storm::storage::MaximalEndComponent const& mec) { // 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; STORM_LOG_THROW(markovianStates.get(state), storm::exceptions::InvalidOperationException, "Markov Automaton has Zeno behavior. Computation of Long Run Average values not supported."); ValueType result = rewardModel.hasStateRewards() ? rewardModel.getStateReward(state) : storm::utility::zero(); if (rewardModel.hasStateActionRewards() || rewardModel.hasTransitionRewards()) { STORM_LOG_ASSERT(mec.begin()->second.size() == 1, "Markovian state has nondeterministic behavior."); uint64_t choice = *mec.begin()->second.begin(); result += exitRateVector[state] * rewardModel.getTotalStateActionReward(state, choice, transitionMatrix, storm::utility::zero()); } 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().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; } if (method == storm::solver::LraMethod::LinearProgramming) { return computeLraForMaximalEndComponentLP(env, dir, transitionMatrix, exitRateVector, markovianStates, rewardModel, mec); } else if (method == storm::solver::LraMethod::ValueIteration) { return computeLraForMaximalEndComponentVI(env, dir, transitionMatrix, exitRateVector, markovianStates, rewardModel, mec); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "Unsupported technique."); } } template ValueType SparseMarkovAutomatonCslHelper::computeLraForMaximalEndComponentLP(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, RewardModelType const& rewardModel, storm::storage::MaximalEndComponent const& mec) { std::unique_ptr> lpSolverFactory(new storm::utility::solver::LpSolverFactory()); std::unique_ptr> solver = lpSolverFactory->create("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 = "x" + std::to_string(stateChoicesPair.first); stateToVariableMap[stateChoicesPair.first] = solver->addUnboundedContinuousVariable(variableName); } storm::expressions::Variable k = solver->addUnboundedContinuousVariable("k", storm::utility::one()); solver->update(); // Now we encode the problem as constraints. std::vector const& nondeterministicChoiceIndices = transitionMatrix.getRowGroupIndices(); for (auto const& stateChoicesPair : mec) { uint64_t state = stateChoicesPair.first; // Now, based on the type of the state, create a suitable constraint. if (markovianStates.get(state)) { STORM_LOG_ASSERT(stateChoicesPair.second.size() == 1, "Markovian state " << state << " is not deterministic: It has " << stateChoicesPair.second.size() << " choices."); uint64_t choice = *stateChoicesPair.second.begin(); storm::expressions::Expression constraint = stateToVariableMap.at(state); for (auto element : transitionMatrix.getRow(nondeterministicChoiceIndices[state])) { constraint = constraint - stateToVariableMap.at(element.getColumn()) * solver->getManager().rational((element.getValue())); } constraint = constraint + solver->getManager().rational(storm::utility::one() / exitRateVector[state]) * k; storm::expressions::Expression rightHandSide = solver->getManager().rational(rewardModel.getTotalStateActionReward(state, choice, transitionMatrix, (ValueType) (storm::utility::one() / exitRateVector[state]))); if (dir == OptimizationDirection::Minimize) { constraint = constraint <= rightHandSide; } else { constraint = constraint >= rightHandSide; } solver->addConstraint("state" + std::to_string(state), constraint); } else { // For probabilistic states, we want to add the constraint x_s <= sum P(s, a, s') * x_s' where a is the current action // and the sum ranges over all states s'. for (auto choice : stateChoicesPair.second) { storm::expressions::Expression constraint = stateToVariableMap.at(state); for (auto element : transitionMatrix.getRow(choice)) { constraint = constraint - stateToVariableMap.at(element.getColumn()) * solver->getManager().rational(element.getValue()); } storm::expressions::Expression rightHandSide = solver->getManager().rational(rewardModel.getTotalStateActionReward(state, choice, transitionMatrix, storm::utility::zero())); if (dir == OptimizationDirection::Minimize) { constraint = constraint <= rightHandSide; } else { constraint = constraint >= rightHandSide; } solver->addConstraint("state" + std::to_string(state), constraint); } } } solver->optimize(); return solver->getContinuousValue(k); } template ValueType SparseMarkovAutomatonCslHelper::computeLraForMaximalEndComponentVI(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, RewardModelType const& rewardModel, storm::storage::MaximalEndComponent const& mec) { // 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); } } storm::storage::BitVector markovianMecStates = mecStates & markovianStates; storm::storage::BitVector probabilisticMecStates = mecStates & ~markovianStates; storm::storage::BitVector probabilisticMecChoices = transitionMatrix.getRowFilter(probabilisticMecStates) & mecChoices; STORM_LOG_THROW(!markovianMecStates.empty(), storm::exceptions::InvalidOperationException, "Markov Automaton has Zeno behavior. Computation of Long Run Average values not supported."); bool hasProbabilisticStates = !probabilisticMecStates.empty(); // Get the uniformization rate ValueType uniformizationRate = storm::utility::vector::max_if(exitRateVector, markovianMecStates); // To ensure that the model is aperiodic, we need to make sure that every Markovian state gets a self loop. // Hence, we increase the uniformization rate a little. uniformizationRate *= (storm::utility::one() + storm::utility::convertNumber(env.solver().lra().getAperiodicFactor())); // Get the transitions of the submodel, that is // * a matrix aMarkovian with all (uniformized) transitions from Markovian mec states to all Markovian mec states. // * a matrix aMarkovianToProbabilistic with all (uniformized) transitions from Markovian mec states to all probabilistic mec states. // * a matrix aProbabilistic with all transitions from probabilistic mec states to other probabilistic mec states. // * a matrix aProbabilisticToMarkovian with all transitions from probabilistic mec states to all Markovian mec states. typename storm::storage::SparseMatrix aMarkovian = transitionMatrix.getSubmatrix(true, markovianMecStates, markovianMecStates, true); typename storm::storage::SparseMatrix aMarkovianToProbabilistic, aProbabilistic, aProbabilisticToMarkovian; if (hasProbabilisticStates) { aMarkovianToProbabilistic = transitionMatrix.getSubmatrix(true, markovianMecStates, probabilisticMecStates); aProbabilistic = transitionMatrix.getSubmatrix(false, probabilisticMecChoices, probabilisticMecStates); aProbabilisticToMarkovian = transitionMatrix.getSubmatrix(false, probabilisticMecChoices, markovianMecStates); } // The matrices with transitions from Markovian states need to be uniformized. uint64_t subState = 0; for (auto state : markovianMecStates) { ValueType uniformizationFactor = exitRateVector[state] / uniformizationRate; if (hasProbabilisticStates) { for (auto& entry : aMarkovianToProbabilistic.getRow(subState)) { entry.setValue(entry.getValue() * uniformizationFactor); } } for (auto& entry : aMarkovian.getRow(subState)) { if (entry.getColumn() == subState) { entry.setValue(storm::utility::one() - uniformizationFactor * (storm::utility::one() - entry.getValue())); } else { entry.setValue(entry.getValue() * uniformizationFactor); } } ++subState; } // Compute the rewards obtained in a single uniformization step std::vector markovianChoiceRewards; markovianChoiceRewards.reserve(aMarkovian.getRowCount()); for (auto const& state : markovianMecStates) { ValueType stateRewardScalingFactor = storm::utility::one() / uniformizationRate; ValueType actionRewardScalingFactor = exitRateVector[state] / uniformizationRate; assert(transitionMatrix.getRowGroupSize(state) == 1); uint64_t choice = transitionMatrix.getRowGroupIndices()[state]; markovianChoiceRewards.push_back(rewardModel.getTotalStateActionReward(state, choice, transitionMatrix, stateRewardScalingFactor, actionRewardScalingFactor)); } std::vector probabilisticChoiceRewards; if (hasProbabilisticStates) { probabilisticChoiceRewards.reserve(aProbabilistic.getRowCount()); for (auto const& state : probabilisticMecStates) { uint64_t groupStart = transitionMatrix.getRowGroupIndices()[state]; uint64_t groupEnd = transitionMatrix.getRowGroupIndices()[state + 1]; for (uint64_t choice = probabilisticMecChoices.getNextSetIndex(groupStart); choice < groupEnd; choice = probabilisticMecChoices.getNextSetIndex(choice + 1)) { probabilisticChoiceRewards.push_back(rewardModel.getTotalStateActionReward(state, choice, transitionMatrix, storm::utility::zero())); } } } // start the iterations ValueType precision = storm::utility::convertNumber(env.solver().lra().getPrecision()) / uniformizationRate; bool relative = env.solver().lra().getRelativeTerminationCriterion(); std::vector v(aMarkovian.getRowCount(), storm::utility::zero()); std::vector w = v; std::vector x, b; auto solverEnv = env; std::unique_ptr> solver; if (hasProbabilisticStates) { if (env.solver().isForceSoundness()) { // To get correct results, the inner equation systems are solved exactly. // TODO investigate how an error would propagate solverEnv.solver().minMax().setMethod(storm::solver::MinMaxMethod::Topological); solverEnv.solver().topological().setUnderlyingMinMaxMethod(storm::solver::MinMaxMethod::PolicyIteration); solverEnv.solver().setLinearEquationSolverType(storm::solver::EquationSolverType::Topological); solverEnv.solver().topological().setUnderlyingEquationSolverType(storm::solver::EquationSolverType::Eigen); solverEnv.solver().eigen().setMethod(storm::solver::EigenLinearEquationSolverMethod::SparseLU); } x.resize(aProbabilistic.getRowGroupCount(), storm::utility::zero()); b = probabilisticChoiceRewards; // Check for requirements of the solver. // The solution is unique as we assume non-zeno MAs. storm::solver::GeneralMinMaxLinearEquationSolverFactory minMaxLinearEquationSolverFactory; storm::solver::MinMaxLinearEquationSolverRequirements requirements = minMaxLinearEquationSolverFactory.getRequirements(solverEnv, true, true, dir); requirements.clearLowerBounds(); STORM_LOG_THROW(!requirements.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException, "Solver requirements " + requirements.getEnabledRequirementsAsString() + " not checked."); solver = minMaxLinearEquationSolverFactory.create(solverEnv, std::move(aProbabilistic)); solver->setLowerBound(storm::utility::zero()); solver->setHasUniqueSolution(true); solver->setHasNoEndComponents(true); solver->setRequirementsChecked(true); solver->setCachingEnabled(true); } 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 expected total rewards for the probabilistic states if (hasProbabilisticStates) { solver->solveEquations(solverEnv, dir, x, b); } // now compute the values for the markovian states. We also keep track of the maximal and minimal difference between two values (for convergence checking) auto vIt = v.begin(); uint64_t row = 0; ValueType newValue = markovianChoiceRewards[row] + aMarkovian.multiplyRowWithVector(row, w); if (hasProbabilisticStates) { newValue += aMarkovianToProbabilistic.multiplyRowWithVector(row, x); } ValueType maxDiff = newValue - *vIt; ValueType minDiff = maxDiff; *vIt = newValue; for (++vIt, ++row; row < aMarkovian.getRowCount(); ++vIt, ++row) { newValue = markovianChoiceRewards[row] + aMarkovian.multiplyRowWithVector(row, w); if (hasProbabilisticStates) { newValue += aMarkovianToProbabilistic.multiplyRowWithVector(row, x); } ValueType diff = newValue - *vIt; maxDiff = std::max(maxDiff, diff); minDiff = std::min(minDiff, diff); *vIt = newValue; } // Check for convergence if ((maxDiff - minDiff) <= (relative ? (precision * (v.front() + minDiff)) : precision)) { break; } // update the rhs of the MinMax equation system ValueType referenceValue = v.front(); storm::utility::vector::applyPointwise(v, w, [&referenceValue] (ValueType const& v_i) -> ValueType { return v_i - referenceValue; }); if (hasProbabilisticStates) { aProbabilisticToMarkovian.multiplyWithVector(w, b); storm::utility::vector::addVectors(b, probabilisticChoiceRewards, b); } } 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."); } return v.front() * uniformizationRate; } template std::vector SparseMarkovAutomatonCslHelper::computeBoundedUntilProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, std::pair const& boundsPair); template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeUntilProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, bool qualitative, bool produceScheduler); template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeReachabilityRewards(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::BitVector const& psiStates, bool produceScheduler); template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeTotalRewards(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel, bool produceScheduler); template std::vector SparseMarkovAutomatonCslHelper::computeLongRunAverageProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& psiStates); template std::vector SparseMarkovAutomatonCslHelper::computeLongRunAverageRewards(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel); template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeReachabilityTimes(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& psiStates, bool produceScheduler); template double SparseMarkovAutomatonCslHelper::computeLraForMaximalEndComponent(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec); template double SparseMarkovAutomatonCslHelper::computeLraForMaximalEndComponentLP(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec); template double SparseMarkovAutomatonCslHelper::computeLraForMaximalEndComponentVI(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec); template std::vector SparseMarkovAutomatonCslHelper::computeBoundedUntilProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, std::pair const& boundsPair); template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeUntilProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, bool qualitative, bool produceScheduler); template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeReachabilityRewards(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::BitVector const& psiStates, bool produceScheduler); template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeTotalRewards(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel, bool produceScheduler); template std::vector SparseMarkovAutomatonCslHelper::computeLongRunAverageProbabilities(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& psiStates); template std::vector SparseMarkovAutomatonCslHelper::computeLongRunAverageRewards(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel); template MDPSparseModelCheckingHelperReturnType SparseMarkovAutomatonCslHelper::computeReachabilityTimes(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::storage::BitVector const& psiStates, bool produceScheduler); template storm::RationalNumber SparseMarkovAutomatonCslHelper::computeLraForMaximalEndComponent(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec); template storm::RationalNumber SparseMarkovAutomatonCslHelper::computeLraForMaximalEndComponentLP(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec); template storm::RationalNumber SparseMarkovAutomatonCslHelper::computeLraForMaximalEndComponentVI(Environment const& env, OptimizationDirection dir, storm::storage::SparseMatrix const& transitionMatrix, std::vector const& exitRateVector, storm::storage::BitVector const& markovianStates, storm::models::sparse::StandardRewardModel const& rewardModel, storm::storage::MaximalEndComponent const& mec); } } }