#include #include #include "storm/solver/IterativeMinMaxLinearEquationSolver.h" #include "storm/utility/ConstantsComparator.h" #include "storm/environment/solver/MinMaxSolverEnvironment.h" #include "storm/environment/solver/OviSolverEnvironment.h" #include "storm/utility/KwekMehlhorn.h" #include "storm/utility/NumberTraits.h" #include "storm/utility/Stopwatch.h" #include "storm/utility/vector.h" #include "storm/utility/macros.h" #include "storm/exceptions/InvalidEnvironmentException.h" #include "storm/exceptions/InvalidStateException.h" #include "storm/exceptions/UnmetRequirementException.h" #include "storm/exceptions/NotSupportedException.h" #include "storm/exceptions/PrecisionExceededException.h" namespace storm { namespace solver { template IterativeMinMaxLinearEquationSolver::IterativeMinMaxLinearEquationSolver(std::unique_ptr>&& linearEquationSolverFactory) : linearEquationSolverFactory(std::move(linearEquationSolverFactory)) { // Intentionally left empty } template IterativeMinMaxLinearEquationSolver::IterativeMinMaxLinearEquationSolver(storm::storage::SparseMatrix const& A, std::unique_ptr>&& linearEquationSolverFactory) : StandardMinMaxLinearEquationSolver(A), linearEquationSolverFactory(std::move(linearEquationSolverFactory)) { // Intentionally left empty. } template IterativeMinMaxLinearEquationSolver::IterativeMinMaxLinearEquationSolver(storm::storage::SparseMatrix&& A, std::unique_ptr>&& linearEquationSolverFactory) : StandardMinMaxLinearEquationSolver(std::move(A)), linearEquationSolverFactory(std::move(linearEquationSolverFactory)) { // Intentionally left empty. } template MinMaxMethod IterativeMinMaxLinearEquationSolver::getMethod(Environment const& env, bool isExactMode) const { // Adjust the method if none was specified and we want exact or sound computations. auto method = env.solver().minMax().getMethod(); if (isExactMode && method != MinMaxMethod::PolicyIteration && method != MinMaxMethod::RationalSearch && method != MinMaxMethod::ViToPi) { if (env.solver().minMax().isMethodSetFromDefault()) { STORM_LOG_INFO("Selecting 'Policy iteration' as the solution technique to guarantee exact results. If you want to override this, please explicitly specify a different method."); method = MinMaxMethod::PolicyIteration; } else { STORM_LOG_WARN("The selected solution method " << toString(method) << " does not guarantee exact results."); } } else if (env.solver().isForceSoundness() && method != MinMaxMethod::SoundValueIteration && method != MinMaxMethod::IntervalIteration && method != MinMaxMethod::PolicyIteration && method != MinMaxMethod::RationalSearch && method != MinMaxMethod::OptimisticValueIteration) { if (env.solver().minMax().isMethodSetFromDefault()) { STORM_LOG_INFO("Selecting 'sound value iteration' as the solution technique to guarantee sound results. If you want to override this, please explicitly specify a different method."); method = MinMaxMethod::SoundValueIteration; } else { STORM_LOG_WARN("The selected solution method does not guarantee sound results."); } } STORM_LOG_THROW(method == MinMaxMethod::ValueIteration || method == MinMaxMethod::PolicyIteration || method == MinMaxMethod::RationalSearch || method == MinMaxMethod::SoundValueIteration || method == MinMaxMethod::IntervalIteration || method == MinMaxMethod::OptimisticValueIteration || method == MinMaxMethod::ViToPi, storm::exceptions::InvalidEnvironmentException, "This solver does not support the selected method."); return method; } template bool IterativeMinMaxLinearEquationSolver::internalSolveEquations(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { bool result = false; switch (getMethod(env, storm::NumberTraits::IsExact)) { case MinMaxMethod::ValueIteration: result = solveEquationsValueIteration(env, dir, x, b); break; case MinMaxMethod::OptimisticValueIteration: result = solveEquationsOptimisticValueIteration(env, dir, x, b); break; case MinMaxMethod::PolicyIteration: result = solveEquationsPolicyIteration(env, dir, x, b); break; case MinMaxMethod::RationalSearch: result = solveEquationsRationalSearch(env, dir, x, b); break; case MinMaxMethod::IntervalIteration: result = solveEquationsIntervalIteration(env, dir, x, b); break; case MinMaxMethod::SoundValueIteration: result = solveEquationsSoundValueIteration(env, dir, x, b); break; case MinMaxMethod::ViToPi: result = solveEquationsViToPi(env, dir, x, b); break; default: STORM_LOG_THROW(false, storm::exceptions::InvalidEnvironmentException, "This solver does not implement the selected solution method"); } return result; } template bool IterativeMinMaxLinearEquationSolver::solveInducedEquationSystem(Environment const& env, std::unique_ptr>& linearEquationSolver, std::vector const& scheduler, std::vector& x, std::vector& subB, std::vector const& originalB) const { assert(subB.size() == x.size()); // Resolve the nondeterminism according to the given scheduler. bool convertToEquationSystem = this->linearEquationSolverFactory->getEquationProblemFormat(env) == LinearEquationSolverProblemFormat::EquationSystem; storm::storage::SparseMatrix submatrix = this->A->selectRowsFromRowGroups(scheduler, convertToEquationSystem); if (convertToEquationSystem) { submatrix.convertToEquationSystem(); } storm::utility::vector::selectVectorValues(subB, scheduler, this->A->getRowGroupIndices(), originalB); // Check whether the linear equation solver is already initialized if (!linearEquationSolver) { // Initialize the equation solver linearEquationSolver = this->linearEquationSolverFactory->create(env, std::move(submatrix)); linearEquationSolver->setBoundsFromOtherSolver(*this); linearEquationSolver->setCachingEnabled(true); } else { // If the equation solver is already initialized, it suffices to update the matrix linearEquationSolver->setMatrix(std::move(submatrix)); } // Solve the equation system for the 'DTMC' and return true upon success return linearEquationSolver->solveEquations(env, x, subB); } template bool IterativeMinMaxLinearEquationSolver::solveEquationsPolicyIteration(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { // Create the initial scheduler. std::vector scheduler = this->hasInitialScheduler() ? this->getInitialScheduler() : std::vector(this->A->getRowGroupCount()); return performPolicyIteration(env, dir, x, b, std::move(scheduler)); } template bool IterativeMinMaxLinearEquationSolver::performPolicyIteration(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b, std::vector&& initialPolicy) const { std::vector scheduler = std::move(initialPolicy); // Get a vector for storing the right-hand side of the inner equation system. if (!auxiliaryRowGroupVector) { auxiliaryRowGroupVector = std::make_unique>(this->A->getRowGroupCount()); } std::vector& subB = *auxiliaryRowGroupVector; // The solver that we will use throughout the procedure. std::unique_ptr> solver; // The linear equation solver should be at least as precise as this solver std::unique_ptr environmentOfSolverStorage; auto precOfSolver = env.solver().getPrecisionOfLinearEquationSolver(env.solver().getLinearEquationSolverType()); if (!storm::NumberTraits::IsExact) { bool changePrecision = precOfSolver.first && precOfSolver.first.get() > env.solver().minMax().getPrecision(); bool changeRelative = precOfSolver.second && !precOfSolver.second.get() && env.solver().minMax().getRelativeTerminationCriterion(); if (changePrecision || changeRelative) { environmentOfSolverStorage = std::make_unique(env); boost::optional newPrecision; boost::optional newRelative; if (changePrecision) { newPrecision = env.solver().minMax().getPrecision(); } if (changeRelative) { newRelative = true; } environmentOfSolverStorage->solver().setLinearEquationSolverPrecision(newPrecision, newRelative); } } storm::Environment const& environmentOfSolver = environmentOfSolverStorage ? *environmentOfSolverStorage : env; SolverStatus status = SolverStatus::InProgress; uint64_t iterations = 0; this->startMeasureProgress(); do { // Solve the equation system for the 'DTMC'. solveInducedEquationSystem(environmentOfSolver, solver, scheduler, x, subB, b); // Go through the multiplication result and see whether we can improve any of the choices. bool schedulerImproved = false; for (uint_fast64_t group = 0; group < this->A->getRowGroupCount(); ++group) { uint_fast64_t currentChoice = scheduler[group]; for (uint_fast64_t choice = this->A->getRowGroupIndices()[group]; choice < this->A->getRowGroupIndices()[group + 1]; ++choice) { // If the choice is the currently selected one, we can skip it. if (choice - this->A->getRowGroupIndices()[group] == currentChoice) { continue; } // Create the value of the choice. ValueType choiceValue = storm::utility::zero(); for (auto const& entry : this->A->getRow(choice)) { choiceValue += entry.getValue() * x[entry.getColumn()]; } choiceValue += b[choice]; // If the value is strictly better than the solution of the inner system, we need to improve the scheduler. // TODO: If the underlying solver is not precise, this might run forever (i.e. when a state has two choices where the (exact) values are equal). // only changing the scheduler if the values are not equal (modulo precision) would make this unsound. if (valueImproved(dir, x[group], choiceValue)) { schedulerImproved = true; scheduler[group] = choice - this->A->getRowGroupIndices()[group]; x[group] = std::move(choiceValue); } } } // If the scheduler did not improve, we are done. if (!schedulerImproved) { status = SolverStatus::Converged; } // Update environment variables. ++iterations; status = updateStatusIfNotConverged(status, x, iterations, env.solver().minMax().getMaximalNumberOfIterations(), dir == storm::OptimizationDirection::Minimize ? SolverGuarantee::GreaterOrEqual : SolverGuarantee::LessOrEqual); // Potentially show progress. this->showProgressIterative(iterations); } while (status == SolverStatus::InProgress); reportStatus(status, iterations); // If requested, we store the scheduler for retrieval. if (this->isTrackSchedulerSet()) { this->schedulerChoices = std::move(scheduler); } if (!this->isCachingEnabled()) { clearCache(); } return status == SolverStatus::Converged || status == SolverStatus::TerminatedEarly; } template bool IterativeMinMaxLinearEquationSolver::valueImproved(OptimizationDirection dir, ValueType const& value1, ValueType const& value2) const { if (dir == OptimizationDirection::Minimize) { return value2 < value1; } else { return value2 > value1; } } template MinMaxLinearEquationSolverRequirements IterativeMinMaxLinearEquationSolver::getRequirements(Environment const& env, boost::optional const& direction, bool const& hasInitialScheduler) const { auto method = getMethod(env, storm::NumberTraits::IsExact); // Check whether a linear equation solver is needed and potentially start with its requirements bool needsLinEqSolver = false; needsLinEqSolver |= method == MinMaxMethod::PolicyIteration; needsLinEqSolver |= method == MinMaxMethod::ValueIteration && (this->hasInitialScheduler() || hasInitialScheduler); needsLinEqSolver |= method == MinMaxMethod::ViToPi; MinMaxLinearEquationSolverRequirements requirements = needsLinEqSolver ? MinMaxLinearEquationSolverRequirements(this->linearEquationSolverFactory->getRequirements(env)) : MinMaxLinearEquationSolverRequirements(); if (method == MinMaxMethod::ValueIteration) { if (!this->hasUniqueSolution()) { // Traditional value iteration has no requirements if the solution is unique. // Computing a scheduler is only possible if the solution is unique if (this->isTrackSchedulerSet()) { requirements.requireUniqueSolution(); } else { // As we want the smallest (largest) solution for maximizing (minimizing) equation systems, we have to approach the solution from below (above). if (!direction || direction.get() == OptimizationDirection::Maximize) { requirements.requireLowerBounds(); } if (!direction || direction.get() == OptimizationDirection::Minimize) { requirements.requireUpperBounds(); } } } } else if (method == MinMaxMethod::OptimisticValueIteration) { // OptimisticValueIteration always requires lower bounds and a unique solution. if (!this->hasUniqueSolution()) { requirements.requireUniqueSolution(); } requirements.requireLowerBounds(); } else if (method == MinMaxMethod::IntervalIteration) { // Interval iteration requires a unique solution and lower+upper bounds if (!this->hasUniqueSolution()) { requirements.requireUniqueSolution(); } requirements.requireBounds(); } else if (method == MinMaxMethod::RationalSearch) { // Rational search needs to approach the solution from below. requirements.requireLowerBounds(); // The solution needs to be unique in case of minimizing or in cases where we want a scheduler. if (!this->hasUniqueSolution() && (!direction || direction.get() == OptimizationDirection::Minimize || this->isTrackSchedulerSet())) { requirements.requireUniqueSolution(); } } else if (method == MinMaxMethod::PolicyIteration) { // The initial scheduler shall not select an end component if (!this->hasNoEndComponents()) { requirements.requireValidInitialScheduler(); } } else if (method == MinMaxMethod::SoundValueIteration) { if (!this->hasUniqueSolution()) { requirements.requireUniqueSolution(); } requirements.requireBounds(false); } else if (method == MinMaxMethod::ViToPi) { // Since we want to use value iteration to extract an initial scheduler, the solution has to be unique. if (!this->hasUniqueSolution()) { requirements.requireUniqueSolution(); } } else { STORM_LOG_THROW(false, storm::exceptions::InvalidEnvironmentException, "Unsupported technique for iterative MinMax linear equation solver."); } return requirements; } template typename IterativeMinMaxLinearEquationSolver::ValueIterationResult IterativeMinMaxLinearEquationSolver::performValueIteration(Environment const& env, OptimizationDirection dir, std::vector*& currentX, std::vector*& newX, std::vector const& b, ValueType const& precision, bool relative, SolverGuarantee const& guarantee, uint64_t currentIterations, uint64_t maximalNumberOfIterations, storm::solver::MultiplicationStyle const& multiplicationStyle) const { STORM_LOG_ASSERT(currentX != newX, "Vectors must not be aliased."); // Get handle to multiplier. storm::solver::Multiplier const& multiplier = *this->multiplierA; // Allow aliased multiplications. bool useGaussSeidelMultiplication = multiplicationStyle == storm::solver::MultiplicationStyle::GaussSeidel; // Proceed with the iterations as long as the method did not converge or reach the maximum number of iterations. uint64_t iterations = currentIterations; SolverStatus status = SolverStatus::InProgress; while (status == SolverStatus::InProgress) { // Compute x' = min/max(A*x + b). if (useGaussSeidelMultiplication) { // Copy over the current vector so we can modify it in-place. *newX = *currentX; multiplier.multiplyAndReduceGaussSeidel(env, dir, *newX, &b); } else { multiplier.multiplyAndReduce(env, dir, *currentX, &b, *newX); } // Determine whether the method converged. if (storm::utility::vector::equalModuloPrecision(*currentX, *newX, precision, relative)) { status = SolverStatus::Converged; } // Update environment variables. std::swap(currentX, newX); ++iterations; status = updateStatusIfNotConverged(status, *currentX, iterations, maximalNumberOfIterations, guarantee); // Potentially show progress. this->showProgressIterative(iterations); } return ValueIterationResult(iterations - currentIterations, status); } template ValueType computeMaxAbsDiff(std::vector const& allValues, storm::storage::BitVector const& relevantValues, std::vector const& oldValues) { ValueType result = storm::utility::zero(); auto oldValueIt = oldValues.begin(); for (auto value : relevantValues) { result = storm::utility::max(result, storm::utility::abs(allValues[value] - *oldValueIt)); ++oldValueIt; } return result; } template ValueType computeMaxAbsDiff(std::vector const& allOldValues, std::vector const& allNewValues, storm::storage::BitVector const& relevantValues) { ValueType result = storm::utility::zero(); for (auto value : relevantValues) { result = storm::utility::max(result, storm::utility::abs(allNewValues[value] - allOldValues[value])); } return result; } template ValueType computeMaxAbsDiff(std::vector const& allOldValues, std::vector const& allNewValues) { ValueType result = storm::utility::zero(); for (uint64_t i = 0; i < allOldValues.size(); ++i) { result = storm::utility::max(result, storm::utility::abs(allNewValues[i] - allOldValues[i])); } return result; } template ValueType computeMaxRelDiff(std::vector const& allOldValues, std::vector const& allNewValues, storm::storage::BitVector const& relevantValues) { ValueType result = storm::utility::zero(); for (auto const& i : relevantValues) { STORM_LOG_ASSERT(!storm::utility::isZero(allNewValues[i]) || storm::utility::isZero(allOldValues[i]), "Unexpected entry in iteration vector."); if (!storm::utility::isZero(allNewValues[i])) { result = storm::utility::max(result, storm::utility::abs(allNewValues[i] - allOldValues[i]) / allNewValues[i]); } } return result; } template ValueType computeMaxRelDiff(std::vector const& allOldValues, std::vector const& allNewValues) { ValueType result = storm::utility::zero(); for (uint64_t i = 0; i < allOldValues.size(); ++i) { STORM_LOG_ASSERT(!storm::utility::isZero(allNewValues[i]) || storm::utility::isZero(allOldValues[i]), "Unexpected entry in iteration vector."); if (!storm::utility::isZero(allNewValues[i])) { result = storm::utility::max(result, storm::utility::abs(allNewValues[i] - allOldValues[i]) / allNewValues[i]); } } return result; } template ValueType updateIterationPrecision(storm::Environment const& env, std::vector const& currentX, std::vector const& newX, bool const& relative, boost::optional const& relevantValues) { auto factor = storm::utility::convertNumber(env.solver().ovi().getPrecisionUpdateFactor()); bool useRelevant = relevantValues.is_initialized() && env.solver().ovi().useRelevantValuesForPrecisionUpdate(); if (relative) { return (useRelevant ? computeMaxRelDiff(newX, currentX, relevantValues.get()) : computeMaxRelDiff(newX, currentX)) * factor; } else { return (useRelevant ? computeMaxAbsDiff(newX, currentX, relevantValues.get()) : computeMaxAbsDiff(newX, currentX)) * factor; } } template void guessUpperBoundRelative(std::vector const& x, std::vector &target, ValueType const& relativeBoundGuessingScaler) { storm::utility::vector::applyPointwise(x, target, [&relativeBoundGuessingScaler] (ValueType const& argument) -> ValueType { return argument * relativeBoundGuessingScaler; }); } template void guessUpperBoundAbsolute(std::vector const& x, std::vector &target, ValueType const& precision) { storm::utility::vector::applyPointwise(x, target, [&precision] (ValueType const& argument) -> ValueType { return argument + precision; }); } template bool IterativeMinMaxLinearEquationSolver::solveEquationsOptimisticValueIteration(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { uint64_t overallIterations = 0; uint64_t maxOverallIterations = env.solver().minMax().getMaximalNumberOfIterations(); uint64_t lastValueIterationIterations = 0; uint64_t currentVerificationIterations = 0; uint64_t valueIterationInvocations = 0; //DEBUG uint64_t up_occur = 0; uint64_t cross_occur = 0; uint64_t down_occur = 0; uint64_t no_case_occur = 0; //DEBUG END if (!this->multiplierA) { this->multiplierA = storm::solver::MultiplierFactory().create(env, *this->A); } if (!auxiliaryRowGroupVector) { auxiliaryRowGroupVector = std::make_unique>(this->A->getRowGroupCount()); } // By default, we can not provide any guarantee SolverGuarantee guarantee = SolverGuarantee::None; // Get handle to multiplier. storm::solver::Multiplier const &multiplier = *this->multiplierA; // Allow aliased multiplications. storm::solver::MultiplicationStyle multiplicationStyle = env.solver().minMax().getMultiplicationStyle(); bool useGaussSeidelMultiplication = multiplicationStyle == storm::solver::MultiplicationStyle::GaussSeidel; // Relative errors bool relative = env.solver().minMax().getRelativeTerminationCriterion(); boost::optional relevantValues; if (this->hasRelevantValues()) { relevantValues = this->getRelevantValues(); } // x has to start with a lower bound. this->createLowerBoundsVector(x); std::vector *currentX = &x; std::vector *newX = auxiliaryRowGroupVector.get(); std::vector currentUpperBound(currentX->size()); std::vector newUpperBound(x.size()); ValueType two = storm::utility::convertNumber(2.0); ValueType precision = storm::utility::convertNumber(env.solver().minMax().getPrecision()); ValueType relativeBoundGuessingScaler = (storm::utility::one() + storm::utility::convertNumber(env.solver().ovi().getUpperBoundGuessingFactor()) * precision); ValueType doublePrecision = precision * two; ValueType iterationPrecision = precision; SolverStatus status = SolverStatus::InProgress; this->startMeasureProgress(); while (status == SolverStatus::InProgress && overallIterations < maxOverallIterations) { // Perform value iteration until convergence ++valueIterationInvocations; ValueIterationResult result = performValueIteration(env, dir, currentX, newX, b, iterationPrecision, relative, guarantee, overallIterations, env.solver().minMax().getMaximalNumberOfIterations(), multiplicationStyle); lastValueIterationIterations = result.iterations; overallIterations += result.iterations; if (result.status != SolverStatus::Converged) { status = result.status; } else { currentVerificationIterations = 0; if (relative) { guessUpperBoundRelative(*currentX, currentUpperBound, relativeBoundGuessingScaler); } else { guessUpperBoundAbsolute(*currentX, currentUpperBound, precision); } bool cancelGuess = false; while (status == SolverStatus::InProgress && overallIterations < maxOverallIterations && !cancelGuess) { // Perform value iteration stepwise for lower bound and guessed upper bound // Lower and upper bound iteration // Compute x' = min/max(A*x + b). if (useGaussSeidelMultiplication) { // Copy over the current vectors so we can modify them in-place. // This is necessary as we want to compare the new values with the current ones. *newX = *currentX; newUpperBound = currentUpperBound; // Do the calculation multiplier.multiplyAndReduceGaussSeidel(env, dir, *newX, &b); multiplier.multiplyAndReduceGaussSeidel(env, dir, newUpperBound, &b); } else { multiplier.multiplyAndReduce(env, dir, *currentX, &b, *newX); multiplier.multiplyAndReduce(env, dir, currentUpperBound, &b, newUpperBound); } bool newUpperBoundAlwaysHigherEqual = true; bool newUpperBoundAlwaysLowerEqual = true; bool valuesCrossed = false; ValueType maxBoundDiff = storm::utility::zero(); for (uint64_t i = 0; i < x.size(); ++i) { if (newUpperBound[i] < currentUpperBound[i]) { newUpperBoundAlwaysHigherEqual = false; } else if (newUpperBound[i] != currentUpperBound[i]) { newUpperBoundAlwaysLowerEqual = false; } if (newUpperBound[i] < (*newX)[i]) { valuesCrossed = true; break; } else { maxBoundDiff = std::max(maxBoundDiff, newUpperBound[i] - (*newX)[i]); } } // Update bounds std::swap(currentX, newX); std::swap(currentUpperBound, newUpperBound); if (newUpperBoundAlwaysHigherEqual & ! newUpperBoundAlwaysLowerEqual) { ++up_occur; iterationPrecision = updateIterationPrecision(env, *currentX, *newX, relative, relevantValues); // Not all values moved up or stayed the same // If we have a single fixed point, we can safely set the new lower bound, to the wrongly guessed upper bound if (this->hasUniqueSolution()) { *currentX = currentUpperBound; } break; } else if (valuesCrossed) { ++cross_occur; STORM_LOG_ASSERT(false, "Cross case occurred."); iterationPrecision = updateIterationPrecision(env, *currentX, *newX, relative, relevantValues); break; } else if (newUpperBoundAlwaysLowerEqual) { ++down_occur; // All values moved down or stayed the same and we have a maximum difference of twice the requested precision // We can safely use twice the requested precision, as we calculate the center of both vectors // We can use max_if instead of computeMaxAbsDiff, as x is definitely a lower bound and ub is larger in all elements // Recalculate terminationPrecision if relative error requested bool reachedPrecision = true; for (auto const& valueIndex : relevantValues ? relevantValues.get() : storm::storage::BitVector(x.size(), true)) { ValueType absDiff = currentUpperBound[valueIndex] - (*currentX)[valueIndex]; if (relative) { if (absDiff > doublePrecision * (*currentX)[valueIndex]) { reachedPrecision = false; break; } } else { if (absDiff > doublePrecision) { reachedPrecision = false; break; } } } if (reachedPrecision) { // Calculate the center of both vectors and store it in currentX storm::utility::vector::applyPointwise(*currentX, currentUpperBound, *currentX, [&two] (ValueType const& a, ValueType const& b) -> ValueType { return (a + b) / two; }); status = SolverStatus::Converged; } } else { ++no_case_occur; } ValueType scaledIterationCount = storm::utility::convertNumber(currentVerificationIterations) * storm::utility::convertNumber(env.solver().ovi().getMaxVerificationIterationFactor()); if (scaledIterationCount >= storm::utility::convertNumber(lastValueIterationIterations)) { cancelGuess = true; iterationPrecision = updateIterationPrecision(env, *currentX, *newX, relative, relevantValues); } ++overallIterations; ++currentVerificationIterations; } } } if (overallIterations > maxOverallIterations) { status = SolverStatus::MaximalIterationsExceeded; } // Swap the result into the output x. if (currentX == auxiliaryRowGroupVector.get()) { std::swap(x, *currentX); } // DEBUG std::cout << "Up mov: " << up_occur << ", Cross: " << cross_occur << ", Down mov: " << down_occur << ", None: " << no_case_occur << std::endl; // DEBUG END reportStatus(status, overallIterations); // If requested, we store the scheduler for retrieval. if (this->isTrackSchedulerSet()) { this->schedulerChoices = std::vector(this->A->getRowGroupCount()); this->multiplierA->multiplyAndReduce(env, dir, x, &b, *auxiliaryRowGroupVector.get(), &this->schedulerChoices.get()); this->multiplierA->multiplyAndReduce(env, dir, x, &b, *auxiliaryRowGroupVector.get(), &this->schedulerChoices.get()); } if (!this->isCachingEnabled()) { clearCache(); } return status == SolverStatus::Converged || status == SolverStatus::TerminatedEarly; } template bool IterativeMinMaxLinearEquationSolver::solveEquationsValueIteration(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { if (!this->multiplierA) { this->multiplierA = storm::solver::MultiplierFactory().create(env, *this->A); } if (!auxiliaryRowGroupVector) { auxiliaryRowGroupVector = std::make_unique>(this->A->getRowGroupCount()); } // By default, we can not provide any guarantee SolverGuarantee guarantee = SolverGuarantee::None; if (this->hasInitialScheduler()) { // Solve the equation system induced by the initial scheduler. std::unique_ptr> linEqSolver; // The linear equation solver should be at least as precise as this solver std::unique_ptr environmentOfSolverStorage; auto precOfSolver = env.solver().getPrecisionOfLinearEquationSolver(env.solver().getLinearEquationSolverType()); if (!storm::NumberTraits::IsExact) { bool changePrecision = precOfSolver.first && precOfSolver.first.get() > env.solver().minMax().getPrecision(); bool changeRelative = precOfSolver.second && !precOfSolver.second.get() && env.solver().minMax().getRelativeTerminationCriterion(); if (changePrecision || changeRelative) { environmentOfSolverStorage = std::make_unique(env); boost::optional newPrecision; boost::optional newRelative; if (changePrecision) { newPrecision = env.solver().minMax().getPrecision(); } if (changeRelative) { newRelative = true; } environmentOfSolverStorage->solver().setLinearEquationSolverPrecision(newPrecision, newRelative); } } storm::Environment const& environmentOfSolver = environmentOfSolverStorage ? *environmentOfSolverStorage : env; solveInducedEquationSystem(environmentOfSolver, linEqSolver, this->getInitialScheduler(), x, *auxiliaryRowGroupVector, b); // If we were given an initial scheduler and are maximizing (minimizing), our current solution becomes // always less-or-equal (greater-or-equal) than the actual solution. guarantee = maximize(dir) ? SolverGuarantee::LessOrEqual : SolverGuarantee::GreaterOrEqual; } else if (!this->hasUniqueSolution()) { if (maximize(dir)) { this->createLowerBoundsVector(x); guarantee = SolverGuarantee::LessOrEqual; } else { this->createUpperBoundsVector(x); guarantee = SolverGuarantee::GreaterOrEqual; } } else if (this->hasCustomTerminationCondition()) { if (this->getTerminationCondition().requiresGuarantee(SolverGuarantee::LessOrEqual) && this->hasLowerBound()) { this->createLowerBoundsVector(x); guarantee = SolverGuarantee::LessOrEqual; } else if (this->getTerminationCondition().requiresGuarantee(SolverGuarantee::GreaterOrEqual) && this->hasUpperBound()) { this->createUpperBoundsVector(x); guarantee = SolverGuarantee::GreaterOrEqual; } } std::vector* newX = auxiliaryRowGroupVector.get(); std::vector* currentX = &x; this->startMeasureProgress(); ValueIterationResult result = performValueIteration(env, dir, currentX, newX, b, storm::utility::convertNumber(env.solver().minMax().getPrecision()), env.solver().minMax().getRelativeTerminationCriterion(), guarantee, 0, env.solver().minMax().getMaximalNumberOfIterations(), env.solver().minMax().getMultiplicationStyle()); // Swap the result into the output x. if (currentX == auxiliaryRowGroupVector.get()) { std::swap(x, *currentX); } reportStatus(result.status, result.iterations); // If requested, we store the scheduler for retrieval. if (this->isTrackSchedulerSet()) { this->schedulerChoices = std::vector(this->A->getRowGroupCount()); this->multiplierA->multiplyAndReduce(env, dir, x, &b, *auxiliaryRowGroupVector.get(), &this->schedulerChoices.get()); } if (!this->isCachingEnabled()) { clearCache(); } return result.status == SolverStatus::Converged || result.status == SolverStatus::TerminatedEarly; } template void preserveOldRelevantValues(std::vector const& allValues, storm::storage::BitVector const& relevantValues, std::vector& oldValues) { storm::utility::vector::selectVectorValues(oldValues, relevantValues, allValues); } /*! * This version of value iteration is sound, because it approaches the solution from below and above. This * technique is due to Haddad and Monmege (Interval iteration algorithm for MDPs and IMDPs, TCS 2017) and was * extended to rewards by Baier, Klein, Leuschner, Parker and Wunderlich (Ensuring the Reliability of Your * Model Checker: Interval Iteration for Markov Decision Processes, CAV 2017). */ template bool IterativeMinMaxLinearEquationSolver::solveEquationsIntervalIteration(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { STORM_LOG_THROW(this->hasUpperBound(), storm::exceptions::UnmetRequirementException, "Solver requires upper bound, but none was given."); if (!this->multiplierA) { this->multiplierA = storm::solver::MultiplierFactory().create(env, *this->A); } if (!auxiliaryRowGroupVector) { auxiliaryRowGroupVector = std::make_unique>(this->A->getRowGroupCount()); } // Allow aliased multiplications. bool useGaussSeidelMultiplication = env.solver().minMax().getMultiplicationStyle() == storm::solver::MultiplicationStyle::GaussSeidel; std::vector* lowerX = &x; this->createLowerBoundsVector(*lowerX); this->createUpperBoundsVector(this->auxiliaryRowGroupVector, this->A->getRowGroupCount()); std::vector* upperX = this->auxiliaryRowGroupVector.get(); std::vector* tmp = nullptr; if (!useGaussSeidelMultiplication) { auxiliaryRowGroupVector2 = std::make_unique>(lowerX->size()); tmp = auxiliaryRowGroupVector2.get(); } // Proceed with the iterations as long as the method did not converge or reach the maximum number of iterations. uint64_t iterations = 0; SolverStatus status = SolverStatus::InProgress; bool doConvergenceCheck = true; bool useDiffs = this->hasRelevantValues() && !env.solver().minMax().isSymmetricUpdatesSet(); std::vector oldValues; if (useGaussSeidelMultiplication && useDiffs) { oldValues.resize(this->getRelevantValues().getNumberOfSetBits()); } ValueType maxLowerDiff = storm::utility::zero(); ValueType maxUpperDiff = storm::utility::zero(); bool relative = env.solver().minMax().getRelativeTerminationCriterion(); ValueType precision = storm::utility::convertNumber(env.solver().minMax().getPrecision()); if (!relative) { precision *= storm::utility::convertNumber(2.0); } this->startMeasureProgress(); while (status == SolverStatus::InProgress && iterations < env.solver().minMax().getMaximalNumberOfIterations()) { // Remember in which directions we took steps in this iteration. bool lowerStep = false; bool upperStep = false; // In every thousandth iteration, we improve both bounds. if (iterations % 1000 == 0 || maxLowerDiff == maxUpperDiff) { lowerStep = true; upperStep = true; if (useGaussSeidelMultiplication) { if (useDiffs) { preserveOldRelevantValues(*lowerX, this->getRelevantValues(), oldValues); } this->multiplierA->multiplyAndReduceGaussSeidel(env, dir, *lowerX, &b); if (useDiffs) { maxLowerDiff = computeMaxAbsDiff(*lowerX, this->getRelevantValues(), oldValues); preserveOldRelevantValues(*upperX, this->getRelevantValues(), oldValues); } this->multiplierA->multiplyAndReduceGaussSeidel(env, dir, *upperX, &b); if (useDiffs) { maxUpperDiff = computeMaxAbsDiff(*upperX, this->getRelevantValues(), oldValues); } } else { this->multiplierA->multiplyAndReduce(env, dir, *lowerX, &b, *tmp); if (useDiffs) { maxLowerDiff = computeMaxAbsDiff(*lowerX, *tmp, this->getRelevantValues()); } std::swap(lowerX, tmp); this->multiplierA->multiplyAndReduce(env, dir, *upperX, &b, *tmp); if (useDiffs) { maxUpperDiff = computeMaxAbsDiff(*upperX, *tmp, this->getRelevantValues()); } std::swap(upperX, tmp); } } else { // In the following iterations, we improve the bound with the greatest difference. if (useGaussSeidelMultiplication) { if (maxLowerDiff >= maxUpperDiff) { if (useDiffs) { preserveOldRelevantValues(*lowerX, this->getRelevantValues(), oldValues); } this->multiplierA->multiplyAndReduceGaussSeidel(env, dir, *lowerX, &b); if (useDiffs) { maxLowerDiff = computeMaxAbsDiff(*lowerX, this->getRelevantValues(), oldValues); } lowerStep = true; } else { if (useDiffs) { preserveOldRelevantValues(*upperX, this->getRelevantValues(), oldValues); } this->multiplierA->multiplyAndReduceGaussSeidel(env, dir, *upperX, &b); if (useDiffs) { maxUpperDiff = computeMaxAbsDiff(*upperX, this->getRelevantValues(), oldValues); } upperStep = true; } } else { if (maxLowerDiff >= maxUpperDiff) { this->multiplierA->multiplyAndReduce(env, dir, *lowerX, &b, *tmp); if (useDiffs) { maxLowerDiff = computeMaxAbsDiff(*lowerX, *tmp, this->getRelevantValues()); } std::swap(tmp, lowerX); lowerStep = true; } else { this->multiplierA->multiplyAndReduce(env, dir, *upperX, &b, *tmp); if (useDiffs) { maxUpperDiff = computeMaxAbsDiff(*upperX, *tmp, this->getRelevantValues()); } std::swap(tmp, upperX); upperStep = true; } } } STORM_LOG_ASSERT(maxLowerDiff >= storm::utility::zero(), "Expected non-negative lower diff."); STORM_LOG_ASSERT(maxUpperDiff >= storm::utility::zero(), "Expected non-negative upper diff."); if (iterations % 1000 == 0) { STORM_LOG_TRACE("Iteration " << iterations << ": lower difference: " << maxLowerDiff << ", upper difference: " << maxUpperDiff << "."); } if (doConvergenceCheck) { // Determine whether the method converged. if (this->hasRelevantValues()) { status = storm::utility::vector::equalModuloPrecision(*lowerX, *upperX, this->getRelevantValues(), precision, relative) ? SolverStatus::Converged : status; } else { status = storm::utility::vector::equalModuloPrecision(*lowerX, *upperX, precision, relative) ? SolverStatus::Converged : status; } } // Update environment variables. ++iterations; doConvergenceCheck = !doConvergenceCheck; if (lowerStep) { status = updateStatusIfNotConverged(status, *lowerX, iterations, env.solver().minMax().getMaximalNumberOfIterations(), SolverGuarantee::LessOrEqual); } if (upperStep) { status = updateStatusIfNotConverged(status, *upperX, iterations, env.solver().minMax().getMaximalNumberOfIterations(), SolverGuarantee::GreaterOrEqual); } // Potentially show progress. this->showProgressIterative(iterations); } reportStatus(status, iterations); // We take the means of the lower and upper bound so we guarantee the desired precision. ValueType two = storm::utility::convertNumber(2.0); storm::utility::vector::applyPointwise(*lowerX, *upperX, *lowerX, [&two] (ValueType const& a, ValueType const& b) -> ValueType { return (a + b) / two; }); // Since we shuffled the pointer around, we need to write the actual results to the input/output vector x. if (&x == tmp) { std::swap(x, *tmp); } else if (&x == this->auxiliaryRowGroupVector.get()) { std::swap(x, *this->auxiliaryRowGroupVector); } // If requested, we store the scheduler for retrieval. if (this->isTrackSchedulerSet()) { this->schedulerChoices = std::vector(this->A->getRowGroupCount()); this->multiplierA->multiplyAndReduce(env, dir, x, &b, *this->auxiliaryRowGroupVector, &this->schedulerChoices.get()); } if (!this->isCachingEnabled()) { clearCache(); } return status == SolverStatus::Converged; } template bool IterativeMinMaxLinearEquationSolver::solveEquationsSoundValueIteration(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { // Prepare the solution vectors and the helper. assert(x.size() == this->A->getRowGroupCount()); if (!this->auxiliaryRowGroupVector) { this->auxiliaryRowGroupVector = std::make_unique>(); } if (!this->soundValueIterationHelper) { this->soundValueIterationHelper = std::make_unique>(*this->A, x, *this->auxiliaryRowGroupVector, env.solver().minMax().getRelativeTerminationCriterion(), storm::utility::convertNumber(env.solver().minMax().getPrecision())); } else { this->soundValueIterationHelper = std::make_unique>(std::move(*this->soundValueIterationHelper), x, *this->auxiliaryRowGroupVector, env.solver().minMax().getRelativeTerminationCriterion(), storm::utility::convertNumber(env.solver().minMax().getPrecision())); } // Prepare initial bounds for the solution (if given) if (this->hasLowerBound()) { this->soundValueIterationHelper->setLowerBound(this->getLowerBound(true)); } if (this->hasUpperBound()) { this->soundValueIterationHelper->setUpperBound(this->getUpperBound(true)); } storm::storage::BitVector const* relevantValuesPtr = nullptr; if (this->hasRelevantValues()) { relevantValuesPtr = &this->getRelevantValues(); } SolverStatus status = SolverStatus::InProgress; this->startMeasureProgress(); uint64_t iterations = 0; while (status == SolverStatus::InProgress && iterations < env.solver().minMax().getMaximalNumberOfIterations()) { ++iterations; this->soundValueIterationHelper->performIterationStep(dir, b); if (this->soundValueIterationHelper->checkConvergenceUpdateBounds(dir, relevantValuesPtr)) { status = SolverStatus::Converged; } else { // Update the status accordingly if (this->hasCustomTerminationCondition() && this->soundValueIterationHelper->checkCustomTerminationCondition(this->getTerminationCondition())) { status = SolverStatus::TerminatedEarly; } else if (iterations >= env.solver().minMax().getMaximalNumberOfIterations()) { status = SolverStatus::MaximalIterationsExceeded; } } // Potentially show progress. this->showProgressIterative(iterations); } this->soundValueIterationHelper->setSolutionVector(); // If requested, we store the scheduler for retrieval. if (this->isTrackSchedulerSet()) { this->schedulerChoices = std::vector(this->A->getRowGroupCount()); this->A->multiplyAndReduce(dir, this->A->getRowGroupIndices(), x, &b, *this->auxiliaryRowGroupVector, &this->schedulerChoices.get()); } reportStatus(status, iterations); if (!this->isCachingEnabled()) { clearCache(); } return status == SolverStatus::Converged; } template bool IterativeMinMaxLinearEquationSolver::solveEquationsViToPi(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { // First create an (inprecise) vi solver to get a good initial strategy for the (potentially precise) policy iteration solver. std::vector initialSched; { Environment viEnv = env; viEnv.solver().minMax().setMethod(MinMaxMethod::ValueIteration); auto impreciseSolver = GeneralMinMaxLinearEquationSolverFactory().create(viEnv, this->A->template toValueType()); impreciseSolver->setHasUniqueSolution(this->hasUniqueSolution()); impreciseSolver->setTrackScheduler(true); if (this->hasInitialScheduler()) { auto initSched = this->getInitialScheduler(); impreciseSolver->setInitialScheduler(std::move(initSched)); } STORM_LOG_THROW(!impreciseSolver->getRequirements(viEnv, dir).hasEnabledCriticalRequirement(), storm::exceptions::UnmetRequirementException, "The value-iteration based solver has an unmet requirement."); auto xVi = storm::utility::vector::convertNumericVector(x); auto bVi = storm::utility::vector::convertNumericVector(b); impreciseSolver->solveEquations(viEnv, dir, xVi, bVi); initialSched = impreciseSolver->getSchedulerChoices(); } STORM_LOG_INFO("Found initial policy using Value Iteration. Starting Policy iteration now."); return performPolicyIteration(env, dir, x, b, std::move(initialSched)); } template bool IterativeMinMaxLinearEquationSolver::isSolution(storm::OptimizationDirection dir, storm::storage::SparseMatrix const& matrix, std::vector const& values, std::vector const& b) { storm::utility::ConstantsComparator comparator; auto valueIt = values.begin(); auto bIt = b.begin(); for (uint64_t group = 0; group < matrix.getRowGroupCount(); ++group, ++valueIt) { ValueType groupValue = *bIt; uint64_t row = matrix.getRowGroupIndices()[group]; groupValue += matrix.multiplyRowWithVector(row, values); ++row; ++bIt; for (auto endRow = matrix.getRowGroupIndices()[group + 1]; row < endRow; ++row, ++bIt) { ValueType newValue = *bIt; newValue += matrix.multiplyRowWithVector(row, values); if ((dir == storm::OptimizationDirection::Minimize && newValue < groupValue) || (dir == storm::OptimizationDirection::Maximize && newValue > groupValue)) { groupValue = newValue; } } // If the value does not match the one in the values vector, the given vector is not a solution. if (!comparator.isEqual(groupValue, *valueIt)) { return false; } } // Checked all values at this point. return true; } template template bool IterativeMinMaxLinearEquationSolver::sharpen(storm::OptimizationDirection dir, uint64_t precision, storm::storage::SparseMatrix const& A, std::vector const& x, std::vector const& b, std::vector& tmp) { for (uint64_t p = 0; p <= precision; ++p) { storm::utility::kwek_mehlhorn::sharpen(p, x, tmp); if (IterativeMinMaxLinearEquationSolver::isSolution(dir, A, tmp, b)) { return true; } } return false; } template template typename std::enable_if::value && !NumberTraits::IsExact, bool>::type IterativeMinMaxLinearEquationSolver::solveEquationsRationalSearchHelper(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { // Version for when the overall value type is imprecise. // Create a rational representation of the input so we can check for a proper solution later. storm::storage::SparseMatrix rationalA = this->A->template toValueType(); std::vector rationalX(x.size()); std::vector rationalB = storm::utility::vector::convertNumericVector(b); if (!this->multiplierA) { this->multiplierA = storm::solver::MultiplierFactory().create(env, *this->A); } if (!auxiliaryRowGroupVector) { auxiliaryRowGroupVector = std::make_unique>(this->A->getRowGroupCount()); } // Forward the call to the core rational search routine. bool converged = solveEquationsRationalSearchHelper(env, dir, *this, rationalA, rationalX, rationalB, *this->A, x, b, *auxiliaryRowGroupVector); // Translate back rational result to imprecise result. auto targetIt = x.begin(); for (auto it = rationalX.begin(), ite = rationalX.end(); it != ite; ++it, ++targetIt) { *targetIt = storm::utility::convertNumber(*it); } if (!this->isCachingEnabled()) { this->clearCache(); } return converged; } template template typename std::enable_if::value && NumberTraits::IsExact, bool>::type IterativeMinMaxLinearEquationSolver::solveEquationsRationalSearchHelper(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { // Version for when the overall value type is exact and the same type is to be used for the imprecise part. if (!this->multiplierA) { this->multiplierA = storm::solver::MultiplierFactory().create(env, *this->A); } if (!auxiliaryRowGroupVector) { auxiliaryRowGroupVector = std::make_unique>(this->A->getRowGroupCount()); } // Forward the call to the core rational search routine. bool converged = solveEquationsRationalSearchHelper(env, dir, *this, *this->A, x, b, *this->A, *auxiliaryRowGroupVector, b, x); if (!this->isCachingEnabled()) { this->clearCache(); } return converged; } template template typename std::enable_if::value, bool>::type IterativeMinMaxLinearEquationSolver::solveEquationsRationalSearchHelper(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { // Version for when the overall value type is exact and the imprecise one is not. We first try to solve the // problem using the imprecise data type and fall back to the exact type as needed. // Translate A to its imprecise version. storm::storage::SparseMatrix impreciseA = this->A->template toValueType(); // Translate x to its imprecise version. std::vector impreciseX(x.size()); { std::vector tmp(x.size()); this->createLowerBoundsVector(tmp); auto targetIt = impreciseX.begin(); for (auto sourceIt = tmp.begin(); targetIt != impreciseX.end(); ++targetIt, ++sourceIt) { *targetIt = storm::utility::convertNumber(*sourceIt); } } // Create temporary storage for an imprecise x. std::vector impreciseTmpX(x.size()); // Translate b to its imprecise version. std::vector impreciseB(b.size()); auto targetIt = impreciseB.begin(); for (auto sourceIt = b.begin(); targetIt != impreciseB.end(); ++targetIt, ++sourceIt) { *targetIt = storm::utility::convertNumber(*sourceIt); } // Create imprecise solver from the imprecise data. IterativeMinMaxLinearEquationSolver impreciseSolver(std::make_unique>()); impreciseSolver.setMatrix(impreciseA); impreciseSolver.setCachingEnabled(true); impreciseSolver.multiplierA = storm::solver::MultiplierFactory().create(env, impreciseA); bool converged = false; try { // Forward the call to the core rational search routine. converged = solveEquationsRationalSearchHelper(env, dir, impreciseSolver, *this->A, x, b, impreciseA, impreciseX, impreciseB, impreciseTmpX); impreciseSolver.clearCache(); } catch (storm::exceptions::PrecisionExceededException const& e) { STORM_LOG_WARN("Precision of value type was exceeded, trying to recover by switching to rational arithmetic."); if (!auxiliaryRowGroupVector) { auxiliaryRowGroupVector = std::make_unique>(this->A->getRowGroupCount()); } // Translate the imprecise value iteration result to the one we are going to use from now on. auto targetIt = auxiliaryRowGroupVector->begin(); for (auto it = impreciseX.begin(), ite = impreciseX.end(); it != ite; ++it, ++targetIt) { *targetIt = storm::utility::convertNumber(*it); } // Get rid of the superfluous data structures. impreciseX = std::vector(); impreciseTmpX = std::vector(); impreciseB = std::vector(); impreciseA = storm::storage::SparseMatrix(); if (!this->multiplierA) { this->multiplierA = storm::solver::MultiplierFactory().create(env, *this->A); } // Forward the call to the core rational search routine, but now with our value type as the imprecise value type. converged = solveEquationsRationalSearchHelper(env, dir, *this, *this->A, x, b, *this->A, *auxiliaryRowGroupVector, b, x); } if (!this->isCachingEnabled()) { this->clearCache(); } return converged; } template struct TemporaryHelper { static std::vector* getTemporary(std::vector& rationalX, std::vector*& currentX, std::vector*& newX) { return &rationalX; } static void swapSolutions(std::vector& rationalX, std::vector*& rationalSolution, std::vector& x, std::vector*& currentX, std::vector*& newX) { // Nothing to do. } }; template struct TemporaryHelper { static std::vector* getTemporary(std::vector& rationalX, std::vector*& currentX, std::vector*& newX) { return newX; } static void swapSolutions(std::vector& rationalX, std::vector*& rationalSolution, std::vector& x, std::vector*& currentX, std::vector*& newX) { if (&rationalX == rationalSolution) { // In this case, the rational solution is in place. // However, since the rational solution is no alias to current x, the imprecise solution is stored // in current x and and rational x is not an alias to x, we can swap the contents of currentX to x. std::swap(x, *currentX); } else { // Still, we may assume that the rational solution is not current x and is therefore new x. std::swap(rationalX, *rationalSolution); std::swap(x, *currentX); } } }; template template bool IterativeMinMaxLinearEquationSolver::solveEquationsRationalSearchHelper(Environment const& env, OptimizationDirection dir, IterativeMinMaxLinearEquationSolver const& impreciseSolver, storm::storage::SparseMatrix const& rationalA, std::vector& rationalX, std::vector const& rationalB, storm::storage::SparseMatrix const& A, std::vector& x, std::vector const& b, std::vector& tmpX) const { std::vector const* originalX = &x; std::vector* currentX = &x; std::vector* newX = &tmpX; SolverStatus status = SolverStatus::InProgress; uint64_t overallIterations = 0; uint64_t valueIterationInvocations = 0; ValueType precision = storm::utility::convertNumber(env.solver().minMax().getPrecision()); impreciseSolver.startMeasureProgress(); while (status == SolverStatus::InProgress && overallIterations < env.solver().minMax().getMaximalNumberOfIterations()) { // Perform value iteration with the current precision. typename IterativeMinMaxLinearEquationSolver::ValueIterationResult result = impreciseSolver.performValueIteration(env, dir, currentX, newX, b, storm::utility::convertNumber(precision), env.solver().minMax().getRelativeTerminationCriterion(), SolverGuarantee::LessOrEqual, overallIterations, env.solver().minMax().getMaximalNumberOfIterations(), env.solver().minMax().getMultiplicationStyle()); // At this point, the result of the imprecise value iteration is stored in the (imprecise) current x. ++valueIterationInvocations; STORM_LOG_TRACE("Completed " << valueIterationInvocations << " value iteration invocations, the last one with precision " << precision << " completed in " << result.iterations << " iterations."); // Count the iterations. overallIterations += result.iterations; // Compute maximal precision until which to sharpen. uint64_t p = storm::utility::convertNumber(storm::utility::ceil(storm::utility::log10(storm::utility::one() / precision))); // Make sure that currentX and rationalX are not aliased. std::vector* temporaryRational = TemporaryHelper::getTemporary(rationalX, currentX, newX); // Sharpen solution and place it in the temporary rational. bool foundSolution = sharpen(dir, p, rationalA, *currentX, rationalB, *temporaryRational); // After sharpen, if a solution was found, it is contained in the free rational. if (foundSolution) { status = SolverStatus::Converged; TemporaryHelper::swapSolutions(rationalX, temporaryRational, x, currentX, newX); } else { // Increase the precision. precision /= storm::utility::convertNumber(static_cast(10)); } } // Swap the two vectors if the current result is not in the original x. if (currentX != originalX) { std::swap(x, tmpX); } if (status == SolverStatus::InProgress && overallIterations == env.solver().minMax().getMaximalNumberOfIterations()) { status = SolverStatus::MaximalIterationsExceeded; } reportStatus(status, overallIterations); return status == SolverStatus::Converged || status == SolverStatus::TerminatedEarly; } template bool IterativeMinMaxLinearEquationSolver::solveEquationsRationalSearch(Environment const& env, OptimizationDirection dir, std::vector& x, std::vector const& b) const { return solveEquationsRationalSearchHelper(env, dir, x, b); } template void IterativeMinMaxLinearEquationSolver::computeOptimalValueForRowGroup(uint_fast64_t group, OptimizationDirection dir, std::vector& x, std::vector const& b, uint_fast64_t* choice) const { uint64_t row = this->A->getRowGroupIndices()[group]; uint64_t groupEnd = this->A->getRowGroupIndices()[group + 1]; assert(row != groupEnd); auto bIt = b.begin() + row; ValueType& xi = x[group]; xi = this->A->multiplyRowWithVector(row, x) + *bIt; uint64_t optimalRow = row; for (++row, ++bIt; row < groupEnd; ++row, ++bIt) { ValueType choiceVal = this->A->multiplyRowWithVector(row, x) + *bIt; if (minimize(dir)) { if (choiceVal < xi) { xi = choiceVal; optimalRow = row; } } else { if (choiceVal > xi) { xi = choiceVal; optimalRow = row; } } } if (choice != nullptr) { *choice = optimalRow - this->A->getRowGroupIndices()[group]; } } template SolverStatus IterativeMinMaxLinearEquationSolver::updateStatusIfNotConverged(SolverStatus status, std::vector const& x, uint64_t iterations, uint64_t maximalNumberOfIterations, SolverGuarantee const& guarantee) const { if (status != SolverStatus::Converged) { if (this->hasCustomTerminationCondition() && this->getTerminationCondition().terminateNow(x, guarantee)) { status = SolverStatus::TerminatedEarly; } else if (iterations >= maximalNumberOfIterations) { status = SolverStatus::MaximalIterationsExceeded; } } return status; } template void IterativeMinMaxLinearEquationSolver::reportStatus(SolverStatus status, uint64_t iterations) { switch (status) { case SolverStatus::Converged: STORM_LOG_TRACE("Iterative solver converged after " << iterations << " iterations."); break; case SolverStatus::TerminatedEarly: STORM_LOG_TRACE("Iterative solver terminated early after " << iterations << " iterations."); break; case SolverStatus::MaximalIterationsExceeded: STORM_LOG_WARN("Iterative solver did not converge after " << iterations << " iterations."); break; default: STORM_LOG_THROW(false, storm::exceptions::InvalidStateException, "Iterative solver terminated unexpectedly."); } } template void IterativeMinMaxLinearEquationSolver::clearCache() const { multiplierA.reset(); auxiliaryRowGroupVector.reset(); auxiliaryRowGroupVector2.reset(); soundValueIterationHelper.reset(); StandardMinMaxLinearEquationSolver::clearCache(); } template class IterativeMinMaxLinearEquationSolver; #ifdef STORM_HAVE_CARL template class IterativeMinMaxLinearEquationSolver; #endif } }