General concepts » Solvers

Guide to linear and nonlinear solvers in Rodin.

Introduction

After assembling a finite element problem, the resulting linear system

\[ A \mathbf{x} = \mathbf{b} \]

must be solved. Rodin provides several solvers in the Rodin::Solver namespace, ranging from direct methods to iterative solvers. This guide covers all available solvers, explains when to use each, and shows how to configure them for best performance.

Available Solvers

Rodin provides a wide range of solvers. They are organized into direct solvers (exact up to floating-point precision), iterative solvers (approximate via successive refinement), and nonlinear solvers.

Direct Solvers

SolverClassMatrix requirementNotes
SparseLUSolver::SparseLUAny non-singularGeneral-purpose, good default for small/medium problems
UMFPackSolver::UMFPackAny non-singularSuiteSparse-based, often faster than SparseLU
CHOLMODSolver::CHOLMODSymmetric positive definiteSuiteSparse Cholesky factorization
SimplicialLLTSolver::SimplicialLLTSymmetric positive definiteEigen's simplicial Cholesky $ LL^T $
SimplicialLDLTSolver::SimplicialLDLTSymmetricEigen's simplicial $ LDL^T $ factorization
LDLTSolver::LDLTSymmetricDense $ LDL^T $ factorization
HouseholderQRSolver::HouseholderQRAnyDense QR factorization
SparseQRSolver::SparseQRAnySparse QR factorization
SPQRSolver::SPQRAnySuiteSparse sparse QR

Iterative Solvers

SolverClassMatrix requirementNotes
CGSolver::CGSymmetric positive definiteConjugate Gradient — optimal for SPD systems
BiCGSTABSolver::BiCGSTABAnyBi-conjugate gradient stabilized
GMRESSolver::GMRESAnyGeneralized Minimum Residual
DGMRESSolver::DGMRESAnyDeflated GMRES variant
MINRESSolver::MINRESSymmetricMinimum Residual method
IDRSSolver::IDRSAnyInduced Dimension Reduction
IDRSTABLSolver::IDRSTABLAnyIDR(s) with higher-order stabilization
LeastSquaresCGSolver::LeastSquaresCGAnyLeast-squares CG for rectangular systems

Direct Solvers

Direct solvers compute the exact solution (up to floating-point precision) by factoring the system matrix. They are reliable but can be expensive for very large systems.

SparseLU

The SparseLU solver performs a sparse LU decomposition. It works for any non-singular matrix, making it the most general-purpose choice:

#include <Rodin/Solver.h>
#include <Rodin/Geometry.h>
#include <Rodin/Variational.h>

using namespace Rodin;
using namespace Rodin::Geometry;
using namespace Rodin::Variational;

int main()
{
  Mesh mesh;
  mesh = mesh.UniformGrid(Polytope::Type::Triangle, {16, 16});
  mesh.getConnectivity().compute(1, 2);

  P1 Vh(mesh);
  TrialFunction u(Vh);
  TestFunction  v(Vh);

  Problem poisson(u, v);
  poisson = Integral(Grad(u), Grad(v))
          - Integral(RealFunction(1.0), v)
          + DirichletBC(u, Zero());

  // Inline usage — construct solver with the problem, then solve
  Solver::SparseLU(poisson).solve();

  return 0;
}

SparseLU is a good default choice when:

  • The system is not too large (up to roughly 100,000 DOFs)
  • You need a robust solver that works for any matrix structure
  • The system matrix is non-symmetric or indefinite

UMFPack

UMFPack is an alternative direct solver from the SuiteSparse library. It is especially effective for general unsymmetric sparse systems and often outperforms SparseLU on larger problems:

#include <Rodin/Solver/UMFPack.h>

Solver::UMFPack(problem).solve();

UMFPack requires the SuiteSparse library to be installed. It is the recommended direct solver for:

  • Saddle-point systems (e.g. Stokes, Navier–Stokes)
  • Coupled multi-physics problems
  • Systems where SparseLU is too slow

Iterative Solvers

Iterative solvers approximate the solution through successive iterations. They are more efficient for large systems but may require tuning.

Conjugate Gradient (CG)

The CG method is optimal for symmetric positive definite (SPD) systems, which arise from many common PDEs (Poisson, elasticity, heat equation):

// Inline usage — construct with the problem and solve immediately
Solver::CG(poisson).solve();

The CG solver supports several configuration options:

Solver::CG solver(poisson);
solver.setTolerance(1e-10);           // Relative convergence threshold
solver.setMaxIterations(1000);        // Maximum number of iterations
solver.solve();

if (solver.success())
  std::cout << "CG converged." << std::endl;

CG is the recommended solver when:

  • The system matrix is symmetric positive definite
  • The system is large (100,000+ DOFs)
  • You want memory-efficient iterative solving

