#include "storm/solver/StandardMinMaxLinearEquationSolver.h" #include "storm/settings/SettingsManager.h" #include "storm/settings/modules/MinMaxEquationSolverSettings.h" #include "storm/solver/GmmxxLinearEquationSolver.h" #include "storm/solver/EigenLinearEquationSolver.h" #include "storm/solver/NativeLinearEquationSolver.h" #include "storm/solver/EliminationLinearEquationSolver.h" #include "storm/utility/vector.h" #include "storm/utility/macros.h" #include "storm/exceptions/InvalidSettingsException.h" #include "storm/exceptions/InvalidStateException.h" #include "storm/exceptions/NotImplementedException.h" namespace storm { namespace solver { template StandardMinMaxLinearEquationSolverSettings::StandardMinMaxLinearEquationSolverSettings() { // Get the settings object to customize linear solving. storm::settings::modules::MinMaxEquationSolverSettings const& settings = storm::settings::getModule(); maximalNumberOfIterations = settings.getMaximalIterationCount(); precision = storm::utility::convertNumber(settings.getPrecision()); relative = settings.getConvergenceCriterion() == storm::settings::modules::MinMaxEquationSolverSettings::ConvergenceCriterion::Relative; auto method = settings.getMinMaxEquationSolvingMethod(); switch (method) { case MinMaxMethod::ValueIteration: this->solutionMethod = SolutionMethod::ValueIteration; break; case MinMaxMethod::PolicyIteration: this->solutionMethod = SolutionMethod::PolicyIteration; break; default: STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "Unsupported technique."); } } template void StandardMinMaxLinearEquationSolverSettings::setSolutionMethod(SolutionMethod const& solutionMethod) { this->solutionMethod = solutionMethod; } template void StandardMinMaxLinearEquationSolverSettings::setMaximalNumberOfIterations(uint64_t maximalNumberOfIterations) { this->maximalNumberOfIterations = maximalNumberOfIterations; } template void StandardMinMaxLinearEquationSolverSettings::setRelativeTerminationCriterion(bool value) { this->relative = value; } template void StandardMinMaxLinearEquationSolverSettings::setPrecision(ValueType precision) { this->precision = precision; } template typename StandardMinMaxLinearEquationSolverSettings::SolutionMethod const& StandardMinMaxLinearEquationSolverSettings::getSolutionMethod() const { return solutionMethod; } template uint64_t StandardMinMaxLinearEquationSolverSettings::getMaximalNumberOfIterations() const { return maximalNumberOfIterations; } template ValueType StandardMinMaxLinearEquationSolverSettings::getPrecision() const { return precision; } template bool StandardMinMaxLinearEquationSolverSettings::getRelativeTerminationCriterion() const { return relative; } template StandardMinMaxLinearEquationSolver::StandardMinMaxLinearEquationSolver(storm::storage::SparseMatrix const& A, std::unique_ptr>&& linearEquationSolverFactory, StandardMinMaxLinearEquationSolverSettings const& settings) : settings(settings), linearEquationSolverFactory(std::move(linearEquationSolverFactory)), localA(nullptr), A(A) { // Intentionally left empty. } template StandardMinMaxLinearEquationSolver::StandardMinMaxLinearEquationSolver(storm::storage::SparseMatrix&& A, std::unique_ptr>&& linearEquationSolverFactory, StandardMinMaxLinearEquationSolverSettings const& settings) : settings(settings), linearEquationSolverFactory(std::move(linearEquationSolverFactory)), localA(std::make_unique>(std::move(A))), A(*localA) { // Intentionally left empty. } template bool StandardMinMaxLinearEquationSolver::solveEquations(OptimizationDirection dir, std::vector& x, std::vector const& b) const { switch (this->getSettings().getSolutionMethod()) { case StandardMinMaxLinearEquationSolverSettings::SolutionMethod::ValueIteration: return solveEquationsValueIteration(dir, x, b); case StandardMinMaxLinearEquationSolverSettings::SolutionMethod::PolicyIteration: return solveEquationsPolicyIteration(dir, x, b); default: STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "This solver does not implement the selected solution method"); } return false; } template bool StandardMinMaxLinearEquationSolver::solveEquationsPolicyIteration(OptimizationDirection dir, std::vector& x, std::vector const& b) const { // Create the initial scheduler. std::vector scheduler = this->hasSchedulerHint() ? this->choicesHint.get() : std::vector(this->A.getRowGroupCount()); // 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; // Resolve the nondeterminism according to the current scheduler. storm::storage::SparseMatrix submatrix = this->A.selectRowsFromRowGroups(scheduler, true); submatrix.convertToEquationSystem(); storm::utility::vector::selectVectorValues(subB, scheduler, this->A.getRowGroupIndices(), b); // Create a solver that we will use throughout the procedure. We will modify the matrix in each iteration. auto solver = linearEquationSolverFactory->create(std::move(submatrix)); if (this->lowerBound) { solver->setLowerBound(this->lowerBound.get()); } if (this->upperBound) { solver->setUpperBound(this->upperBound.get()); } solver->setCachingEnabled(true); Status status = Status::InProgress; uint64_t iterations = 0; do { // Solve the equation system for the 'DTMC'. // FIXME: we need to remove the 0- and 1- states to make the solution unique. // HOWEVER: if we start with a valid scheduler, then we will never get an illegal one, because staying // within illegal MECs will never strictly improve the value. Is this true? solver->solveEquations(x, subB); // 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 = Status::Converged; } else { // Update the scheduler and the solver. submatrix = this->A.selectRowsFromRowGroups(scheduler, true); submatrix.convertToEquationSystem(); storm::utility::vector::selectVectorValues(subB, scheduler, this->A.getRowGroupIndices(), b); solver->setMatrix(std::move(submatrix)); } // Update environment variables. ++iterations; status = updateStatusIfNotConverged(status, x, iterations); } while (status == Status::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 == Status::Converged || status == Status::TerminatedEarly; } template bool StandardMinMaxLinearEquationSolver::valueImproved(OptimizationDirection dir, ValueType const& value1, ValueType const& value2) const { if (dir == OptimizationDirection::Minimize) { return value2 < value1; } else { return value2 > value1; } } template ValueType StandardMinMaxLinearEquationSolver::getPrecision() const { return this->getSettings().getPrecision(); } template bool StandardMinMaxLinearEquationSolver::getRelative() const { return this->getSettings().getRelativeTerminationCriterion(); } template bool StandardMinMaxLinearEquationSolver::solveEquationsValueIteration(OptimizationDirection dir, std::vector& x, std::vector const& b) const { if(!linEqSolverA) { linEqSolverA = linearEquationSolverFactory->create(A); linEqSolverA->setCachingEnabled(true); } if (!auxiliaryRowVector) { auxiliaryRowVector = std::make_unique>(A.getRowCount()); } std::vector& multiplyResult = *auxiliaryRowVector; if (!auxiliaryRowGroupVector) { auxiliaryRowGroupVector = std::make_unique>(A.getRowGroupCount()); } if (this->hasSchedulerHint()) { // Resolve the nondeterminism according to the scheduler hint storm::storage::SparseMatrix submatrix = this->A.selectRowsFromRowGroups(this->choicesHint.get(), true); submatrix.convertToEquationSystem(); storm::utility::vector::selectVectorValues(*auxiliaryRowGroupVector, this->choicesHint.get(), this->A.getRowGroupIndices(), b); // Solve the resulting equation system. // Note that the linEqSolver might consider a slightly different interpretation of "equalModuloPrecision". Hence, we iteratively increase its precision. auto submatrixSolver = linearEquationSolverFactory->create(std::move(submatrix)); submatrixSolver->setCachingEnabled(true); if (this->lowerBound) { submatrixSolver->setLowerBound(this->lowerBound.get()); } if (this->upperBound) { submatrixSolver->setUpperBound(this->upperBound.get()); } submatrixSolver->solveEquations(x, *auxiliaryRowGroupVector); } std::vector* newX = auxiliaryRowGroupVector.get(); std::vector* currentX = &x; // Proceed with the iterations as long as the method did not converge or reach the maximum number of iterations. uint64_t iterations = 0; Status status = Status::InProgress; while (status == Status::InProgress) { // Compute x' = A*x + b. linEqSolverA->multiply(*currentX, &b, multiplyResult); // Reduce the vector x' by applying min/max for all non-deterministic choices. storm::utility::vector::reduceVectorMinOrMax(dir, multiplyResult, *newX, this->A.getRowGroupIndices()); // Determine whether the method converged. if (storm::utility::vector::equalModuloPrecision(*currentX, *newX, this->getSettings().getPrecision(), this->getSettings().getRelativeTerminationCriterion())) { status = Status::Converged; } // Update environment variables. std::swap(currentX, newX); ++iterations; status = updateStatusIfNotConverged(status, *currentX, iterations); } reportStatus(status, iterations); // If we performed an odd number of iterations, we need to swap the x and currentX, because the newest result // is currently stored in currentX, but x is the output vector. if (currentX == auxiliaryRowGroupVector.get()) { std::swap(x, *currentX); } // If requested, we store the scheduler for retrieval. if (this->isTrackSchedulerSet()) { // Due to a custom termination condition, it may be the case that no iterations are performed. In this // case we need to compute x'= A*x+b once. if (iterations==0) { linEqSolverA->multiply(x, &b, multiplyResult); } this->schedulerChoices = std::vector(this->A.getRowGroupCount()); // Reduce the multiplyResult and keep track of the choices made storm::utility::vector::reduceVectorMinOrMax(dir, multiplyResult, x, this->A.getRowGroupIndices(), &this->schedulerChoices.get()); } if (!this->isCachingEnabled()) { clearCache(); } return status == Status::Converged || status == Status::TerminatedEarly; } template void StandardMinMaxLinearEquationSolver::repeatedMultiply(OptimizationDirection dir, std::vector& x, std::vector const* b, uint_fast64_t n) const { if(!linEqSolverA) { linEqSolverA = linearEquationSolverFactory->create(A); linEqSolverA->setCachingEnabled(true); } if (!auxiliaryRowVector) { auxiliaryRowVector = std::make_unique>(A.getRowCount()); } std::vector& multiplyResult = *auxiliaryRowVector; for (uint64_t i = 0; i < n; ++i) { linEqSolverA->multiply(x, b, multiplyResult); // Reduce the vector x' by applying min/max for all non-deterministic choices as given by the topmost // element of the min/max operator stack. storm::utility::vector::reduceVectorMinOrMax(dir, multiplyResult, x, this->A.getRowGroupIndices()); } if(!this->isCachingEnabled()) { clearCache(); } } template typename StandardMinMaxLinearEquationSolver::Status StandardMinMaxLinearEquationSolver::updateStatusIfNotConverged(Status status, std::vector const& x, uint64_t iterations) const { if (status != Status::Converged) { if (this->hasCustomTerminationCondition() && this->getTerminationCondition().terminateNow(x)) { status = Status::TerminatedEarly; } else if (iterations >= this->getSettings().getMaximalNumberOfIterations()) { status = Status::MaximalIterationsExceeded; } } return status; } template void StandardMinMaxLinearEquationSolver::reportStatus(Status status, uint64_t iterations) const { switch (status) { case Status::Converged: STORM_LOG_INFO("Iterative solver converged after " << iterations << " iterations."); break; case Status::TerminatedEarly: STORM_LOG_INFO("Iterative solver terminated early after " << iterations << " iterations."); break; case Status::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 StandardMinMaxLinearEquationSolverSettings const& StandardMinMaxLinearEquationSolver::getSettings() const { return settings; } template void StandardMinMaxLinearEquationSolver::setSettings(StandardMinMaxLinearEquationSolverSettings const& newSettings) { settings = newSettings; } template void StandardMinMaxLinearEquationSolver::clearCache() const { linEqSolverA.reset(); auxiliaryRowVector.reset(); auxiliaryRowGroupVector.reset(); MinMaxLinearEquationSolver::clearCache(); } template StandardMinMaxLinearEquationSolverFactory::StandardMinMaxLinearEquationSolverFactory(bool trackScheduler) : MinMaxLinearEquationSolverFactory(trackScheduler), linearEquationSolverFactory(nullptr) { // Intentionally left empty. } template StandardMinMaxLinearEquationSolverFactory::StandardMinMaxLinearEquationSolverFactory(std::unique_ptr>&& linearEquationSolverFactory, bool trackScheduler) : MinMaxLinearEquationSolverFactory(trackScheduler), linearEquationSolverFactory(std::move(linearEquationSolverFactory)) { // Intentionally left empty. } template StandardMinMaxLinearEquationSolverFactory::StandardMinMaxLinearEquationSolverFactory(EquationSolverType const& solverType, bool trackScheduler) : MinMaxLinearEquationSolverFactory(trackScheduler) { switch (solverType) { case EquationSolverType::Gmmxx: linearEquationSolverFactory = std::make_unique>(); break; case EquationSolverType::Eigen: linearEquationSolverFactory = std::make_unique>(); break; case EquationSolverType::Native: linearEquationSolverFactory = std::make_unique>(); break; case EquationSolverType::Elimination: linearEquationSolverFactory = std::make_unique>(); break; } } #ifdef STORM_HAVE_CARL template<> StandardMinMaxLinearEquationSolverFactory::StandardMinMaxLinearEquationSolverFactory(EquationSolverType const& solverType, bool trackScheduler) : MinMaxLinearEquationSolverFactory(trackScheduler) { switch (solverType) { case EquationSolverType::Eigen: linearEquationSolverFactory = std::make_unique>(); break; case EquationSolverType::Elimination: linearEquationSolverFactory = std::make_unique>(); break; default: STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "Cannot create the requested solver for this data type."); } } #endif template std::unique_ptr> StandardMinMaxLinearEquationSolverFactory::create(storm::storage::SparseMatrix const& matrix) const { std::unique_ptr> result; if (linearEquationSolverFactory) { result = std::make_unique>(matrix, linearEquationSolverFactory->clone(), settings); } else { result = std::make_unique>(matrix, std::make_unique>(), settings); } if (this->isTrackSchedulerSet()) { result->setTrackScheduler(true); } return result; } template std::unique_ptr> StandardMinMaxLinearEquationSolverFactory::create(storm::storage::SparseMatrix&& matrix) const { std::unique_ptr> result; if (linearEquationSolverFactory) { result = std::make_unique>(std::move(matrix), linearEquationSolverFactory->clone(), settings); } else { result = std::make_unique>(std::move(matrix), std::make_unique>(), settings); } if (this->isTrackSchedulerSet()) { result->setTrackScheduler(true); } return result; } template StandardMinMaxLinearEquationSolverSettings& StandardMinMaxLinearEquationSolverFactory::getSettings() { return settings; } template StandardMinMaxLinearEquationSolverSettings const& StandardMinMaxLinearEquationSolverFactory::getSettings() const { return settings; } template GmmxxMinMaxLinearEquationSolverFactory::GmmxxMinMaxLinearEquationSolverFactory(bool trackScheduler) : StandardMinMaxLinearEquationSolverFactory(EquationSolverType::Gmmxx, trackScheduler) { // Intentionally left empty. } template EigenMinMaxLinearEquationSolverFactory::EigenMinMaxLinearEquationSolverFactory(bool trackScheduler) : StandardMinMaxLinearEquationSolverFactory(EquationSolverType::Eigen, trackScheduler) { // Intentionally left empty. } template NativeMinMaxLinearEquationSolverFactory::NativeMinMaxLinearEquationSolverFactory(bool trackScheduler) : StandardMinMaxLinearEquationSolverFactory(EquationSolverType::Native, trackScheduler) { // Intentionally left empty. } template EliminationMinMaxLinearEquationSolverFactory::EliminationMinMaxLinearEquationSolverFactory(bool trackScheduler) : StandardMinMaxLinearEquationSolverFactory(EquationSolverType::Elimination, trackScheduler) { // Intentionally left empty. } template class StandardMinMaxLinearEquationSolverSettings; template class StandardMinMaxLinearEquationSolver; template class StandardMinMaxLinearEquationSolverFactory; template class GmmxxMinMaxLinearEquationSolverFactory; template class EigenMinMaxLinearEquationSolverFactory; template class NativeMinMaxLinearEquationSolverFactory; template class EliminationMinMaxLinearEquationSolverFactory; #ifdef STORM_HAVE_CARL template class StandardMinMaxLinearEquationSolverSettings; template class StandardMinMaxLinearEquationSolver; template class StandardMinMaxLinearEquationSolverFactory; template class EigenMinMaxLinearEquationSolverFactory; template class EliminationMinMaxLinearEquationSolverFactory; #endif } }