FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
SUNDIALS and linear-solver configuration

The <Integrator> block accepts three child elements that together control how the nonlinear and linear algebra is handled inside SUNDIALS at each time step:

Parameter XML element / attribute Default Description
Jacobian type <Jacobian Type="..."> dense Storage format of the Jacobian matrix passed to SUNDIALS
Linear solver <LinearSolver Type="..."> SPGMR Linear solver used within Newton iterations
Nonlinear solver <LinearSolver NonlinearSolver="..."> newton Nonlinear iteration strategy
Warning
<Jacobian> and its attributes apply to the SUNDIALS backend only. On the Engine backend <Jacobian Type> is ignored (the storage follows from the linear solver) and <Jacobian Policy> is ignored with a warning in the log.

<Jacobian Type> — Jacobian matrix format

Value Matrix Best for
auto Exactly the same as dense — see the note below
dense Full N×N dense matrix Small to medium systems (up to ~200 states)
band Banded matrix with upper/lower bandwidth Chain-coupled or banded systems where bandwidth << N
sparse Sparse CSR matrix (N×N allocated, filled analytically or by differences) Large sparse systems; enables diagonal Jacobi preconditioner for SPGMR
none No matrix allocated Matrix-free Krylov (SPGMR); SUNDIALS uses internal difference quotients
Note
Type="auto" decides nothing. It is a parse-time spelling of dense: the two are indistinguishable after the input file is read, and no model capability is inspected. The attribute that does inspect the model is Policy — see Implicit Integration with Analytical Jacobians. The linear solver has its own, genuinely adaptive auto; see Linear Solver Selection.

When <Jacobian Type="band">, the bandwidth is auto-detected from the Jacobian sparsity pattern (the maximum |i-j| over all non-zero entries). You can override the detected values with BandUpper and BandLower on <LinearSolver>:

<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="BDF">
<Jacobian Type="band"/>
<LinearSolver Type="BAND" BandUpper="25" BandLower="25"/>
</Integrator>
</SIMULATION>

If only BandUpper is specified, BandLower defaults to the same value (symmetric bandwidth).

<LinearSolver Type> — Linear solver

Value Solver Best for
SPGMR Krylov iterative solver (Scaled Preconditioned GMRES) Large or sparse systems; also the only option when <Jacobian Type="none">
DENSE Direct dense LU factorisation Small systems where the full Jacobian is available
BAND Direct banded LU factorisation Systems with limited bandwidth (bw << N); requires <Jacobian Type="band">

<LinearSolver NonlinearSolver> — Nonlinear solver

Value Strategy
newton Newton iteration (default, recommended for stiff systems)
fixed_point Anderson-accelerated fixed-point iteration. Expert tuning via <SundialsExtra>.

Valid <Jacobian Type> / <LinearSolver Type> combinations

Not all combinations are meaningful. The table below lists every combination and whether it is valid:

Jacobian Type LinearSolver Type Valid Notes
dense DENSE Yes Direct dense LU solve. Recommended for small systems (< ~50 states).
dense SPGMR Yes Iterative Krylov solve with a dense Jacobian available. Useful when the system is moderately large but a full Jacobian is still affordable.
band BAND Yes Direct banded LU solve. Best when bandwidth << N (e.g., chain-coupled systems). Storage O(N·bw), factorization O(N·bw²).
band DENSE No The dense direct solver cannot operate on a band matrix object. Use <LinearSolver Type="BAND">.
band SPGMR No Use <Jacobian Type="none"> with <LinearSolver Type="SPGMR" Preconditioner="block_tridiagonal"/> for iterative + block preconditioner instead.
sparse SPGMR Yes Sparse Jacobian is used to build an ILUT (Incomplete LU with Threshold) preconditioner for GMRES. Best choice for large sparse systems (> 200 states).
sparse DENSE No The dense direct solver cannot operate on a sparse matrix object. FhSim reports an error at startup.
none SPGMR Yes Matrix-free: SUNDIALS computes Jacobian-vector products by finite differences internally. No Jacobian storage.
none DENSE No The dense direct solver requires an explicit Jacobian matrix. FhSim reports an error at startup.
Note
When FhSim assembles an analytical Jacobian (see Implicit Integration with Analytical Jacobians), the <Jacobian Type> setting still controls the storage format that SUNDIALS uses for that Jacobian. Use <Jacobian Type="dense"> for full dense storage or <Jacobian Type="sparse"> to pass it as a sparse CSR matrix (required for the ILUT preconditioner with SPGMR).