Newton Solver

For nonlinear variational problems (e.g. hyperelasticity, Navier–Stokes), Rodin provides a NewtonSolver<LinearSolver> that wraps a linear solver and performs Newton-Raphson iteration. At each iterate $ u^k $ , the tangent system is assembled and solved:

\[ J(u^k)\,\delta u^k = -F(u^k), \qquad u^{k+1} = u^k + \delta u^k \]

where $ J $ is the Jacobian (tangent matrix) and $ F $ is the nonlinear residual.

#include <Rodin/Solver/NewtonSolver.h>
#include <Rodin/Solver/SparseLU.h>

Solver::SparseLU linearSolver(problem);
Solver::NewtonSolver newton(linearSolver);
newton.setMaxIterations(20);
newton.setAbsoluteTolerance(1e-10);
newton.setRelativeTolerance(1e-8);
newton.solve(u);  // updates u in place

Configuration methods:

MethodDefaultDescription
setMaxIterations(n)100Maximum Newton iterations
setAbsoluteTolerance(tol) $ 10^{-12} $ Converged when $ \|r\| \le \mathrm{atol} $
setRelativeTolerance(tol) $ 10^{-8} $ Converged when $ \|r\|/\|r_0\| \le \mathrm{rtol} $

Convergence is declared when either criterion is satisfied (not both).

Hyperelastic Example

For hyperelastic problems, the Newton system is built from Solid::MaterialTangent (tangent stiffness) and Solid::InternalForce (nonlinear residual), both linearized about the current displacement:

#include <Rodin/Solid.h>
#include <Rodin/Solver/NewtonSolver.h>
#include <Rodin/Solver/SparseLU.h>

Solid::NeoHookean law(lambda, mu);
GridFunction u(Vh);   // accumulated displacement

Solid::MaterialTangent tangent(law, du, v);
tangent.setDisplacement(u);

Solid::InternalForce residual(law, v);
residual.setDisplacement(u);

Problem newton(du, v);
newton = tangent + residual
       - Integral(bodyForce, v)
       + DirichletBC(du, VectorFunction{Zero(), Zero()}).on(fixedBC);

SparseLU linearSolver(newton);
NewtonSolver solver(linearSolver);
solver.setMaxIterations(50)
      .setAbsoluteTolerance(1e-10);
solver.solve(u);

The Newton solver is used for:

  • Hyperelastic solid mechanics (with Solid::InternalForce / MaterialTangent)
  • Nonlinear PDEs (Navier–Stokes, nonlinear diffusion)
  • Any problem where the bilinear form depends on the current solution

Usage Patterns

Rodin solvers support two main usage patterns.

Inline Solving

For quick one-shot use, construct the solver with the problem and call solve() in a single expression. This is the most common pattern in Rodin:

Solver::CG(poisson).solve();
Solver::SparseLU(elasticity).solve();

Configured Solving

When you need to tune parameters, construct the solver object first, configure it, then call solve():

Solver::CG solver(poisson);
solver.setMaxIterations(500);
solver.setTolerance(1e-12);
solver.solve();

Solver Reuse via problem.solve()

You can also pass a pre-configured solver to problem.solve(). This is useful when the same solver is applied to multiple problems (e.g.\ in an optimization loop):

Solver::SparseLU solver;
for (size_t i = 0; i < nIterations; ++i)
{
  // ... update problem ...
  problem.solve(solver);
}

Choosing a Solver

CriterionRecommended solverWhy
Small system (< 100k DOFs), any matrixSparseLU or UMFPackRobust direct factorization
Large SPD system (Poisson, elasticity)CGMemory-efficient, optimal for SPD
Large non-symmetric systemBiCGSTAB or GMRESGeneral iterative methods
Symmetric indefinite (saddle-point)UMFPack or MINRESUMFPack is most robust
Saddle-point (Stokes, mixed FEM)UMFPackHandles indefinite structure
SPD with SuiteSparse availableCHOLMOD or SimplicialLLTExploit symmetric structure
Need guaranteed convergenceAny direct solverNo iteration needed
Memory-constrained large systemCG (if SPD), GMRES (otherwise)Matrix-free possible
Nonlinear PDENewton + any linear solverSuccessive linearization

Complete Example

#include <Rodin/Solver.h>
#include <Rodin/Geometry.h>
#include <Rodin/Variational.h>
#include <Rodin/IO/XDMF.h>

using namespace Rodin;
using namespace Rodin::Geometry;
using namespace Rodin::Variational;

