#include "storm/solver/SymbolicMinMaxLinearEquationSolver.h" #include "storm/storage/dd/DdManager.h" #include "storm/storage/dd/Add.h" #include "storm/storage/dd/Bdd.h" #include "storm/utility/constants.h" #include "storm/environment/solver/MinMaxSolverEnvironment.h" #include "storm/utility/dd.h" #include "storm/utility/macros.h" #include "storm/exceptions/InvalidEnvironmentException.h" #include "storm/exceptions/PrecisionExceededException.h" namespace storm { namespace solver { template SymbolicMinMaxLinearEquationSolver::SymbolicMinMaxLinearEquationSolver() : SymbolicEquationSolver(), uniqueSolution(false), requirementsChecked(false) { // Intentionally left empty. } template SymbolicMinMaxLinearEquationSolver::SymbolicMinMaxLinearEquationSolver(storm::dd::Add const& A, storm::dd::Bdd const& allRows, storm::dd::Bdd const& illegalMask, std::set const& rowMetaVariables, std::set const& columnMetaVariables, std::set const& choiceVariables, std::vector> const& rowColumnMetaVariablePairs, std::unique_ptr>&& linearEquationSolverFactory) : SymbolicEquationSolver(allRows), A(A), illegalMask(illegalMask), illegalMaskAdd(illegalMask.ite(A.getDdManager().getConstant(storm::utility::infinity()), A.getDdManager().template getAddZero())), rowMetaVariables(rowMetaVariables), columnMetaVariables(columnMetaVariables), choiceVariables(choiceVariables), rowColumnMetaVariablePairs(rowColumnMetaVariablePairs), linearEquationSolverFactory(std::move(linearEquationSolverFactory)), uniqueSolution(false), requirementsChecked(false) { // Intentionally left empty. } template MinMaxMethod SymbolicMinMaxLinearEquationSolver::getMethod(Environment const& env, bool isExactMode) const { // Adjust the method if none was specified and we are using rational numbers. auto method = env.solver().minMax().getMethod(); if (isExactMode && method != MinMaxMethod::RationalSearch) { if (env.solver().minMax().isMethodSetFromDefault()) { STORM_LOG_INFO("Selecting 'rational search' as the solution technique to guarantee exact results. If you want to override this, please explicitly specify a different method."); method = MinMaxMethod::RationalSearch; } else { STORM_LOG_WARN("The selected solution method does not guarantee exact results."); } } STORM_LOG_THROW(method == MinMaxMethod::ValueIteration || method == MinMaxMethod::PolicyIteration || method == MinMaxMethod::RationalSearch, storm::exceptions::InvalidEnvironmentException, "This solver does not support the selected method."); return method; } template storm::dd::Add SymbolicMinMaxLinearEquationSolver::solveEquations(Environment const& env, storm::solver::OptimizationDirection const& dir, storm::dd::Add const& x, storm::dd::Add const& b) const { STORM_LOG_WARN_COND_DEBUG(this->isRequirementsCheckedSet(), "The requirements of the solver have not been marked as checked. Please provide the appropriate check or mark the requirements as checked (if applicable)."); switch (getMethod(env, std::is_same::value)) { case MinMaxMethod::ValueIteration: return solveEquationsValueIteration(env, dir, x, b); break; case MinMaxMethod::PolicyIteration: return solveEquationsPolicyIteration(env, dir, x, b); break; case MinMaxMethod::RationalSearch: return solveEquationsRationalSearch(env, dir, x, b); break; default: STORM_LOG_THROW(false, storm::exceptions::InvalidEnvironmentException, "The selected min max technique is not supported by this solver."); } return storm::dd::Add(); } template typename SymbolicMinMaxLinearEquationSolver::ValueIterationResult SymbolicMinMaxLinearEquationSolver::performValueIteration(storm::solver::OptimizationDirection const& dir, storm::dd::Add const& x, storm::dd::Add const& b, ValueType const& precision, bool relativeTerminationCriterion, uint64_t maximalIterations) const { // Set up local variables. storm::dd::Add localX = x; uint64_t iterations = 0; // Value iteration loop. SolverStatus status = SolverStatus::InProgress; while (status == SolverStatus::InProgress && iterations < maximalIterations) { // Compute tmp = A * x + b storm::dd::Add localXAsColumn = localX.swapVariables(this->rowColumnMetaVariablePairs); storm::dd::Add tmp = this->A.multiplyMatrix(localXAsColumn, this->columnMetaVariables); tmp += b; if (dir == storm::solver::OptimizationDirection::Minimize) { tmp += illegalMaskAdd; tmp = tmp.minAbstract(this->choiceVariables); } else { tmp = tmp.maxAbstract(this->choiceVariables); } // Now check if the process already converged within our precision. if (localX.equalModuloPrecision(tmp, precision, relativeTerminationCriterion)) { status = SolverStatus::Converged; } // Set up next iteration. localX = tmp; ++iterations; } if (status != SolverStatus::Converged) { status = SolverStatus::MaximalIterationsExceeded; } return SymbolicMinMaxLinearEquationSolver::ValueIterationResult(status, iterations, localX); } template bool SymbolicMinMaxLinearEquationSolver::isSolution(OptimizationDirection dir, storm::dd::Add const& x, storm::dd::Add const& b) const { storm::dd::Add xAsColumn = x.swapVariables(this->rowColumnMetaVariablePairs); storm::dd::Add tmp = this->A.multiplyMatrix(xAsColumn, this->columnMetaVariables); tmp += b; if (dir == storm::solver::OptimizationDirection::Minimize) { tmp += illegalMaskAdd; tmp = tmp.minAbstract(this->choiceVariables); } else { tmp = tmp.maxAbstract(this->choiceVariables); } return x == tmp; } template template storm::dd::Add SymbolicMinMaxLinearEquationSolver::sharpen(OptimizationDirection dir, uint64_t precision, SymbolicMinMaxLinearEquationSolver const& rationalSolver, storm::dd::Add const& x, storm::dd::Add const& rationalB, bool& isSolution) { storm::dd::Add sharpenedX; for (uint64_t p = 1; p < precision; ++p) { sharpenedX = x.sharpenKwekMehlhorn(p); isSolution = rationalSolver.isSolution(dir, sharpenedX, rationalB); if (isSolution) { break; } } return sharpenedX; } template template storm::dd::Add SymbolicMinMaxLinearEquationSolver::solveEquationsRationalSearchHelper(Environment const& env, OptimizationDirection dir, SymbolicMinMaxLinearEquationSolver const& rationalSolver, SymbolicMinMaxLinearEquationSolver const& impreciseSolver, storm::dd::Add const& rationalB, storm::dd::Add const& x, storm::dd::Add const& b) const { // Storage for the rational sharpened vector and the power iteration intermediate vector. storm::dd::Add sharpenedX; storm::dd::Add currentX = x; // The actual rational search. uint64_t overallIterations = 0; uint64_t valueIterationInvocations = 0; ValueType precision = storm::utility::convertNumber(env.solver().minMax().getPrecision()); uint64_t maxIter = env.solver().minMax().getMaximalNumberOfIterations(); bool relative = env.solver().minMax().getRelativeTerminationCriterion(); SolverStatus status = SolverStatus::InProgress; while (status == SolverStatus::InProgress && overallIterations < maxIter) { typename SymbolicMinMaxLinearEquationSolver::ValueIterationResult viResult = impreciseSolver.performValueIteration(dir, currentX, b, storm::utility::convertNumber(precision), relative, maxIter); ++valueIterationInvocations; STORM_LOG_TRACE("Completed " << valueIterationInvocations << " value iteration invocations, the last one with precision " << precision << " completed in " << viResult.iterations << " iterations."); // Count the iterations. overallIterations += viResult.iterations; // Compute maximal precision until which to sharpen. uint64_t p = storm::utility::convertNumber(storm::utility::ceil(storm::utility::log10(storm::utility::one() / precision))); bool isSolution = false; sharpenedX = sharpen(dir, p, rationalSolver, viResult.values, rationalB, isSolution); if (isSolution) { status = SolverStatus::Converged; } else { currentX = viResult.values; precision /= storm::utility::convertNumber(10); } } if (status == SolverStatus::InProgress) { status = SolverStatus::MaximalIterationsExceeded; } if (status == SolverStatus::Converged) { STORM_LOG_INFO("Iterative solver (rational search) converged in " << overallIterations << " iterations."); } else { STORM_LOG_WARN("Iterative solver (rational search) did not converge in " << overallIterations << " iterations."); } return sharpenedX; } template template typename std::enable_if::value && storm::NumberTraits::IsExact, storm::dd::Add>::type SymbolicMinMaxLinearEquationSolver::solveEquationsRationalSearchHelper(Environment const& env, storm::solver::OptimizationDirection const& dir, storm::dd::Add const& x, storm::dd::Add const& b) const { return solveEquationsRationalSearchHelper(env, dir, *this, *this, b, this->getLowerBoundsVector(), b); } template template typename std::enable_if::value && !storm::NumberTraits::IsExact, storm::dd::Add>::type SymbolicMinMaxLinearEquationSolver::solveEquationsRationalSearchHelper(Environment const& env, storm::solver::OptimizationDirection const& dir, storm::dd::Add const& x, storm::dd::Add const& b) const { storm::dd::Add rationalB = b.template toValueType(); SymbolicMinMaxLinearEquationSolver rationalSolver(this->A.template toValueType(), this->allRows, this->illegalMask, this->rowMetaVariables, this->columnMetaVariables, this->choiceVariables, this->rowColumnMetaVariablePairs, std::make_unique>()); storm::dd::Add rationalResult = solveEquationsRationalSearchHelper(env, dir, rationalSolver, *this, rationalB, this->getLowerBoundsVector(), b); return rationalResult.template toValueType(); } template template typename std::enable_if::value, storm::dd::Add>::type SymbolicMinMaxLinearEquationSolver::solveEquationsRationalSearchHelper(Environment const& env, storm::solver::OptimizationDirection const& dir, storm::dd::Add const& x, storm::dd::Add const& b) const { // First try to find a solution using the imprecise value type. storm::dd::Add rationalResult; storm::dd::Add impreciseX; try { impreciseX = this->getLowerBoundsVector().template toValueType(); storm::dd::Add impreciseB = b.template toValueType(); SymbolicMinMaxLinearEquationSolver impreciseSolver(this->A.template toValueType(), this->allRows, this->illegalMask, this->rowMetaVariables, this->columnMetaVariables, this->choiceVariables, this->rowColumnMetaVariablePairs, std::make_unique>()); rationalResult = solveEquationsRationalSearchHelper(env, dir, *this, impreciseSolver, b, impreciseX, impreciseB); } catch (storm::exceptions::PrecisionExceededException const& e) { STORM_LOG_WARN("Precision of value type was exceeded, trying to recover by switching to rational arithmetic."); // Fall back to precise value type if the precision of the imprecise value type was exceeded. rationalResult = solveEquationsRationalSearchHelper(env, dir, *this, *this, b, impreciseX.template toValueType(), b); } return rationalResult.template toValueType(); } template storm::dd::Add SymbolicMinMaxLinearEquationSolver::solveEquationsRationalSearch(Environment const& env, storm::solver::OptimizationDirection const& dir, storm::dd::Add const& x, storm::dd::Add const& b) const { return solveEquationsRationalSearchHelper(env, dir, x, b); } template storm::dd::Add SymbolicMinMaxLinearEquationSolver::solveEquationsValueIteration(Environment const& env, storm::solver::OptimizationDirection const& dir, storm::dd::Add const& x, storm::dd::Add const& b) const { // Set up the environment. storm::dd::Add localX; if (this->hasUniqueSolution()) { localX = x; } else { // If we were given an initial scheduler, we take its solution as the starting point. if (this->hasInitialScheduler()) { // 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; localX = solveEquationsWithScheduler(environmentOfSolver, this->getInitialScheduler(), x, b); } else { localX = this->getLowerBoundsVector(); } } ValueType precision = storm::utility::convertNumber(env.solver().minMax().getPrecision()); ValueIterationResult viResult = performValueIteration(dir, localX, b, precision, env.solver().minMax().getRelativeTerminationCriterion(), env.solver().minMax().getMaximalNumberOfIterations()); if (viResult.status == SolverStatus::Converged) { STORM_LOG_INFO("Iterative solver (value iteration) converged in " << viResult.iterations << " iterations."); } else { STORM_LOG_WARN("Iterative solver (value iteration) did not converge in " << viResult.iterations << " iterations."); } return viResult.values; } template storm::dd::Add SymbolicMinMaxLinearEquationSolver::solveEquationsWithScheduler(Environment const& env, storm::dd::Bdd const& scheduler, storm::dd::Add const& x, storm::dd::Add const& b) const { std::unique_ptr> solver = linearEquationSolverFactory->create(env, this->allRows, this->rowMetaVariables, this->columnMetaVariables, this->rowColumnMetaVariablePairs); this->forwardBounds(*solver); storm::dd::Add diagonal = (storm::utility::dd::getRowColumnDiagonal(x.getDdManager(), this->rowColumnMetaVariablePairs) && this->allRows).template toAdd(); return solveEquationsWithScheduler(env, *solver, scheduler, x, b, diagonal); } template storm::dd::Add SymbolicMinMaxLinearEquationSolver::solveEquationsWithScheduler(Environment const& env, SymbolicLinearEquationSolver& solver, storm::dd::Bdd const& scheduler, storm::dd::Add const& x, storm::dd::Add const& b, storm::dd::Add const& diagonal) const { // Apply scheduler to the matrix and vector. storm::dd::Add schedulerA = scheduler.ite(this->A, scheduler.getDdManager().template getAddZero()).sumAbstract(this->choiceVariables); if (solver.getEquationProblemFormat(env) == storm::solver::LinearEquationSolverProblemFormat::EquationSystem) { schedulerA = diagonal - schedulerA; } storm::dd::Add schedulerB = scheduler.ite(b, scheduler.getDdManager().template getAddZero()).sumAbstract(this->choiceVariables); // Set the matrix for the solver. solver.setMatrix(schedulerA); // Solve for the value of the scheduler. storm::dd::Add schedulerX = solver.solveEquations(env, x, schedulerB); return schedulerX; } template storm::dd::Add SymbolicMinMaxLinearEquationSolver::solveEquationsPolicyIteration(Environment const& env, storm::solver::OptimizationDirection const& dir, storm::dd::Add const& x, storm::dd::Add const& b) const { // Set up the environment. storm::dd::Add currentSolution = x; storm::dd::Add diagonal = (storm::utility::dd::getRowColumnDiagonal(x.getDdManager(), this->rowColumnMetaVariablePairs) && this->allRows).template toAdd(); uint_fast64_t iterations = 0; bool converged = false; // Choose initial scheduler. storm::dd::Bdd scheduler = this->hasInitialScheduler() ? this->getInitialScheduler() : (this->A.sumAbstract(this->columnMetaVariables).notZero() || b.notZero()).existsAbstractRepresentative(this->choiceVariables); // Initialize linear equation solver. // It 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; std::unique_ptr> linearEquationSolver = linearEquationSolverFactory->create(environmentOfSolver, this->allRows, this->rowMetaVariables, this->columnMetaVariables, this->rowColumnMetaVariablePairs); this->forwardBounds(*linearEquationSolver); // Iteratively solve and improve the scheduler. uint64_t maxIter = env.solver().minMax().getMaximalNumberOfIterations(); while (!converged && iterations < maxIter) { storm::dd::Add schedulerX = solveEquationsWithScheduler(environmentOfSolver, *linearEquationSolver, scheduler, currentSolution, b, diagonal); // Policy improvement step. storm::dd::Add choiceValues = this->A.multiplyMatrix(schedulerX.swapVariables(this->rowColumnMetaVariablePairs), this->columnMetaVariables) + b; storm::dd::Bdd nextScheduler; if (dir == storm::solver::OptimizationDirection::Minimize) { choiceValues += illegalMaskAdd; storm::dd::Add newStateValues = choiceValues.minAbstract(this->choiceVariables); storm::dd::Bdd improvedStates = newStateValues.less(schedulerX); nextScheduler = improvedStates.ite(choiceValues.minAbstractRepresentative(this->choiceVariables), scheduler); } else { storm::dd::Add newStateValues = choiceValues.maxAbstract(this->choiceVariables); storm::dd::Bdd improvedStates = newStateValues.greater(schedulerX); nextScheduler = improvedStates.ite(choiceValues.maxAbstractRepresentative(this->choiceVariables), scheduler); } // Check for convergence. converged = nextScheduler == scheduler; // Set up next iteration. if (!converged) { scheduler = nextScheduler; } currentSolution = schedulerX; ++iterations; } if (converged) { STORM_LOG_INFO("Iterative solver (policy iteration) converged in " << iterations << " iterations."); } else { STORM_LOG_WARN("Iterative solver (policy iteration) did not converge in " << iterations << " iterations."); } return currentSolution; } template storm::dd::Add SymbolicMinMaxLinearEquationSolver::multiply(storm::solver::OptimizationDirection const& dir, storm::dd::Add const& x, storm::dd::Add const* b, uint_fast64_t n) const { storm::dd::Add xCopy = x; // Perform matrix-vector multiplication while the bound is met. for (uint_fast64_t i = 0; i < n; ++i) { xCopy = xCopy.swapVariables(this->rowColumnMetaVariablePairs); xCopy = this->A.multiplyMatrix(xCopy, this->columnMetaVariables); if (b != nullptr) { xCopy += *b; } if (dir == storm::solver::OptimizationDirection::Minimize) { // This is a hack and only here because of the lack of a suitable minAbstract/maxAbstract function // that can properly deal with a restriction of the choices. xCopy += illegalMaskAdd; xCopy = xCopy.minAbstract(this->choiceVariables); } else { xCopy = xCopy.maxAbstract(this->choiceVariables); } } return xCopy; } template void SymbolicMinMaxLinearEquationSolver::setInitialScheduler(storm::dd::Bdd const& scheduler) { this->initialScheduler = scheduler; } template storm::dd::Bdd const& SymbolicMinMaxLinearEquationSolver::getInitialScheduler() const { return initialScheduler.get(); } template bool SymbolicMinMaxLinearEquationSolver::hasInitialScheduler() const { return static_cast(initialScheduler); } template MinMaxLinearEquationSolverRequirements SymbolicMinMaxLinearEquationSolver::getRequirements(Environment const& env, boost::optional const& direction) const { MinMaxLinearEquationSolverRequirements requirements; auto method = getMethod(env, std::is_same::value); if (method == MinMaxMethod::PolicyIteration) { if (!this->hasUniqueSolution()) { requirements.requireValidInitialScheduler(); } } else if (method == MinMaxMethod::ValueIteration) { if (!this->hasUniqueSolution()) { if (!direction || direction.get() == storm::solver::OptimizationDirection::Maximize) { requirements.requireLowerBounds(); } if (!direction || direction.get() == storm::solver::OptimizationDirection::Minimize) { requirements.requireValidInitialScheduler(); } } } else if (method == MinMaxMethod::RationalSearch) { requirements.requireLowerBounds(); if (!this->hasUniqueSolution() && (!direction || direction.get() == storm::solver::OptimizationDirection::Minimize)) { requirements.requireNoEndComponents(); } } else { STORM_LOG_THROW(false, storm::exceptions::InvalidEnvironmentException, "The selected min max technique is not supported by this solver."); } return requirements; } template void SymbolicMinMaxLinearEquationSolver::setHasUniqueSolution(bool value) { this->uniqueSolution = value; } template bool SymbolicMinMaxLinearEquationSolver::hasUniqueSolution() const { return this->uniqueSolution; } template void SymbolicMinMaxLinearEquationSolver::setRequirementsChecked(bool value) { this->requirementsChecked = value; } template bool SymbolicMinMaxLinearEquationSolver::isRequirementsCheckedSet() const { return this->requirementsChecked; } template void SymbolicMinMaxLinearEquationSolver::forwardBounds(storm::solver::SymbolicLinearEquationSolver& solver) const { if (this->hasLowerBound()) { solver.setLowerBound(this->getLowerBound()); } if (this->hasLowerBounds()) { solver.setLowerBounds(this->getLowerBounds()); } if (this->hasUpperBound()) { solver.setUpperBound(this->getUpperBound()); } if (this->hasUpperBounds()) { solver.setUpperBounds(this->getUpperBounds()); } } template MinMaxLinearEquationSolverRequirements SymbolicMinMaxLinearEquationSolverFactory::getRequirements(Environment const& env, bool hasUniqueSolution, boost::optional const& direction) const { std::unique_ptr> solver = this->create(); solver->setHasUniqueSolution(hasUniqueSolution); return solver->getRequirements(env, direction); } template std::unique_ptr> GeneralSymbolicMinMaxLinearEquationSolverFactory::create(storm::dd::Add const& A, storm::dd::Bdd const& allRows, storm::dd::Bdd const& illegalMask, std::set const& rowMetaVariables, std::set const& columnMetaVariables, std::set const& choiceVariables, std::vector> const& rowColumnMetaVariablePairs) const { return std::make_unique>(A, allRows, illegalMask, rowMetaVariables, columnMetaVariables, choiceVariables, rowColumnMetaVariablePairs, std::make_unique>()); } template std::unique_ptr> GeneralSymbolicMinMaxLinearEquationSolverFactory::create() const { return std::make_unique>(); } template class SymbolicMinMaxLinearEquationSolver; template class SymbolicMinMaxLinearEquationSolver; template class SymbolicMinMaxLinearEquationSolver; template class GeneralSymbolicMinMaxLinearEquationSolverFactory; template class GeneralSymbolicMinMaxLinearEquationSolverFactory; template class GeneralSymbolicMinMaxLinearEquationSolverFactory; } }