Quick-start examples

Small system, direct solver (recommended default):

<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="BDF">
<Jacobian Type="dense"/>
<LinearSolver Type="DENSE" NonlinearSolver="newton"/>
</Integrator>
</SIMULATION>

Large sparse system, iterative solver with ILUT preconditioner:

<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="BDF">
<Jacobian Type="sparse"/>
<LinearSolver Type="SPGMR" NonlinearSolver="newton"/>
</Integrator>
</SIMULATION>

Matrix-free solve (no Jacobian storage):

<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="BDF">
<Jacobian Type="none"/>
<LinearSolver Type="SPGMR" NonlinearSolver="newton"/>
</Integrator>
</SIMULATION>

Banded direct solve (chain-coupled or limited bandwidth systems):

<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="BDF">
<Jacobian Type="band"/>
<LinearSolver Type="BAND" NonlinearSolver="newton"/>
</Integrator>
</SIMULATION>

SUNDIALS Output Modes

In FhSim v3 the integrator no longer controls output timing. The flat <Integrator> attributes for output scheduling used in earlier versions have been removed. Output timing is now configured through the observer/output pipeline — see OBSERVERS section. Internal step-size bounds are set on <StepControl> via StepMin, StepMax, Step0, and MaxSteps.

Linear Solver Selection

During Newton iteration, implicit integration requires solving the linear system

\[ (I - \Delta t \cdot J) \cdot \Delta x = r \]

where \(J\) is the system Jacobian. FhSim supports multiple linear solver strategies, selected automatically by default or manually via configuration.

Solver Types

Solver keyword Description Complexity Best For
auto Heuristic selection based on topology and sparsity Default; works well for most cases
dense Dense LU factorization (Eigen::PartialPivLU) O(n³) Small systems (n < 30)
sparse Sparse LU with METIS nested dissection ordering O(nnz·√n) typical Medium/large sparse systems; especially effective for 2D/3D mesh structures (trawl nets, aquaculture nets)
band Direct banded LU with automatic RCM reordering O(n·bw²) Narrow-bandwidth chains (bw < n/4)
iterative BiCGSTAB with ILUT preconditioner O(k·nnz)/iter Very large sparse systems (n ≥ 5000)
iterative_gmres GMRES with ILUT preconditioner O(k·nnz)/iter Ill-conditioned systems where BiCGSTAB stagnates
block_tridiagonal Thomas algorithm for block-tridiagonal systems O(N·bs³) Pure chain topologies
near_tridiagonal Woodbury-corrected block-Thomas O(N·bs³ + k³) Single-SO rings, T-junctions, psi-junctions
component Direct solve per connected component of the SimObject coupling graph; each component uses the structured local solver its sparsity allows (block-Thomas, Woodbury, dense LU) and is assembled without a dense N×N matrix O(Σ n_c³) worst case, O(N·bs³) for chain components Multi-SimObject systems; the only structure it exploits is within a component

Automatic Selection

By default, <LinearSolver Type="auto"> applies a topology-aware hierarchy based on the cached Jacobian sparsity pattern and SimObject graph structure:

Priority Condition Selected Solver
1 n < 30 DenseLU
2 Pure block-tridiagonal chain detected BlockTridiagonal
3 Single SimObject, density < 0.5, near-tridiagonal structure confirmed NearTridiagonal
4 Single SimObject, density < 0.5, near-tridiagonal not viable (e.g. 2D mesh) falls to 5–7
5 ≥ 2 stateful SimObjects, density < 0.5 Component
6 density < 0.25 AND bandwidth < n/4 BandedLU
7 density < 0.25 AND n ≥ 5000 Iterative (BiCGSTAB)
8 density < 0.25 SparseLU (METIS ordering)
9 Otherwise (dense or irregular) DenseLU

The near-tridiagonal viability check at priority 3 calls NearTridiagonalSolver::DetectStructure at selection time. This means 2D/3D mesh SimObjects (trawl nets, aquaculture nets, flexible surfaces) whose sparsity graph has too many off-tridiagonal connections correctly fall through to SparseLU (priority 8) rather than stalling at a failed NearTridiagonal setup and degrading to DenseLU.