int main()
{
  Mesh mesh;
  mesh = mesh.UniformGrid(Polytope::Type::Triangle, {32, 32});
  mesh.getConnectivity().compute(1, 2);

  P1 Vh(mesh);
  TrialFunction u(Vh);
  TestFunction  v(Vh);

  Problem poisson(u, v);
  poisson = Integral(Grad(u), Grad(v))
          - Integral(RealFunction(1.0), v)
          + DirichletBC(u, Zero());

  // Solve with CG
  Solver::CG solver(poisson);
  solver.setMaxIterations(1000);
  solver.setTolerance(1e-10);
  solver.solve();

  // Export result
  IO::XDMF xdmf("SolverExample");
  xdmf.grid().setMesh(mesh).add("u", u.getSolution());
  xdmf.write();

  return 0;
}

PETSc Solvers

When Rodin is built with PETSc support (RODIN_USE_PETSC=ON), additional solvers become available that support both sequential and MPI-distributed linear systems. PETSc solvers work with PETSc::Math::LinearSystem and are activated by using PETSc::Variational::TrialFunction / PETSc::Variational::TestFunction instead of the standard trial/test functions.

KSP (Krylov Subspace)

KSP is the base PETSc linear solver wrapper. It provides access to the full PETSc Krylov solver library and can be configured both programmatically and via PETSc command-line options.

MethodDescription
setType(KSPType)Select algorithm (e.g. KSPCG, KSPGMRES, KSPBCGS)
setTolerances(rtol, abstol, dtol, maxIt)Set convergence tolerances and iteration limit
setPreconditioner(Mat)Supply an explicit preconditioner matrix
setPrefix(string)Set option prefix for multi-solver configurations
#include <Rodin/PETSc.h>

// Use the KSP base solver directly
Solver::KSP(problem).solve();

// Configure via PETSc command-line options:
//   ./myprogram -ksp_type gmres -pc_type ilu -ksp_rtol 1e-8 -ksp_monitor

PETSc CG

CG<PETSc::Math::LinearSystem> is a convenience specialization that sets the KSP type to KSPCG. It is the recommended iterative solver for SPD systems when using PETSc:

PETSc::Variational::TrialFunction u(Vh);
PETSc::Variational::TestFunction  v(Vh);

Problem poisson(u, v);
poisson = Integral(Grad(u), Grad(v))
        - Integral(f, v)
        + DirichletBC(u, Zero());

Solver::CG(poisson).solve();

PETSc GMRES

GMRES<PETSc::Math::LinearSystem> sets the KSP type to KSPGMRES. Suitable for non-symmetric and indefinite systems:

Solver::GMRES(stokes).solve();

SNES (Nonlinear)

SNES wraps the PETSc Scalable Nonlinear Equations Solver for Newton-type methods. It uses an associated KSP for the inner linear solve at each Newton step:

// Create the KSP linear solver
Solver::KSP ksp(problem);
ksp.setTolerances(1e-10, 1e-14, 1e5, 200);

// Wrap it in SNES for Newton iteration
Solver::SNES snes(ksp);
snes.setTolerances(1e-8, 1e-10, 1e-6, 50, 10000);
snes.solve(x);

Multi-Field Problems

PETSc solvers support block systems with multiple trial/test function pairs. The resulting linear system can be solved with field-split preconditioning configured via PETSc command-line options:

PETSc::Variational::TrialFunction u(Vh_velocity);
PETSc::Variational::TrialFunction p(Vh_pressure);

PETSc::Variational::TestFunction v(Vh_velocity);
PETSc::Variational::TestFunction q(Vh_pressure);

Problem stokes(u, p, v, q);
stokes = Integral(Jacobian(u), Jacobian(v))
       - Integral(p, Div(v))
       + Integral(Div(u), q)
       - Integral(f, v)
       + DirichletBC(u, g);

Solver::KSP(stokes).solve();
// Run with: -ksp_type gmres -pc_type fieldsplit -ksp_monitor

PETSc Solver Summary

SolverClassSystem typeNotes
KSPSolver::KSPAny (via PETSc type selection)Base wrapper, full PETSc configuration
CGSolver::CG<PETSc::Math::LinearSystem>SPDSets KSPCG
GMRESSolver::GMRES<PETSc::Math::LinearSystem>Non-symmetric, indefiniteSets KSPGMRES
SNESSolver::SNESNonlinear $ F(x)=0 $ Newton-type, uses KSP internally

Troubleshooting

  • CG does not converge: The matrix may not be SPD. Switch to SparseLU or UMFPack.
  • SparseLU runs out of memory: The system is too large for a direct solver. Use CG instead (if SPD) or consider PETSc solvers.
  • Solution is incorrect: Check that boundary conditions are applied correctly and that the mesh connectivity is computed before assembling.
  • PETSc solver does not converge: Use -ksp_monitor to inspect residual history. Try -pc_type gamg for AMG preconditioning or -ksp_type gmres -pc_type ilu for non-SPD systems.

See Also