#include "src/solver/NativeLinearEquationSolver.h" #include #include "src/settings/SettingsManager.h" #include "src/settings/modules/NativeEquationSolverSettings.h" #include "src/utility/vector.h" #include "src/exceptions/InvalidStateException.h" #include "src/exceptions/InvalidSettingsException.h" namespace storm { namespace solver { template NativeLinearEquationSolverSettings::NativeLinearEquationSolverSettings() { storm::settings::modules::NativeEquationSolverSettings const& settings = storm::settings::getModule(); storm::settings::modules::NativeEquationSolverSettings::LinearEquationMethod methodAsSetting = settings.getLinearEquationSystemMethod(); if (methodAsSetting == storm::settings::modules::NativeEquationSolverSettings::LinearEquationMethod::GaussSeidel) { method = SolutionMethod::GaussSeidel; } else if (methodAsSetting == storm::settings::modules::NativeEquationSolverSettings::LinearEquationMethod::Jacobi) { method = SolutionMethod::Jacobi; } else if (methodAsSetting == storm::settings::modules::NativeEquationSolverSettings::LinearEquationMethod::SOR) { method = SolutionMethod::SOR; } else { STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "The selected solution technique is invalid for this solver."); } maximalNumberOfIterations = settings.getMaximalIterationCount(); precision = settings.getPrecision(); relative = settings.getConvergenceCriterion() == storm::settings::modules::NativeEquationSolverSettings::ConvergenceCriterion::Relative; omega = storm::settings::getModule().getOmega(); } template void NativeLinearEquationSolverSettings::setSolutionMethod(SolutionMethod const& method) { this->method = method; } template void NativeLinearEquationSolverSettings::setPrecision(ValueType precision) { this->precision = precision; } template void NativeLinearEquationSolverSettings::setMaximalNumberOfIterations(uint64_t maximalNumberOfIterations) { this->maximalNumberOfIterations = maximalNumberOfIterations; } template void NativeLinearEquationSolverSettings::setRelativeTerminationCriterion(bool value) { this->relative = value; } template void NativeLinearEquationSolverSettings::setOmega(ValueType omega) { this->omega = omega; } template typename NativeLinearEquationSolverSettings::SolutionMethod NativeLinearEquationSolverSettings::getSolutionMethod() const { return method; } template ValueType NativeLinearEquationSolverSettings::getPrecision() const { return precision; } template uint64_t NativeLinearEquationSolverSettings::getMaximalNumberOfIterations() const { return maximalNumberOfIterations; } template uint64_t NativeLinearEquationSolverSettings::getRelativeTerminationCriterion() const { return relative; } template ValueType NativeLinearEquationSolverSettings::getOmega() const { return omega; } template NativeLinearEquationSolver::NativeLinearEquationSolver(storm::storage::SparseMatrix const& A, NativeLinearEquationSolverSettings const& settings) : localA(nullptr), A(A), settings(settings) { // Intentionally left empty. } template NativeLinearEquationSolver::NativeLinearEquationSolver(storm::storage::SparseMatrix&& A, NativeLinearEquationSolverSettings const& settings) : localA(std::make_unique>(std::move(A))), A(*localA), settings(settings) { // Intentionally left empty. } template void NativeLinearEquationSolver::solveEquationSystem(std::vector& x, std::vector const& b, std::vector* multiplyResult) const { if (this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::SOR || this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::GaussSeidel) { // Define the omega used for SOR. ValueType omega = this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::SOR ? this->getSettings().getOmega() : storm::utility::one(); // To avoid copying the contents of the vector in the loop, we create a temporary x to swap with. bool tmpXProvided = true; std::vector* tmpX = multiplyResult; if (multiplyResult == nullptr) { tmpX = new std::vector(x); tmpXProvided = false; } else { *tmpX = x; } // Set up additional environment variables. uint_fast64_t iterationCount = 0; bool converged = false; while (!converged && iterationCount < this->getSettings().getMaximalNumberOfIterations()) { A.performSuccessiveOverRelaxationStep(omega, x, b); // Now check if the process already converged within our precision. converged = storm::utility::vector::equalModuloPrecision(x, *tmpX, static_cast(this->getSettings().getPrecision()), this->getSettings().getRelativeTerminationCriterion()) || (this->hasCustomTerminationCondition() && this->getTerminationCondition().terminateNow(x)); // If we did not yet converge, we need to copy the contents of x to *tmpX. if (!converged) { *tmpX = x; } // Increase iteration count so we can abort if convergence is too slow. ++iterationCount; } // If the vector for the temporary multiplication result was not provided, we need to delete it. if (!tmpXProvided) { delete tmpX; } } else { // Get a Jacobi decomposition of the matrix A. std::pair, std::vector> jacobiDecomposition = A.getJacobiDecomposition(); // To avoid copying the contents of the vector in the loop, we create a temporary x to swap with. bool multiplyResultProvided = true; std::vector* nextX = multiplyResult; if (nextX == nullptr) { nextX = new std::vector(x.size()); multiplyResultProvided = false; } std::vector const* copyX = nextX; std::vector* currentX = &x; // Target vector for multiplication. std::vector tmpX(x.size()); // Set up additional environment variables. uint_fast64_t iterationCount = 0; bool converged = false; while (!converged && iterationCount < this->getSettings().getMaximalNumberOfIterations() && !(this->hasCustomTerminationCondition() && this->getTerminationCondition().terminateNow(*currentX))) { // Compute D^-1 * (b - LU * x) and store result in nextX. jacobiDecomposition.first.multiplyWithVector(*currentX, tmpX); storm::utility::vector::subtractVectors(b, tmpX, tmpX); storm::utility::vector::multiplyVectorsPointwise(jacobiDecomposition.second, tmpX, *nextX); // Swap the two pointers as a preparation for the next iteration. std::swap(nextX, currentX); // Now check if the process already converged within our precision. converged = storm::utility::vector::equalModuloPrecision(*currentX, *nextX, static_cast(this->getSettings().getPrecision()), this->getSettings().getRelativeTerminationCriterion()); // Increase iteration count so we can abort if convergence is too slow. ++iterationCount; } // If the last iteration did not write to the original x we have to swap the contents, because the // output has to be written to the input parameter x. if (currentX == copyX) { std::swap(x, *currentX); } // If the vector for the temporary multiplication result was not provided, we need to delete it. if (!multiplyResultProvided) { delete copyX; } } } template void NativeLinearEquationSolver::performMatrixVectorMultiplication(std::vector& x, std::vector const* b, uint_fast64_t n, std::vector* multiplyResult) const { // Set up some temporary variables so that we can just swap pointers instead of copying the result after // each iteration. std::vector* currentX = &x; bool multiplyResultProvided = true; std::vector* nextX = multiplyResult; if (nextX == nullptr) { nextX = new std::vector(x.size()); multiplyResultProvided = false; } std::vector const* copyX = nextX; // Now perform matrix-vector multiplication as long as we meet the bound. for (uint_fast64_t i = 0; i < n; ++i) { A.multiplyWithVector(*currentX, *nextX); std::swap(nextX, currentX); // If requested, add an offset to the current result vector. if (b != nullptr) { storm::utility::vector::addVectors(*currentX, *b, *currentX); } } // If we performed an odd number of repetitions, we need to swap the contents of currentVector and x, // because the output is supposed to be stored in the input vector x. if (currentX == copyX) { std::swap(x, *currentX); } // If the vector for the temporary multiplication result was not provided, we need to delete it. if (!multiplyResultProvided) { delete copyX; } } template NativeLinearEquationSolverSettings& NativeLinearEquationSolver::getSettings() { return settings; } template NativeLinearEquationSolverSettings const& NativeLinearEquationSolver::getSettings() const { return settings; } template std::unique_ptr> NativeLinearEquationSolverFactory::create(storm::storage::SparseMatrix const& matrix) const { return std::make_unique>(matrix, settings); } template std::unique_ptr> NativeLinearEquationSolverFactory::create(storm::storage::SparseMatrix&& matrix) const { return std::make_unique>(std::move(matrix), settings); } template NativeLinearEquationSolverSettings& NativeLinearEquationSolverFactory::getSettings() { return settings; } template NativeLinearEquationSolverSettings const& NativeLinearEquationSolverFactory::getSettings() const { return settings; } // Explicitly instantiate the linear equation solver. template class NativeLinearEquationSolverSettings; template class NativeLinearEquationSolver; template class NativeLinearEquationSolverFactory; } }