IterativeGMRES and explicit NearTridiagonal / Component overrides are also accepted as manual selections. The sparsity pattern and topology are computed once at model initialization with negligible runtime overhead.

Note
The heuristic was significantly revised in a recent update. The previous flat density-first logic is replaced by the topology-aware hierarchy above. Systems that previously fell through to SparseLU or Iterative may now be routed to BlockTridiagonal, NearTridiagonal, or Component for better performance.

Manual Configuration

To override automatic selection, use <LinearSolver> child element:

<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="BackwardEuler_i">
<Jacobian Type="dense"/>
<LinearSolver Type="SPGMR" MaxIter="50" Tol="1e-10" Restart="30"/>
</Integrator>
</SIMULATION>
Note
<LinearSolver> attributes MaxIter, Tol, and Restart configure iterative solver parameters. Jacobian verification and sparsity logging are configured in the unified <Diagnostics> block.

Example with Jacobian verification enabled:

<SIMULATION>
<Integrator Method="BackwardEuler_i">
<LinearSolver Type="sparse"/>
<Diagnostics>
<Triggers Once="true"/>
<Settings>
<JacobianVerify/>
</Settings>
</Diagnostics>
</Integrator>
</SIMULATION>

For banded systems (e.g., a chain of 6-DOF bodies), <LinearSolver Type="band"> is often the fastest direct solver. Bandwidth is auto-detected from the sparsity pattern; user overrides are available:

<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="BackwardEuler_i">
<LinearSolver Type="band" BandUpper="13" BandLower="13"/>
</Integrator>
</SIMULATION>

If BandUpper and BandLower are omitted, they default to the symmetric bandwidth detected from GetJacobianSparsity (or n-1 if no sparsity info is available).

When to Override Auto-Selection

  • Ring topologies: Closed-chain systems (e.g., 6-body mooring loops) destroy bandedness but maintain sparsity. Use near_tridiagonal (single SO, auto-detected) or sparse.
  • 2D/3D mesh SimObjects (trawl nets, aquaculture nets, flexible surfaces): the sparse solver with METIS nested dissection ordering is the recommended direct solver. For very large meshes (n > 2000) consider iterative or iterative_gmres.
  • Nearly dense Jacobians: If nnz/n² > 0.3, sparse solvers incur overhead without benefit. Use dense.
  • Very large systems (n > 1000): iterative scales better than direct sparse LU. Tune convergence with <LinearSolver MaxIter="..."> and <LinearSolver Tol="..."> attributes.
  • BiCGSTAB stagnation: If Iterative fails to converge on ill-conditioned systems, switch to iterative_gmres. GMRES has more robust convergence (monotonic residual decrease) at the cost of higher memory (stores a restart-count of Krylov vectors). Adjust the restart count via <LinearSolver Restart="30"> — higher values improve convergence but use more memory.
  • Narrow-bandwidth chain systems: For chains of 6-DOF bodies or similar where bandwidth bw << n, band (banded LU) is often the fastest direct Engine/BackwardEuler_i solver — O(n·bw²) factorize vs O(n·bw³) for block-tridiagonal and O(nnz·√n) for sparse LU. The auto heuristic selects it when bw < n/4.
  • Multi-body systems with per-SimObject Jacobians: For systems where each SimObject provides analytical Jacobians and components are coupled through ports, use component for a block Gauss-Seidel preconditioned GMRES that exploits the coupling graph topology.

Performance Optimizations

Several internal optimizations reduce solve time and memory cost regardless of which linear solver is selected.

RCM Reordering (Banded LU)

The band solver applies Reverse Cuthill-McKee (RCM) reordering before factorization. RCM permutes the rows and columns to minimize the matrix bandwidth, which reduces the fill-in during LU factorization and lowers the O(n·bw²) cost:

  1. A pseudo-peripheral node is found using a BFS heuristic as the starting vertex.
  2. A BFS level-set traversal with degree-ascending tie-breaking generates the RCM ordering.
  3. The permutation is reversed (CM → RCM) so that nodes within each level are ordered from highest to lowest degree, further reducing bandwidth.

The permutation perm[] and its inverse iperm[] are computed once at Setup time and reused throughout the simulation. No user configuration is required; the banded solver always reorders.

Fixed-Size Block Dispatch (Block-Tridiagonal Solver)

