Solvers: overview
Viscosity contrast
MILEVA solves Stokes flow at viscosity contrasts up to
3-D, FGMRES + geometric multigrid. With geometric averaging of the viscosity at velocity faces and matrix-dependent pressure interpolation, FGMRES reaches a relative residual of
at every contrast from 1 to , in 28 to 73 iterations, and needs fewer iterations as the contrast grows (43 at , 28 from to ). Arithmetic face averaging stops converging from on. 2-D, sparse direct (MUMPS, UMFPACK). From contrast 1 to
the solution is exact to round-off: the two independent factorisations give the same velocity to (mid-depth step) and (thin stiff lid), the divergence stays below and the work-dissipation ratio holds its grid value to four digits. At the two still agree to (mid-depth) and (lid), with unchanged. Beyond that double precision sets the limit: a lid step still solves at , while a stiff half-shell fails there.
Each time step MILEVA solves two kinds of linear system. The first is the coupled Stokes saddle-point system for velocity and pressure,
assembled as described in Discretisation. The second is the implicit temperature diffusion step. This section describes the solvers for both, how to select them, and what they cost.
| Page | Contents |
|---|---|
| Direct solvers | MUMPS and UMFPACK for the 2-D Stokes system, the pressure pin, factor reuse, BiCGSTAB |
| Geometric multigrid | SCGS smoother, Galerkin coarse operators, BC lift, matrix-dependent pressure interpolation, FGMRES wrapper |
| Yin-Yang Schwarz coupling | how the two 3-D patches are solved and coupled |
| Parallel solvers (MPI) | one rank per patch, distributed MUMPS, block-Jacobi FGMRES, the dispatcher |
| Energy equation | explicit, Crank-Nicolson and backward-Euler diffusion |
Which solver for which problem
| Problem | Recommended | Setting | Function |
|---|---|---|---|
| 2-D Stokes, any viscosity contrast | sparse direct | cfg.solverType = "mumps" or "direct" | solve_stokes_mumps, solve_stokes_v2 |
| 2-D Stokes, very large grids or solver studies | geometric multigrid | cfg.solverType = "multigrid", cfg.mgLevels ≥ 2 | multigrid_stokes(::MutableGrid2D, ...) |
| 2-D Stokes, Krylov experiments | BiCGSTAB with direct fallback | cfg.solverType = "amg" | solve_stokes_amg |
| 3-D Stokes (Yin-Yang shell) | FGMRES + multigrid V-cycle per patch, Schwarz between patches | cfg.solverType = "multigrid", cfg.mgLevels ≥ 2 | multigrid_stokes(::MutableGrid3D, ...) |
| 3-D Stokes on several MPI ranks | dispatcher, mode = :auto | cfg.useMPI = true, launch with mpiexec | solve_stokes_distributed! |
| 2-D temperature diffusion | Crank-Nicolson (default) | cfg.useImplicitTemp = true, cfg.implicitTheta = 0.5 | diffuse_temperature |
| 3-D temperature diffusion | explicit in the time loop | step limited by cfg.dtMax | diffuse_temperature(::Array{Float64,3}, ...) |
All 2-D Stokes solvers call the same assemble_stokes(...; point_pin = true), so they solve the same discrete problem and differ only in cost and in how tightly they solve it. The 3-D branch of the time loop always calls multigrid_stokes; with cfg.mgLevels = 1 it would factorise each patch with sparse LU, whose memory outgrows a workstation at about

Wall time of one 2-D Stokes solve at docs/scripts/solvers_2d.jl.
Viscosity-contrast range

