FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
0035 — The reverse (row) colouring costs O(n · nnz) at every model setup and nothing reads it
ID 0035
Class ARCHITECTURE
Severity 2
Status ready
Models — (model assembly: JacobianSparsityAnalyzer::BuildReverseColoringGroups)
Found 2026-09-28, fixing FHSIM-0031 and FHSIM-0032 (net-hydrodynamics deep review, worktrees/nethydro/review/deep/net-force-performance.md A1/A2), at 3a187c5b
Decision needed —

Evidence

JacobianAssembler::Initialize → AllocateHybridJacWorkspace → JacobianSparsityAnalyzer::Build runs BuildReverseColoringGroups() for every model, whatever the integrator. When the pattern is sparse, its row conflict graph is built at src/engine/model/JacobianSparsityAnalyzer.cpp:890 as

for (int c = 0; c < nStates; ++c) {
    for (int r = 0; r < nStates; ++r) {
        for (int p = rowPtr[r]; p < rowPtr[r + 1]; ++p) {
            if (colIdx[p] == c) { ... break; }

that is, the whole pattern is scanned once per column: O(n · nnz). For a model that declares only its diagonal (n = nnz = 46 341), that is 2.1 · 10⁹ comparisons; JacobianSparsityAnalyzer.DiagonalModelAboveIntSquareLimit_BuildsOneColourGroup takes 1.5–1.9 s, nearly all of it here. By the same count (estimate, not measured), a sparse model of 30 000–50 000 states with ~20–30 entries per row would spend 10–60 s on it.

The result has no reader: GetReverseColorGroups() and GetRowToCols() are called nowhere in src/, include/ or tests/ (the adjoint derivative sweeps use the dense Jacobian from OdeJacobian, and tests/integrator/methods/TestObjects.cpp AdjointDD.ReverseColoringGroups* rebuild a colouring by hand). m_rowToCols also keeps a second copy of the pattern (O(nnz)) for the run.

Effect

Setup time only; no number depends on it. It does not show in the FHSIM-0032 measurements because those nets are dense (NetStructure keeps the default GetJacobianSparsity), which takes the reverse colouring's O(n) dense branch; it will show as soon as a large model declares its sparsity (net-force-performance.md A3).

Possible fix

Either drop BuildReverseColoringGroups, m_reverseColorGroups, m_rowToCols and their accessors (no reader), or, if a row-compressed adjoint sweep is planned, build the rows-per-column lists by one transpose pass (the forward colouring already builds m_colToRows, which is exactly that) and derive the conflict graph from them: O(nnz + Σ_c rows(c)²).

Test that would prove it

A diagonal model of 46 341 states: Build() well under 0.1 s (the existing DiagonalModelAboveIntSquareLimit_BuildsOneColourGroup with a time bound), and, if the colouring is kept, the reverse groups of the existing sparse fixtures unchanged.

Risk

None for results: the groups feed no computation. Removing the accessors changes the internal (non-installed) JacobianSparsityAnalyzer interface only.