BlockTridiagonalSolver (also when it is the local solver of a ComponentSolver component) detects the block size bs at Setup time and dispatches to a compile-time fixed-size kernel for bs = 1–16. Each fixed-size kernel stores blocks as Eigen::Matrix<double, bs, bs> (stack-allocated, fully unrolled) and uses Eigen::PartialPivLU<Matrix<double,bs,bs>> which the compiler can vectorize and inline completely.

Block size Kernel storage LU solver
1–16 unique_ptr<BlockTriKernel<bs>> via macro dispatch PartialPivLU<Matrix<double,bs,bs>> (compile-time)
≥ 17 Heap-allocated MatrixXd blocks PartialPivLU<MatrixXd> (runtime size)

The dispatch uses a switch over bs with ALL_BLOCK_SIZES(MACRO) expanding 16 cases. Heap-allocated kernels are stored as unique_ptr<void, KernelDeleter> with a typed function-pointer deleter so that the correct destructor is called without exposing the templated type in the header.

For the common 13-state rigid-body case (6-DOF body in FhSim), the bs=13 kernel is approximately 3–5× faster than the dynamic-size path on a typical x86-64 system.

Dense Jacobian Bypass

Several solvers own their Jacobian representation directly and do not require FhSim to assemble a full N×N dense matrix first:

Solver NeedsDenseJacobian() How Jacobian is obtained
DenseLUSolver true Receives dense N×N matrix from caller
BlockTridiagonalAdapter false Assembles block-tridiagonal pattern directly from SimObject OdeJacobian() and port links
ComponentSolver false Assembles each connected component from the block Jacobians and the couplings inside it
SparseLUAdapter false Fills CSR values via AssembleSparseJacobianValues()
BandedLUAdapter true Receives dense matrix; extracts band after RCM reordering

When NeedsDenseJacobian() returns false, the implicit method skips the O(n²) dense assembly step entirely and calls FactorizeFromModel() instead, which lets each solver fill only the entries it needs. For large systems this can reduce the per-Newton-step wall time by 30–70%.

BiCGSTAB vs GMRES

BiCGSTAB (iterative) GMRES (iterative_gmres)
Memory O(n) — fixed O(k·n) — grows with restart parameter
Convergence Can stagnate on difficult spectra Monotonic residual decrease, more robust
Tuning None beyond MaxIter/Tol <LinearSolver Restart="..."> (default 30)
Recommendation Default for large systems Fallback when BiCGSTAB fails to converge

Block-Tridiagonal Solver (Thomas Algorithm)

For systems with chain topology — such as a series of rigid bodies connected end-to-end — the Jacobian has block-tridiagonal structure. The block_tridiagonal solver exploits this structure using the block Thomas algorithm, achieving O(N × bs³) complexity instead of O((N·bs)³) for dense LU, where N is the number of blocks and bs is the block size.

This is particularly effective for:

  • Chains of rigid bodies (13 states each: position, velocity, quaternion, angular velocity)
  • Discretized 1D PDEs presented as a single SimObject with internal chain structure
  • Any system where each subsystem only couples to its immediate neighbors

Engine Framework

With the <Integrator> integrator, block_tridiagonal is used as a direct linear solver during Newton iteration:

<SIMULATION>
<Timing TStart="0" TEnd="1"/>
<Integrator Method="BackwardEuler_i">
<LinearSolver Type="block_tridiagonal"/>
</Integrator>
</SIMULATION>

SUNDIALS Framework

With SUNDIALS (CVODE or ARKode), block_tridiagonal configures SPGMR with a block-Thomas preconditioner. The preconditioner assembles the block-tridiagonal portion of the Jacobian directly from SimObject Jacobians and port connections (without forming the full dense Jacobian), then factorizes P = I - gamma·J using the Thomas algorithm. Use <Jacobian Type="none"> for matrix-free Jacobian-vector products:

<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="BDF">
<Jacobian Type="none"/>
<LinearSolver Type="SPGMR" NonlinearSolver="newton" Preconditioner="block_tridiagonal"/>
</Integrator>
</SIMULATION>

The preconditioner supports SUNDIALS jok (Jacobian OK) optimization: when only gamma changes between Newton iterations, the raw Jacobian blocks are re-factorized without re-assembling them from the SimObjects.

