#include "storm/solver/NativeLinearEquationSolver.h" #include #include "storm/settings/SettingsManager.h" #include "storm/settings/modules/GeneralSettings.h" #include "storm/settings/modules/NativeEquationSolverSettings.h" #include "storm/utility/constants.h" #include "storm/utility/vector.h" #include "storm/exceptions/InvalidStateException.h" #include "storm/exceptions/InvalidSettingsException.h" #include "storm/exceptions/UnmetRequirementException.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 if (methodAsSetting == storm::settings::modules::NativeEquationSolverSettings::LinearEquationMethod::WalkerChae) { method = SolutionMethod::WalkerChae; } else if (methodAsSetting == storm::settings::modules::NativeEquationSolverSettings::LinearEquationMethod::Power) { method = SolutionMethod::Power; } 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 = settings.getOmega(); multiplicationStyle = settings.getPowerMethodMultiplicationStyle(); // Finally force soundness and potentially overwrite some other settings. this->setForceSoundness(storm::settings::getModule().isSoundSet()); } template void NativeLinearEquationSolverSettings::setSolutionMethod(SolutionMethod const& method) { this->method = method; // Make sure we switch the method if we have to guarantee soundness. this->setForceSoundness(forceSoundness); } 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 void NativeLinearEquationSolverSettings::setPowerMethodMultiplicationStyle(MultiplicationStyle value) { this->multiplicationStyle = value; } template void NativeLinearEquationSolverSettings::setForceSoundness(bool value) { forceSoundness = value; if (forceSoundness) { STORM_LOG_WARN_COND(method != SolutionMethod::Power, "To guarantee soundness, the equation solving technique has been switched to '" << storm::settings::modules::NativeEquationSolverSettings::LinearEquationMethod::Power << "'."); method = SolutionMethod::Power; } } 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 MultiplicationStyle NativeLinearEquationSolverSettings::getPowerMethodMultiplicationStyle() const { return multiplicationStyle; } template bool NativeLinearEquationSolverSettings::getForceSoundness() const { return forceSoundness; } template NativeLinearEquationSolver::NativeLinearEquationSolver(NativeLinearEquationSolverSettings const& settings) : localA(nullptr), A(nullptr), settings(settings) { // Intentionally left empty. } template NativeLinearEquationSolver::NativeLinearEquationSolver(storm::storage::SparseMatrix const& A, NativeLinearEquationSolverSettings const& settings) : localA(nullptr), A(nullptr), settings(settings) { this->setMatrix(A); } template NativeLinearEquationSolver::NativeLinearEquationSolver(storm::storage::SparseMatrix&& A, NativeLinearEquationSolverSettings const& settings) : localA(nullptr), A(nullptr), settings(settings) { this->setMatrix(std::move(A)); } template void NativeLinearEquationSolver::setMatrix(storm::storage::SparseMatrix const& A) { localA.reset(); this->A = &A; clearCache(); } template void NativeLinearEquationSolver::setMatrix(storm::storage::SparseMatrix&& A) { localA = std::make_unique>(std::move(A)); this->A = localA.get(); clearCache(); } template bool NativeLinearEquationSolver::solveEquationsSOR(std::vector& x, std::vector const& b, ValueType const& omega) const { STORM_LOG_INFO("Solving linear equation system (" << x.size() << " rows) with NativeLinearEquationSolver (Gauss-Seidel, SOR omega = " << omega << ")"); if (!this->cachedRowVector) { this->cachedRowVector = std::make_unique>(getMatrixRowCount()); } // Set up additional environment variables. uint_fast64_t iterations = 0; bool converged = false; bool terminate = false; while (!converged && !terminate && iterations < this->getSettings().getMaximalNumberOfIterations()) { A->performSuccessiveOverRelaxationStep(omega, x, b); // Now check if the process already converged within our precision. converged = storm::utility::vector::equalModuloPrecision(*this->cachedRowVector, x, static_cast(this->getSettings().getPrecision()), this->getSettings().getRelativeTerminationCriterion()); terminate = this->terminateNow(x, SolverGuarantee::None); // If we did not yet converge, we need to backup the contents of x. if (!converged) { *this->cachedRowVector = x; } // Increase iteration count so we can abort if convergence is too slow. ++iterations; } if (!this->isCachingEnabled()) { clearCache(); } this->logIterations(converged, terminate, iterations); return converged; } template bool NativeLinearEquationSolver::solveEquationsJacobi(std::vector& x, std::vector const& b) const { STORM_LOG_INFO("Solving linear equation system (" << x.size() << " rows) with NativeLinearEquationSolver (Jacobi)"); if (!this->cachedRowVector) { this->cachedRowVector = std::make_unique>(getMatrixRowCount()); } // Get a Jacobi decomposition of the matrix A. if (!jacobiDecomposition) { jacobiDecomposition = std::make_unique, std::vector>>(A->getJacobiDecomposition()); } storm::storage::SparseMatrix const& jacobiLU = jacobiDecomposition->first; std::vector const& jacobiD = jacobiDecomposition->second; std::vector* currentX = &x; std::vector* nextX = this->cachedRowVector.get(); // Set up additional environment variables. uint_fast64_t iterations = 0; bool converged = false; bool terminate = false; while (!converged && !terminate && iterations < this->getSettings().getMaximalNumberOfIterations()) { // Compute D^-1 * (b - LU * x) and store result in nextX. multiplier.multAdd(jacobiLU, *currentX, nullptr, *nextX); storm::utility::vector::subtractVectors(b, *nextX, *nextX); storm::utility::vector::multiplyVectorsPointwise(jacobiD, *nextX, *nextX); // 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()); terminate = this->terminateNow(*currentX, SolverGuarantee::None); // Swap the two pointers as a preparation for the next iteration. std::swap(nextX, currentX); // Increase iteration count so we can abort if convergence is too slow. ++iterations; } // 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 == this->cachedRowVector.get()) { std::swap(x, *currentX); } if (!this->isCachingEnabled()) { clearCache(); } this->logIterations(converged, terminate, iterations); return converged; } template NativeLinearEquationSolver::WalkerChaeData::WalkerChaeData(storm::storage::SparseMatrix const& originalMatrix, std::vector const& originalB) : t(storm::utility::convertNumber(1000.0)) { computeWalkerChaeMatrix(originalMatrix); computeNewB(originalB); precomputeAuxiliaryData(); } template void NativeLinearEquationSolver::WalkerChaeData::computeWalkerChaeMatrix(storm::storage::SparseMatrix const& originalMatrix) { storm::storage::BitVector columnsWithNegativeEntries(originalMatrix.getColumnCount()); ValueType zero = storm::utility::zero(); for (auto const& e : originalMatrix) { if (e.getValue() < zero) { columnsWithNegativeEntries.set(e.getColumn()); } } std::vector columnsWithNegativeEntriesBefore = columnsWithNegativeEntries.getNumberOfSetBitsBeforeIndices(); // We now build an extended equation system matrix that only has non-negative coefficients. storm::storage::SparseMatrixBuilder builder; uint64_t row = 0; for (; row < originalMatrix.getRowCount(); ++row) { for (auto const& entry : originalMatrix.getRow(row)) { if (entry.getValue() < zero) { builder.addNextValue(row, originalMatrix.getRowCount() + columnsWithNegativeEntriesBefore[entry.getColumn()], -entry.getValue()); } else { builder.addNextValue(row, entry.getColumn(), entry.getValue()); } } } ValueType one = storm::utility::one(); for (auto column : columnsWithNegativeEntries) { builder.addNextValue(row, column, one); builder.addNextValue(row, originalMatrix.getRowCount() + columnsWithNegativeEntriesBefore[column], one); ++row; } matrix = builder.build(); } template void NativeLinearEquationSolver::WalkerChaeData::computeNewB(std::vector const& originalB) { b = std::vector(originalB); b.resize(matrix.getRowCount()); } template void NativeLinearEquationSolver::WalkerChaeData::precomputeAuxiliaryData() { columnSums = std::vector(matrix.getColumnCount()); for (auto const& e : matrix) { columnSums[e.getColumn()] += e.getValue(); } newX.resize(matrix.getRowCount()); } template bool NativeLinearEquationSolver::solveEquationsWalkerChae(std::vector& x, std::vector const& b) const { STORM_LOG_INFO("Solving linear equation system (" << x.size() << " rows) with NativeLinearEquationSolver (WalkerChae)"); // (1) Compute an equivalent equation system that has only non-negative coefficients. if (!walkerChaeData) { walkerChaeData = std::make_unique(*this->A, b); } // (2) Enlarge the vectors x and b to account for additional variables. x.resize(walkerChaeData->matrix.getRowCount()); // Square the error bound, so we can use it to check for convergence. We take the squared error, because we // do not want to compute the root in the 2-norm computation. ValueType squaredErrorBound = storm::utility::pow(this->getSettings().getPrecision(), 2); // Set up references to the x-vectors used in the iteration loop. std::vector* currentX = &x; std::vector* nextX = &walkerChaeData->newX; std::vector tmp = walkerChaeData->matrix.getRowSumVector(); storm::utility::vector::applyPointwise(tmp, walkerChaeData->b, walkerChaeData->b, [this] (ValueType const& first, ValueType const& second) { return walkerChaeData->t * first + second; } ); // Add t to all entries of x. storm::utility::vector::applyPointwise(x, x, [this] (ValueType const& value) { return value + walkerChaeData->t; }); // Create a vector that always holds Ax. std::vector currentAx(x.size()); multiplier.multAdd(walkerChaeData->matrix, *currentX, nullptr, currentAx); // (3) Perform iterations until convergence. bool converged = false; uint64_t iterations = 0; while (!converged && iterations < this->getSettings().getMaximalNumberOfIterations()) { // Perform one Walker-Chae step. walkerChaeData->matrix.performWalkerChaeStep(*currentX, walkerChaeData->columnSums, walkerChaeData->b, currentAx, *nextX); // Compute new Ax. multiplier.multAdd(walkerChaeData->matrix, *nextX, nullptr, currentAx); // Check for convergence. converged = storm::utility::vector::computeSquaredNorm2Difference(currentAx, walkerChaeData->b) <= squaredErrorBound; // Swap the x vectors for the next iteration. std::swap(currentX, nextX); // Increase iteration count so we can abort if convergence is too slow. ++iterations; } // 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 == &walkerChaeData->newX) { std::swap(x, *currentX); } // Resize the solution to the right size. x.resize(this->A->getRowCount()); // Finalize solution vector. storm::utility::vector::applyPointwise(x, x, [this] (ValueType const& value) { return value - walkerChaeData->t; } ); if (!this->isCachingEnabled()) { clearCache(); } if (converged) { STORM_LOG_INFO("Iterative solver converged in " << iterations << " iterations."); } else { STORM_LOG_WARN("Iterative solver did not converge in " << iterations << " iterations."); } return converged; } template bool NativeLinearEquationSolver::solveEquationsPower(std::vector& x, std::vector const& b) const { STORM_LOG_THROW(this->hasLowerBound(), storm::exceptions::UnmetRequirementException, "Solver requires upper bound, but none was given."); STORM_LOG_INFO("Solving linear equation system (" << x.size() << " rows) with NativeLinearEquationSolver (Power)"); if (!this->cachedRowVector) { this->cachedRowVector = std::make_unique>(getMatrixRowCount()); } std::vector* currentX = &x; this->createLowerBoundsVector(*currentX); std::vector* nextX = this->cachedRowVector.get(); bool useGaussSeidelMultiplication = this->getSettings().getPowerMethodMultiplicationStyle() == storm::solver::MultiplicationStyle::GaussSeidel; bool converged = false; bool terminate = this->terminateNow(*currentX, SolverGuarantee::GreaterOrEqual); uint64_t iterations = 0; while (!converged && !terminate && iterations < this->getSettings().getMaximalNumberOfIterations()) { if (useGaussSeidelMultiplication) { *nextX = *currentX; this->multiplier.multAddGaussSeidelBackward(*this->A, *nextX, &b); } else { this->multiplier.multAdd(*this->A, *currentX, &b, *nextX); } // Now check for termination. converged = storm::utility::vector::equalModuloPrecision(*currentX, *nextX, static_cast(this->getSettings().getPrecision()), this->getSettings().getRelativeTerminationCriterion()); terminate = this->terminateNow(*currentX, SolverGuarantee::GreaterOrEqual); // Set up next iteration. std::swap(currentX, nextX); ++iterations; } if (currentX == this->cachedRowVector.get()) { std::swap(x, *nextX); } if (!this->isCachingEnabled()) { clearCache(); } this->logIterations(converged, terminate, iterations); return converged; } template bool NativeLinearEquationSolver::solveEquationsSoundPower(std::vector& x, std::vector const& b) const { STORM_LOG_THROW(this->hasLowerBound(), storm::exceptions::UnmetRequirementException, "Solver requires upper bound, but none was given."); STORM_LOG_THROW(this->hasUpperBound(), storm::exceptions::UnmetRequirementException, "Solver requires upper bound, but none was given."); STORM_LOG_INFO("Solving linear equation system (" << x.size() << " rows) with NativeLinearEquationSolver (SoundPower)"); std::vector* lowerX = &x; this->createLowerBoundsVector(*lowerX); this->createUpperBoundsVector(this->cachedRowVector, this->getMatrixRowCount()); std::vector* upperX = this->cachedRowVector.get(); bool useGaussSeidelMultiplication = this->getSettings().getPowerMethodMultiplicationStyle() == storm::solver::MultiplicationStyle::GaussSeidel; std::vector* tmp; if (!useGaussSeidelMultiplication) { cachedRowVector2 = std::make_unique>(x.size()); tmp = cachedRowVector2.get(); } bool converged = false; bool terminate = false; uint64_t iterations = 0; bool doConvergenceCheck = false; ValueType upperDiff; ValueType lowerDiff; while (!converged && !terminate && iterations < this->getSettings().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) { lowerStep = true; upperStep = true; if (useGaussSeidelMultiplication) { lowerDiff = (*lowerX)[0]; this->multiplier.multAddGaussSeidelBackward(*this->A, *lowerX, &b); lowerDiff = (*lowerX)[0] - lowerDiff; upperDiff = (*upperX)[0]; this->multiplier.multAddGaussSeidelBackward(*this->A, *upperX, &b); upperDiff = upperDiff - (*upperX)[0]; } else { this->multiplier.multAdd(*this->A, *lowerX, &b, *tmp); lowerDiff = (*tmp)[0] - (*lowerX)[0]; std::swap(tmp, lowerX); this->multiplier.multAdd(*this->A, *upperX, &b, *tmp); upperDiff = (*upperX)[0] - (*tmp)[0]; std::swap(tmp, upperX); } } else { // In the following iterations, we improve the bound with the greatest difference. if (useGaussSeidelMultiplication) { if (lowerDiff >= upperDiff) { lowerDiff = (*lowerX)[0]; this->multiplier.multAddGaussSeidelBackward(*this->A, *lowerX, &b); lowerDiff = (*lowerX)[0] - lowerDiff; lowerStep = true; } else { upperDiff = (*upperX)[0]; this->multiplier.multAddGaussSeidelBackward(*this->A, *upperX, &b); upperDiff = upperDiff - (*upperX)[0]; upperStep = true; } } else { if (lowerDiff >= upperDiff) { this->multiplier.multAdd(*this->A, *lowerX, &b, *tmp); lowerDiff = (*tmp)[0] - (*lowerX)[0]; std::swap(tmp, lowerX); lowerStep = true; } else { this->multiplier.multAdd(*this->A, *upperX, &b, *tmp); upperDiff = (*upperX)[0] - (*tmp)[0]; std::swap(tmp, upperX); upperStep = true; } } } STORM_LOG_ASSERT(lowerDiff >= storm::utility::zero(), "Expected non-negative lower diff."); STORM_LOG_ASSERT(upperDiff >= storm::utility::zero(), "Expected non-negative upper diff."); if (iterations % 1000 == 0) { STORM_LOG_TRACE("Iteration " << iterations << ": lower difference: " << lowerDiff << ", upper difference: " << upperDiff << "."); } if (doConvergenceCheck) { // Now check if the process already converged within our precision. Note that we double the target // precision here. Doing so, we need to take the means of the lower and upper values later to guarantee // the original precision. converged = storm::utility::vector::equalModuloPrecision(*lowerX, *upperX, storm::utility::convertNumber(2.0) * static_cast(this->getSettings().getPrecision()), this->getSettings().getRelativeTerminationCriterion()); if (lowerStep) { terminate |= this->terminateNow(*lowerX, SolverGuarantee::GreaterOrEqual); } if (upperStep) { terminate |= this->terminateNow(*upperX, SolverGuarantee::GreaterOrEqual); } } // Set up next iteration. ++iterations; doConvergenceCheck = !doConvergenceCheck; } // We take the means of the lower and upper bound so we guarantee the desired precision. storm::utility::vector::applyPointwise(*lowerX, *upperX, *lowerX, [] (ValueType const& a, ValueType const& b) { return (a + b) / storm::utility::convertNumber(2.0); }); // 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->cachedRowVector.get()) { std::swap(x, *this->cachedRowVector); } if (!this->isCachingEnabled()) { clearCache(); } this->logIterations(converged, terminate, iterations); return converged; } template void NativeLinearEquationSolver::logIterations(bool converged, bool terminate, uint64_t iterations) const { if (converged) { STORM_LOG_INFO("Iterative solver converged in " << iterations << " iterations."); } else if (terminate) { STORM_LOG_INFO("Iterative solver terminated after " << iterations << " iterations."); } else { STORM_LOG_WARN("Iterative solver did not converge in " << iterations << " iterations."); } } template bool NativeLinearEquationSolver::internalSolveEquations(std::vector& x, std::vector const& b) const { if (this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::SOR || this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::GaussSeidel) { return this->solveEquationsSOR(x, b, this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::SOR ? this->getSettings().getOmega() : storm::utility::one()); } else if (this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::Jacobi) { return this->solveEquationsJacobi(x, b); } else if (this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::WalkerChae) { return this->solveEquationsWalkerChae(x, b); } else if (this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::Power) { if (this->getSettings().getForceSoundness()) { return this->solveEquationsSoundPower(x, b); } else { return this->solveEquationsPower(x, b); } } STORM_LOG_THROW(false, storm::exceptions::InvalidSettingsException, "Unknown solving technique."); return false; } template void NativeLinearEquationSolver::multiply(std::vector& x, std::vector const* b, std::vector& result) const { if (&x != &result) { multiplier.multAdd(*A, x, b, result); } else { // If the two vectors are aliases, we need to create a temporary. if (!this->cachedRowVector) { this->cachedRowVector = std::make_unique>(getMatrixRowCount()); } multiplier.multAdd(*A, x, b, *this->cachedRowVector); result.swap(*this->cachedRowVector); if (!this->isCachingEnabled()) { clearCache(); } } } template void NativeLinearEquationSolver::multiplyAndReduce(OptimizationDirection const& dir, std::vector const& rowGroupIndices, std::vector& x, std::vector const* b, std::vector& result, std::vector* choices) const { if (&x != &result) { multiplier.multAddReduce(dir, rowGroupIndices, *A, x, b, result, choices); } else { // If the two vectors are aliases, we need to create a temporary. if (!this->cachedRowVector) { this->cachedRowVector = std::make_unique>(getMatrixRowCount()); } multiplier.multAddReduce(dir, rowGroupIndices, *A, x, b, *this->cachedRowVector, choices); result.swap(*this->cachedRowVector); if (!this->isCachingEnabled()) { clearCache(); } } } template bool NativeLinearEquationSolver::supportsGaussSeidelMultiplication() const { return true; } template void NativeLinearEquationSolver::multiplyGaussSeidel(std::vector& x, std::vector const* b) const { STORM_LOG_ASSERT(this->A->getRowCount() == this->A->getColumnCount(), "This function is only applicable for square matrices."); multiplier.multAddGaussSeidelBackward(*A, x, b); } template void NativeLinearEquationSolver::multiplyAndReduceGaussSeidel(OptimizationDirection const& dir, std::vector const& rowGroupIndices, std::vector& x, std::vector const* b, std::vector* choices) const { multiplier.multAddReduceGaussSeidelBackward(dir, rowGroupIndices, *A, x, b, choices); } template void NativeLinearEquationSolver::setSettings(NativeLinearEquationSolverSettings const& newSettings) { settings = newSettings; } template NativeLinearEquationSolverSettings const& NativeLinearEquationSolver::getSettings() const { return settings; } template LinearEquationSolverProblemFormat NativeLinearEquationSolver::getEquationProblemFormat() const { if (this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::Power) { return LinearEquationSolverProblemFormat::FixedPointSystem; } else { return LinearEquationSolverProblemFormat::EquationSystem; } } template LinearEquationSolverRequirements NativeLinearEquationSolver::getRequirements() const { LinearEquationSolverRequirements requirements; if (this->getSettings().getForceSoundness()) { if (this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::Power) { requirements.requireBounds(); } else { STORM_LOG_WARN("Forcing soundness, but selecting a method other than the power iteration is not supported."); } } else { if (this->getSettings().getSolutionMethod() == NativeLinearEquationSolverSettings::SolutionMethod::Power) { requirements.requireLowerBounds(); } } return requirements; } template void NativeLinearEquationSolver::clearCache() const { jacobiDecomposition.reset(); cachedRowVector2.reset(); walkerChaeData.reset(); LinearEquationSolver::clearCache(); } template uint64_t NativeLinearEquationSolver::getMatrixRowCount() const { return this->A->getRowCount(); } template uint64_t NativeLinearEquationSolver::getMatrixColumnCount() const { return this->A->getColumnCount(); } template std::unique_ptr> NativeLinearEquationSolverFactory::create() const { return std::make_unique>(settings); } template NativeLinearEquationSolverSettings& NativeLinearEquationSolverFactory::getSettings() { return settings; } template NativeLinearEquationSolverSettings const& NativeLinearEquationSolverFactory::getSettings() const { return settings; } template std::unique_ptr> NativeLinearEquationSolverFactory::clone() const { return std::make_unique>(*this); } // Explicitly instantiate the linear equation solver. template class NativeLinearEquationSolverSettings; template class NativeLinearEquationSolver; template class NativeLinearEquationSolverFactory; } }