Solver Module Unit Tests¶
Tests for the ODE integrators, iterative solvers, and direct factorization
routines in src/Solver/. All C++ tests use
doctest and run serially (no MPI).
Building and Running¶
# Build all Solver C++ test executables
cmake --build build -t solver_unit_tests -j8
# Run every Solver CTest
ctest --test-dir build -R solver_ --output-on-failure
# Run a single suite
ctest --test-dir build -R solver_ode --output-on-failure
Target Summary¶
CMake target |
CTest name |
Source file |
Timeout |
|---|---|---|---|
|
|
test_ODE.cpp |
60 s |
|
|
test_Linear.cpp |
60 s |
|
|
test_Direct.cpp |
60 s |
|
|
test_Scalar.cpp |
60 s |
ODE Time Integrators (test_ODE.cpp)¶
See also: test_ODE.cpp
13 test cases, 25 assertions. Tests use the harmonic oscillator
du/dt = A * u, A = [[0, -w], [w, 0]], u(0) = [1, 0]
with exact solution u(t) = [cos(wt), sin(wt)]. This oscillatory (non-dissipative) system reveals phase and amplitude errors that exponential-decay tests hide.
Convergence Order Methodology¶
Each integrator is run at two step counts (N, 2N) over one full period T = 2*pi/w. The order is computed as log2(err_N / err_2N). A tolerance of 0.15 is used (expected_order - 0.15 < measured < expected_order + 0.85).
For implicit methods, Newton iterations use threshold-based stopping
(resNorm < 1e-13 or iter >= 50). ESDIRK methods require at least 2
Newton iterations so that rhsbuf[iB] holds f(x_converged) for
subsequent stages. Fresh ODE instances are constructed per convergence
run to avoid FSAL state carryover.
Explicit Methods¶
Test case |
Integrator |
Expected order |
|---|---|---|
|
ExplicitSSPRK3 |
3 |
Implicit Single-Step Methods¶
Test case |
Integrator |
Expected order |
|---|---|---|
|
ImplicitEuler |
1 |
|
SDIRK4, subCode=0 |
4 |
|
ESDIRK, subCode=1 |
4 |
|
ESDIRK, subCode=2 |
3 |
|
ESDIRK, subCode=3 (trapezoidal rule) |
2 |
|
ESDIRK, subCode=4 |
2 |
Note on ESDIRK4 step count: N is kept at 10-20 for clean 4th-order measurement. At large N the error approaches machine precision and roundoff dominates, showing only 2nd order.
Multistep Methods (VBDF)¶
Test case |
Integrator |
Expected order |
|---|---|---|
|
VBDF, maxOrder=1 |
1 |
|
VBDF, maxOrder=2 |
2 |
DITR (Implicit Hermite3 Dual-Step)¶
The DITR family uses ImplicitHermite3SimpleJacobianDualStep with
mask and alpha parameters:
Test case |
Mask |
Alpha |
Expected order |
Notes |
|---|---|---|---|---|
|
0 |
0.5 |
4 |
4th order at alpha=0.5 exactly |
|
0 |
0.55 |
3 |
3rd order for alpha != 0.5 |
|
1 |
0.5 |
3 |
L-stable, 3rd order |
Theory reference: HM3Draft_Content.tex (U2R2, U2R1, U3R1 methods in the Hermite-Multistep family).
Golden Value¶
Test case |
Description |
|---|---|
|
Error at N=100 against captured golden value. |
Iterative Linear Solvers (test_Linear.cpp)¶
See also: test_Linear.cpp
4 test cases, 8 assertions. Tests use a thin DVec wrapper around
Eigen::VectorXd satisfying the TDATA interface.
GMRES¶
Test case |
Description |
|---|---|
|
A = [[4,1,0],[1,3,1],[0,1,2]], b = [1,2,3]. Residual < 1e-12 after at most 3 iterations. |
|
Random A’A + 10I system. Residual < 1e-10 within 10 iterations. |
|
Preconditioned GMRES converges in fewer iterations than unpreconditioned. |
PCG (Preconditioned Conjugate Gradient)¶
Test case |
Description |
|---|---|
|
Tridiagonal (2,-1) matrix. Exact solution recovery to 1e-12. |
Block-Sparse Direct Solvers (test_Direct.cpp)¶
See also: test_Direct.cpp
7 test cases, 26 assertions. Uses a 2D periodic 4x4 Laplacian with 2x2 blocks (16 cells, 5-point stencil with wrap-around).
Problem Setup¶
Each cell (i,j) on a 4x4 periodic grid connects to its 4 neighbors (i+/-1, j+/-1) with periodic wrap-around. The diagonal block is 4I + deltadiag(i, 2i) (asymmetric to exercise full LU, not LDLT). Off-diagonal blocks are -I. This gives a 32x32 system (16 cells x 2x2 blocks) with a diverse sparse pattern including fill-in during factorization.
Tests¶
Test case |
Description |
|---|---|
|
|
|
A*x == b verified against dense reference for random x. |
|
Full LU decompose + solve recovers x to 1e-12. |
|
ILU(0) single-step solve has residual > 1e-10 (not exact) but < initial residual. |
|
Fixed-point iteration x += M^{-1}(b-Ax): ILU(1) reaches tolerance in fewer iterations. Only original-adjacency entries are filled with -I; fill-in positions stay zero. |
|
Symmetric variant produces identical A*x. |
|
LDLT on symmetric system recovers x to 1e-12. |
ILU Preconditioned Iteration Detail¶
The ILU(0) vs ILU(1) comparison uses 200 iterations of stationary
iteration x_{k+1} = x_k + M^{-1}(b - A*x_k). The test verifies that
ILU(1) reaches a residual threshold (1e-8) in strictly fewer iterations
than ILU(0). Fill-in positions from higher ILU levels are identified via
cell2cellFaceVLocal2FullRowPos and left at zero so only original
adjacency entries carry -I blocks.
Scalar Utilities (test_Scalar.cpp)¶
See also: test_Scalar.cpp
5 test cases, 6 assertions. Tests BisectSolveLower from
Solver/Scalar.hpp, a bisection root-finder for monotonic functions.
Test case |
Description |
|---|---|
|
Quadratic, tolerance 1e-10. |
|
Trigonometric, tolerance 1e-10. |
|
Trivial case. |
|
Exponential. |
|
5 iterations: error < 0.1 but > 1e-6 (verifying iteration count affects precision). |