Detection: When block_tridiagonal is selected, FhSim automatically:

  1. Analyzes PortJacobianLink topology for chain structure (topological sort from endpoint)
  2. Falls back to analyzing intra-object sparsity patterns for block-tridiagonal structure
  3. Validates the detected structure against the cached CSR sparsity pattern
  4. Errors with a message if no block-tridiagonal structure is found

Block size: Auto-detected from SimObject state sizes (multi-object chains) or by scanning for the largest block size that divides the state count and matches the sparsity pattern (intra-object). For the fixed 13-state case, compile-time optimized Eigen types (Matrix<double,13,13>, PartialPivLU<Matrix13>) are used.

Performance comparison (N=10 bodies × 13 states = 130 total states):

Solver Complexity Approximate Flops
Dense LU O(n³) ~2.2M
Banded LU (bw=25) O(n·bw²) ~81K
Sparse LU O(nnz·√n) ~50K
Block Thomas O(N·bs³) ~22K

Near-Tridiagonal Solver (Woodbury)

For single-SimObject systems that are almost block-tridiagonal — such as rings of bodies, T-junctions, or psi-junctions — the near_tridiagonal solver extends the block-Thomas algorithm using the Woodbury identity (Sherman-Morrison-Woodbury matrix identity).

The idea is to decompose the full system matrix as:

\[ A_{full} = A_{tridiag} + U V^T \]

where \(A_{tridiag}\) is the block-tridiagonal core and \(U\), \(V\) (each \(n \times k\)) encode the extra off-diagonal blocks that break tridiagonality (e.g., the closing edge of a ring). With \(k \ll n\) this adds only a cheap \(k \times k\) correction on top of the Thomas solve.

Solve algorithm (per Newton step):

  1. \( y = A_{tridiag}^{-1} \cdot rhs \) — one block-Thomas solve
  2. \( (I + V^T A_{tridiag}^{-1} U)\,\delta = V^T y \) — dense \(k \times k\) LU
  3. \( x = y - A_{tridiag}^{-1} U\,\delta \) — \(A_{tridiag}^{-1} U\) is precomputed at factorize time

Factorize cost: k additional block-Thomas solves to build \(A_{tridiag}^{-1} U\), plus one \(k \times k\) LU factorization.

Viability: The correction rank \(k\) = (number of extra blocks) × block_size must satisfy \(k < n/4\). When this condition is not met (the system is too far from tridiagonal), the structure is not detected and the auto-selector falls back to SparseLU or Component.

Supported Topologies

All of the following are detected automatically from the CSR sparsity pattern:

Topology Extra blocks Correction rank
Ring (N bodies, closed loop) 2 (blocks (0,N-1) and (N-1,0)) 2·bs
T-junction (main chain + 1 branch) 2 (branch connection, symmetric) 2·bs
Psi-junction (main chain + 2 branches) 4 4·bs
Any combination with k < n/4 ≤ n/4 / bs extra block pairs < n/4

Usage

Engine/BackwardEuler_i (direct solver):

<SIMULATION>
<Timing TStart="0" TEnd="10"/>
<Integrator Method="BackwardEuler_i">
<LinearSolver Type="near_tridiagonal"/>
</Integrator>
</SIMULATION>

SUNDIALS (as preconditioner for SPGMR):

<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="BDF">
<Jacobian Type="none"/>
<LinearSolver Type="SPGMR" Preconditioner="near_tridiagonal"/>
</Integrator>
</SIMULATION>

When <LinearSolver Type="auto"> is in effect and there is a single stateful SimObject with a near-tridiagonal sparsity pattern and density < 0.5, near_tridiagonal is selected automatically.

Performance

The dominant cost is the block-Thomas solve, repeated k+1 times at factorize and once at solve time. For a ring of N bodies (k = 2·bs), the factorize cost is roughly 3× that of a pure chain — still far cheaper than SparseLU or dense LU for large N:

Solver Ring N=20 bodies (bs=6, n=120)
Dense LU O(n³) ≈ 1.7M flops
SparseLU O(nnz·√n) ≈ 150K flops
Near-tridiagonal O((k+1)·N·bs³) ≈ 25K flops

Sparse LU with METIS Ordering

The sparse solver (<LinearSolver Type="sparse">) uses Eigen's SparseLU with METIS nested dissection fill-reducing ordering. METIS is the industry standard for sparse direct solvers on mesh-like sparsity patterns.

Why METIS over COLAMD:

