Solving sparse systems
There are many methods available for solving sparse systems. We compare some of them here.
Mathematica has the following methods available (https://reference.wolfram.com/language/ref/LinearSolve.html#DetailsAndOptions)
- direct: banded, cholesky, multifrontal (direct sparse LU)
- iterative: Krylov
Matlab has the following methods:
- direct: https://www.mathworks.com/help/matlab/ref/mldivide.html#bt42omx_head
- iterative: https://www.mathworks.com/help/matlab/math/systems-of-linear-equations.html#brzoiix, including bicgstab, gmres
Eigen has the following methods: (https://eigen.tuxfamily.org/dox-devel/group__TopicSparseSystems.html)
- direct: sparse LU
- iterative: bicgstab, cg
Solving a simple sparse system $A x = b$ for steady space of heat equation in 1d with $n$ nodes, results in a matrix shown in Figure Figure 1.
The following timings of solvers are given in seconds:
$n = 10^6$ | Matlab | Mathematica | Eigen |
---|---|---|---|
Banded | 0.16 | 0.28 | 0.04 |
SparseLU | / | 1.73 | 0.82 |
BICGStab / Krylov | 0.33 | 0.39 | 0.53 |
Incomplete LU preconditioner was used for BICGStab. Without the preconditioner BICGStab does not converge.
BICGStab can be run in parallel, as explain in the general parallelism: https://eigen.tuxfamily.org/dox/TopicMultiThreading.html, and specifically
"When using sparse matrices, best performance is achied for a row-major sparse matrix format.
Moreover, in this case multi-threading can be exploited if the user code is compiled with OpenMP enabled".
Eigen uses number of threads specified my OopenMP, unless Eigen::setNbThreads(n);
was called.
Minimal working example:
- include <iostream>
- include <vector>
- include "Eigen/Sparse"
- include "Eigen/IterativeLinearSolvers"
using namespace std; using namespace Eigen;
int main(int argc, char* argv[]) {
assert(argc == 2 && "Second argument is size of the system."); stringstream ss(argv[1]); int n; ss >> n; cout << "n = " << n << endl;
// Fill the matrix VectorXd b = VectorXd::Ones(n) / n / n; b(0) = b(n-1) = 1; SparseMatrix<double, RowMajor> A(n, n); A.reserve(vector<int>(n, 3)); // 3 per row for (int i = 0; i < n-1; ++i) { A.insert(i, i) = -2; A.insert(i, i+1) = 1; A.insert(i+1, i) = 1; } A.coeffRef(0, 0) = 1; A.coeffRef(0, 1) = 0; A.coeffRef(n-1, n-2) = 0; A.coeffRef(n-1, n-1) = 1;
// Solve the system BiCGSTAB<SparseMatrix<double, RowMajor>, IncompleteLUT<double>> solver; solver.setTolerance(1e-10); solver.setMaxIterations(1000); solver.compute(A); VectorXd x = solver.solve(b); cout << "#iterations: " << solver.iterations() << endl; cout << "estimated error: " << solver.error() << endl; cout << "sol: " << x.head(6).transpose() << endl;
return 0;
}
was compiled with g++ -o parallel_solve -O3 -fopenmp solver_test_parallel.cpp
Figure Figure 2. was produced when the program above was run as ./parallel_solve 10000000
.