Measured by docs/scripts/solvers_contrast_range.jl. 2-D: spherical annulus
Measured values (2-D, MUMPS unless stated):
| Contrast | MUMPS vs UMFPACK, mid-depth | MUMPS vs UMFPACK, lid | 2-D multigrid iterations (mid-depth / lid) | 3-D FGMRES iterations | ||
|---|---|---|---|---|---|---|
| 1 | 1.0017 | 1.0017 | 29 / 29 | 31 | ||
| 1.0378 | 1.0019 | 22 / 287 | 72 ( | |||
| 1.0386 | 1.0019 | 28 / 171 | 43 | |||
| 1.0386 | 1.0019 | 33 / no convergence | 34 ( | |||
| 1.0386 | 1.0006 | 61 / no convergence | 28 ( | |||
| 1.0377 | 1.0044 | 164 / no convergence | 28 ( | |||
| fails | 1.0037 | fails | no convergence | 28 |
On the mid-depth step the 2-D multigrid matches MUMPS to
Configuration fields
| Field | Default | Meaning |
|---|---|---|
solverType | "direct" | 2-D Stokes solver: "direct" (UMFPACK), "mumps", "amg", "multigrid" |
mgLevels | 4 | multigrid levels, including the finest; at least 2 for 3-D |
mgSmooth | 2 | pre- and post-smoothing sweeps of the 2-D V-cycle |
krylovTol | relative residual target of FGMRES (2-D multigrid and 3-D) | |
krylovMaxIter | 200 | FGMRES iteration cap (2-D multigrid and 3-D) |
luCacheTol | reuse the MUMPS factor while the largest relative viscosity change stays below this | |
luReuseMaxIter, luReuseTol | 0, | precondition GMRES with a stale MUMPS factor (0 disables) |
useGeometricEta | true | geometric rather than arithmetic viscosity averaging at velocity faces (3-D) |
useImplicitTemp, implicitTheta | true, 0.5 | 2-D diffusion scheme: θ = 0.5 is Crank-Nicolson, 1 is backward Euler |
nTSchwarz | 3 | temperature Schwarz sweeps between the 3-D patches per step |
useMPI, mpiNr, mpiNt | false | 3-D domain decomposition (see Parallel solvers) |
The nonlinear loop
With a yield law the viscosity depends on the strain rate of the flow it produces. main_SphCon.jl therefore wraps the Stokes solve in a Picard loop:
Solve Stokes with the current viscosity.
In 2-D, remove the net rotation (
_net_rotation_rate).Measure the relative change of
from the previous sweep; stop if it is below cfg.picardTol(default). Recompute the strain rate from the new velocity and rebuild the viscosity with
composite_rheology(whencfg.useCompositeRheologyis on). The new field is blended with the previous one inwith the weight cfg.picardDamping(default 1, undamped); if the change stops decreasing, the weight is halved, down to 0.05. Damping changes the path to the fixed point, not the fixed point. Damage and viscoelastic state advance once per time step, not per sweep.
cfg.picardMax = 0 (default) means 50 sweeps when a yield law is active and one sweep otherwise; a contracting rheology without yielding needs no iteration. Steps that hit the cap unconverged are counted and logged, and every row of diagnostics.csv carries picard_n and picard_rel. With a direct solver each sweep refactorises, because the viscosity has changed.

The loop above rebuilt outside the time loop from the same calls (solve_stokes_v2, _net_rotation_rate, compute_strain_rate, composite_rheology, _picard_viscosity_update), on a 64 × 256 spherical annulus with a 100 km conductive lid and a 400 K cold drip below it, started from a uniform strain rate of docs/scripts/solvers_overview_sections.jl.
Where in the code
| Topic | File | Function |
|---|---|---|
| Solver dispatch, Picard loop | main_SphCon.jl | time loop, step (5) |
| UMFPACK | solvers/solve_stokes_v2.jl | solve_stokes_v2 |
| MUMPS | solvers/solve_stokes_mumps.jl | solve_stokes_mumps |
| BiCGSTAB | solvers/solve_stokes_amg.jl | solve_stokes_amg |
| Multigrid, Schwarz | solvers/multigrid_stokes.jl | multigrid_stokes |
| MPI dispatcher | solvers/solve_stokes_distributed.jl | solve_stokes_distributed! |
| Temperature diffusion | solvers/diffuse_temperature.jl | diffuse_temperature |
| Tests | tests/ | test_stokes.jl, test_2d_vcycle_scgs.jl, test_mdpi_eta_contrast.jl, test_diffusion.jl, test_mpi_*.jl |