COLAMD (Column Approximate Minimum Degree) is a greedy heuristic that minimises fill-in locally. METIS nested dissection finds global separator sets that partition the graph recursively, producing a much smaller fill footprint for 2D and 3D mesh structures. The asymptotic advantage is well-established:

Problem type COLAMD fill METIS fill
1D chain (banded) O(n·bw) O(n·bw) — same
2D mesh (M×N grid) O(n^1.5) O(n log n) — significantly less
3D mesh O(n^2) O(n^1.33)

For a trawl net or aquaculture net modelled as a single SimObject with a triangulated 2D surface (extruded ring or planar mesh):

  • Each node couples to 4–6 neighbours (circumferential + axial + diagonal)
  • With M nodes per ring and N rings, the Jacobian has bandwidth ≈ M·dof
  • COLAMD ordering gives O(n^1.5) fill; METIS nested dissection gives O(n log n)
  • For n = 1 300 (10×10 net, 13 DOF/node), the difference is roughly 3–5× in fill-in and corresponding factorisation cost

The ordering is computed once at AnalyzePattern time (model initialisation); subsequent Factorize calls reuse the permutation at no extra cost.

Note
For chain or banded systems, METIS and COLAMD produce similar orderings and the performance difference is negligible. METIS is most beneficial when the sparsity graph has 2D or 3D mesh structure — exactly the case for net SimObjects.

Usage (the sparse solver is auto-selected when density < 0.25 and no structured topology is detected; it can also be forced manually):

<SIMULATION>
<Integrator Method="BackwardEuler_i">
<LinearSolver Type="sparse"/>
</Integrator>
</SIMULATION>

Choosing Between Band and Block-Tridiagonal

Both the banded solver (<Jacobian Type="band"> + <LinearSolver Type="BAND"> for SUNDIALS, or <LinearSolver Type="band"> for Engine/BackwardEuler_i) and the block-tridiagonal solver (<LinearSolver Type="block_tridiagonal">) target systems with limited coupling range, but they serve different use cases:

Banded LU Block-Tridiagonal
Structure required Any banded Jacobian Strict block-tridiagonal (chain topology)
Non-uniform blocks Handled naturally Requires uniform or compatible block sizes
SUNDIALS role Direct solver (replaces dense LU) Preconditioner for SPGMR
Jacobian assembly SUNDIALS handles it (analytical or FD) Custom assembly from SimObject Jacobians
Best for Banded but not cleanly block-tridiagonal Clean chains of identical bodies
Asymptotic cost O(N·bw²) where bw = 2·bs-1 O(N·bs³) — ~4× fewer ops for block-tridiagonal

Rule of thumb:

  • If the system is a clean chain of identically-sized SimObjects, use <LinearSolver Type="block_tridiagonal">.
  • If the system has non-uniform coupling, mixed block sizes, or partial dependencies within the band, use <Jacobian Type="band"> + <LinearSolver Type="BAND">.
  • For arbitrary multi-body models with per-SimObject Jacobians and complex coupling topology, use <LinearSolver Type="component"> (see below).

Component Solver

The component solver (<LinearSolver Type="component">) is a direct solver over the connected components of the SimObject coupling graph. The stateful SimObjects are the nodes and the port-Jacobian links the edges. Two SimObjects that are not connected — directly or through other SimObjects — never share a row of \(I - \gamma J\), so each connected component is an independent linear system:

\[ \begin{bmatrix} A_1 & & \\ & A_2 & \\ & & \ddots \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \\ \vdots \end{bmatrix} = \begin{bmatrix} b_1 \\ b_2 \\ \vdots \end{bmatrix}, \qquad A_k = I - \gamma J_k . \]

At Setup the solver partitions the SimObjects into components and picks, per component, the local solver its sparsity allows:

Component structure Local solver
A correction-free block partition exists BlockTridiagonalSolver (block-Thomas, fixed-size kernels for bs ≤ 16)
One exists only with a small Woodbury correction NearTridiagonalSolver (block-Thomas + Woodbury)
Anything else, a single SimObject, or ≤ 64 states Eigen::PartialPivLU (dense LU)

The partition is detected over the component's states in the order the solver discovers its SimObjects (a breadth-first walk of the coupling graph), which is itself bandwidth-reducing. A ring, T-junction or psi-junction chain is therefore usually relabelled into a band and served by BlockTridiagonalSolver at a coarser block size, not by the Woodbury path.

Each component's \(J_k\) is assembled straight from the model — the SimObjects' OdeJacobian() blocks plus the port couplings inside the component — so no dense N×N Jacobian is ever formed (NeedsDenseJacobian() is false). Re-factorizing at a new \(\gamma\) reuses the assembled blocks.

What the solver does not do is decompose a connected system. A model whose stateful SimObjects are all coupled together is one component, and component is then one structured (or dense) direct solve of the whole system. Its saving over dense is the skipped dense assembly and the structured local solver, not a smaller factorization. If your system is one big component with no chain structure, sparse or band is usually the better choice.

The solve is exact — the local solver is picked from the sparsity of everything the component assembles, couplings included, so the partition it settles on covers every assembled entry — so Newton converges quadratically and, as a SUNDIALS SPGMR preconditioner, component is a perfect preconditioner: SPGMR converges in one iteration.

History. Earlier versions described a "Schur complement" mode with an interface between components and a block Gauss-Seidel + GMRES fallback tuned by GsSweeps / GsSymmetric. Because components are, by construction, not coupled to each other, that interface was always empty and the fallback was never reached; the code was removed and the two attributes are now rejected with an error.

Usage

Engine/BackwardEuler_i:

<SIMULATION>
<Timing TStart="0" TEnd="10"/>
<Integrator Method="BackwardEuler_i">
<LinearSolver Type="component"/>
</Integrator>
</SIMULATION>

SUNDIALS CVODE/ARKode with SPGMR:

<SIMULATION>
<Timing TStart="0" TEnd="100"/>
<Integrator Method="BDF">
<Jacobian Type="none"/>
<LinearSolver Type="SPGMR" Preconditioner="component"/>
</Integrator>
</SIMULATION>

When to Use

Scenario Recommended solver
Pure chain of identical bodies block_tridiagonal
Ring, T-junction, psi-junction (single SO) near_tridiagonal (auto-detected)
Several independent (uncoupled) groups of SimObjects component — one factorization per group
Multi-SO sparse system (auto) component (selected automatically)
One large fully-coupled multi-SO system without chain structure sparse or band
Single large monolithic SimObject, chain/ring/junction structure near_tridiagonal
Single large monolithic SimObject, 2D/3D mesh structure (e.g. trawl net) sparse (auto-selected via METIS SparseLU)
Single large monolithic SimObject, very large mesh (n ≥ 5000) iterative or iterative_gmres

SUNDIALS Linear Solver Integration

When using CVODES or ARKODE/DIRK with <LinearSolver Type="SPGMR">, the Preconditioner attribute of <LinearSolver> selects the SUNDIALS preconditioner:

<LinearSolver Preconditioner> SPGMR Preconditioner Description
block_tridiagonal Block-Thomas Assembles and factorizes block-tridiagonal portion; O(N·bs³) per setup
near_tridiagonal Near-Thomas + Woodbury Block-Thomas + Woodbury correction for rings/junctions
component Component direct solve Exact solve per connected component — a perfect preconditioner
jacobi Jacobi diagonal P = diag(I − γJ); the Jacobian diagonal is read from the model, so it works with every <Jacobian Type> including none
none None SPGMR runs unpreconditioned
iterative / iterative_gmres Jacobi diagonal These name iterative Newton solvers; as an SPGMR preconditioner they fall back to the Jacobi diagonal
(default, auto) Structural, else Jacobi diagonal The structural preconditioner of the model's resolved solver type when it has one, otherwise the Jacobi diagonal

The Jacobi diagonal preconditioner needs the Jacobian diagonal. Entries where 1 − γ·J_ii comes out zero, negative or non-finite are left unpreconditioned (P_ii = 1) and reported once as a warning. Preconditioner="jacobi" on a model where no SimObject supplies a Jacobian is an error; with auto the same situation only logs a warning and SPGMR runs unpreconditioned.

Preconditioner only applies to <LinearSolver Type="SPGMR">. Combining it with Type="BAND" or Type="DENSE" is an error — those are direct solvers and use no preconditioning.

Method Order Selection

DIRK Method Selection (ARKodeSetTableNum)

When using ARKODE with Method="DIRK", FhSim uses a default DIRK method (typically 4th order). To select a specific DIRK Butcher table, use the ARKodeSetTableNum option:

<SIMULATION>
<Integrator Method="DIRK">
<SundialsExtra ARKodeSetTableNum="SDIRK_2_1_2"/>
</Integrator>
</SIMULATION>

Available DIRK Methods

Method Name Stages Order Embedding Notes
BACKWARD_EULER_1_1 1 1 Implicit Euler, A-stable, L-stable
IMPLICIT_MIDPOINT_1_2 1 2 Implicit midpoint rule
IMPLICIT_TRAPEZOIDAL_2_2 2 2 Trapezoidal rule (Crank-Nicolson)
SDIRK_2_1_2 2 2 1 Default 2nd order, A-stable
ARK2_DIRK_3_1_2 3 2 1 Implicit part of ARK2 pair
BILLINGTON_3_3_2 3 2
TRBDF2_3_3_2 3 2 TR-BDF2 composite method
KVAERNO_4_2_3 4 3 2 L-stable
ARK324L2SA_DIRK_4_2_3 4 3 2 Implicit part of ARK3 pair
ESDIRK324L2SA_4_2_3 4 3 2 ESDIRK variant
ESDIRK325L2SA_5_2_3 5 3 2 ESDIRK variant
ESDIRK32I5L2SA_5_2_3 5 3 2 ESDIRK variant
CASH_5_2_4 5 4 2 Cash 4th order
CASH_5_3_4 5 4 3 Cash 4th order
SDIRK_5_3_4 5 4 3 Standard SDIRK 4th order
KVAERNO_5_3_4 5 4 3 L-stable
ARK436L2SA_DIRK_6_3_4 6 4 3 Implicit part of ARK4 pair
ARK437L2SA_DIRK_7_3_4 7 4 3 Default 4th order additive
ESDIRK436L2SA_6_3_4 6 4 3 ESDIRK variant
ESDIRK43I6L2SA_6_3_4 6 4 3 ESDIRK variant
QESDIRK436L2SA_6_3_4 6 4 3 Quasi-ESDIRK variant
ESDIRK437L2SA_7_3_4 7 4 3 ESDIRK variant
KVAERNO_7_4_5 7 5 4 L-stable
ARK548L2SA_DIRK_8_4_5 8 5 4 Implicit part of ARK5 pair
ARK548L2SAb_DIRK_8_4_5 8 5 4 Alternate ARK5 pair
ESDIRK547L2SA_7_4_5 7 5 4 ESDIRK variant
ESDIRK547L2SA2_7_4_5 7 5 4 ESDIRK variant

Notes:

  • Methods with an embedding order support adaptive time stepping.
  • Methods without an embedding (marked --) require fixed-step integration.
  • L-stable methods have better damping of high-frequency modes.
  • ESDIRK (Explicit first stage SDIRK) methods share the first stage with explicit methods, useful for IMEX (additive) schemes.

Example: Using Backward Euler

For maximum stability with a simple 1st-order method:

<SIMULATION>
<Timing TStart="0" TEnd="10"/>
<Integrator Method="DIRK">
<SundialsExtra ARKodeSetTableNum="BACKWARD_EULER_1_1" ARKodeSetFixedStep="0.01"/>
<Jacobian Type="dense"/>
<LinearSolver Type="DENSE" NonlinearSolver="newton"/>
</Integrator>
</SIMULATION>
Note
BACKWARD_EULER_1_1 has no embedding, so adaptive stepping is disabled. Use ARKodeSetFixedStep in <SundialsExtra> to set the step size.

BDF Order Control (CVodeSetMaxOrd)

For CVODES with Method="BDF", the solver automatically adapts between orders 1-5 based on error estimates. To limit the maximum order (e.g., for stability reasons or to reduce memory), use CVodeSetMaxOrd:

<SIMULATION>
<Integrator Method="BDF">
<SundialsExtra CVodeSetMaxOrd="2"/>
</Integrator>
</SIMULATION>
Max Order Behavior
1 Equivalent to backward Euler (most stable, least accurate)
2 BDF-2, good balance of stability and accuracy
3-5 Higher accuracy, but may have stability issues for very stiff systems

The default is order 5, which provides the best accuracy for smooth solutions. Reducing the order can help when:

  • The system has algebraic constraints or near-singular Jacobians
  • High-frequency oscillations are polluting the solution
  • Memory is constrained (lower order uses fewer history vectors)

See Also