Gyredocs
Depth Practice
API

Gyre/Mathematics & Numerical Methods

Implicit solver

In one minuteSteady states are found by pseudo-transient Newton–Krylov iteration. The Jacobian is never formed — its action comes from automatic differentiation — and it is preconditioned by p-multigrid with element-block-Jacobi smoothing whose blocks are also obtained by AD.

Written at L2 Practice goes to L4 Theory 3218 words Markdown

The steady problem is Rw(U)=0\mathbf R_w(\mathbf U) = \mathbf 0, where Rw=(wJ) R\mathbf R_w = (wJ)\,\mathbf R is the weak-form residual (the pointwise residual scaled by the diagonal mass matrix). Scaling by the mass balances large and small elements in every norm the solver uses, and makes adjoint variables nodal values of the continuous adjoint.

Pseudo-transient continuation#

Each nonlinear iteration solves

(MΔτ+∂Rw∂U)ΔU=−Rw(U),Δτe=CFL⋅Δte,\left( \frac{\mathbf M}{\Delta\tau} + \frac{\partial \mathbf R_w}{\partial \mathbf U} \right) \Delta\mathbf U = -\mathbf R_w(\mathbf U), \qquad \Delta\tau_e = \mathrm{CFL}\cdot\Delta t_e ,

with an element-local time scale Δte\Delta t_e from the convective and viscous spectral radii. Small CFL gives robust, damped steps far from the solution; as CFL grows the method becomes Newton’s method with an exact Jacobian. The linear systems are solved to a fixed relative tolerance (inexact Newton, lin_tol, 10−210^{-2} by default) and the CFL grows by at most a factor 10 per step, so the terminal convergence is linear, not quadratic: the residual falls by about the factor lin_tol per step. The solver study checks that contraction.

  • CFL evolution. After an accepted full step the CFL is multiplied by the residual reduction factor, clipped to [1.5,10][1.5, 10]; after a rejected step it is divided by 10.
  • Line search. A step length ω∈{1,12,… }\omega \in \{1, \tfrac12, \dots\} is accepted if the state stays admissible (ρ,p>0\rho, p > 0) and the unsteady residual ∥Rw(U+ωΔU)+MΔτωΔU∥\|\mathbf R_w(\mathbf U + \omega\Delta\mathbf U) + \tfrac{\mathbf M}{\Delta\tau}\omega\Delta\mathbf U\| drops below ∥Rw(U)∥\|\mathbf R_w(\mathbf U)\|. Requiring the steady residual to drop would reject legitimate transient steps at low CFL.
  • Inexact Newton. Linear systems are solved to a relative tolerance of 10−210^{-2} by default.
  • Warm starts. When the initial state is the converged solution of a neighbouring problem — the normal situation inside a design loop — continuation starts directly at CFL=104\mathrm{CFL} = 10^4; the line search still protects against a bad step.
  • Round-off floor. If the residual stops halving for six steps in the Newton regime, the solver stops and reports whether the plateau is below the acceptance level.
  • Failure is reported, not raised. A solve ends unconverged when the step limit is reached, the residual stalls above the acceptance level, the residual is not finite or the pseudo-time step collapses; the history carries converged: false and the reason. The discipline then keeps its last converged state as the warm start of the next solve, holds the failed iterate separately (failed_state), and refuses derivative calls until a solve converges (Python API).
Residual historyNonlinear and linear solver convergence
Data table
seriesnonlinear iteration|Rw|
Euler NACA 0012, p = 2: block Jacobi00.366317
Euler NACA 0012, p = 2: block Jacobi10.238266
Euler NACA 0012, p = 2: block Jacobi20.153701
Euler NACA 0012, p = 2: block Jacobi30.128176
Euler NACA 0012, p = 2: block Jacobi40.107526
Euler NACA 0012, p = 2: block Jacobi50.0911261
Euler NACA 0012, p = 2: block Jacobi60.0708977
Euler NACA 0012, p = 2: block Jacobi70.0507337
Euler NACA 0012, p = 2: block Jacobi80.0305722
Euler NACA 0012, p = 2: block Jacobi90.0140312
Euler NACA 0012, p = 2: block Jacobi100.00539924
Euler NACA 0012, p = 2: block Jacobi119.9688e-04
Euler NACA 0012, p = 2: block Jacobi126.0154e-05
Euler NACA 0012, p = 2: block Jacobi138.0257e-07
Euler NACA 0012, p = 2: block Jacobi147.8549e-09
Euler NACA 0012, p = 2: block Jacobi157.7948e-11
Euler NACA 0012, p = 2: block Jacobi161.0461e-12
Euler NACA 0012, p = 2: p-multigrid00.366317
Euler NACA 0012, p = 2: p-multigrid10.238446
Euler NACA 0012, p = 2: p-multigrid20.15374
Euler NACA 0012, p = 2: p-multigrid30.127815
Euler NACA 0012, p = 2: p-multigrid40.10725
Euler NACA 0012, p = 2: p-multigrid50.0910524
Euler NACA 0012, p = 2: p-multigrid60.0707134
Euler NACA 0012, p = 2: p-multigrid70.0507297
Euler NACA 0012, p = 2: p-multigrid80.0306123
Euler NACA 0012, p = 2: p-multigrid90.0141012
Euler NACA 0012, p = 2: p-multigrid100.00544363
Euler NACA 0012, p = 2: p-multigrid110.00100033
Euler NACA 0012, p = 2: p-multigrid126.0277e-05
Euler NACA 0012, p = 2: p-multigrid136.6459e-07
Euler NACA 0012, p = 2: p-multigrid146.4517e-09
Euler NACA 0012, p = 2: p-multigrid154.5689e-11
Euler NACA 0012, p = 2: p-multigrid167.0588e-13
Laminar cylinder Re = 40, p = 2: block Jacobi020.2116
Laminar cylinder Re = 40, p = 2: block Jacobi113.1423
Laminar cylinder Re = 40, p = 2: block Jacobi29.47857
Laminar cylinder Re = 40, p = 2: block Jacobi36.10869
Laminar cylinder Re = 40, p = 2: block Jacobi44.09279
Laminar cylinder Re = 40, p = 2: block Jacobi53.38947
Laminar cylinder Re = 40, p = 2: block Jacobi61.97246
Laminar cylinder Re = 40, p = 2: block Jacobi70.483745
Laminar cylinder Re = 40, p = 2: block Jacobi80.141892
Laminar cylinder Re = 40, p = 2: block Jacobi90.074575
Laminar cylinder Re = 40, p = 2: block Jacobi100.0199166
Laminar cylinder Re = 40, p = 2: block Jacobi110.0024487
Laminar cylinder Re = 40, p = 2: block Jacobi123.2684e-05
Laminar cylinder Re = 40, p = 2: block Jacobi133.2557e-07
Laminar cylinder Re = 40, p = 2: block Jacobi143.2121e-09
Laminar cylinder Re = 40, p = 2: block Jacobi153.0809e-11
Laminar cylinder Re = 40, p = 2: p-multigrid020.2116
Laminar cylinder Re = 40, p = 2: p-multigrid113.1323
Laminar cylinder Re = 40, p = 2: p-multigrid29.44805
Laminar cylinder Re = 40, p = 2: p-multigrid36.06778
Laminar cylinder Re = 40, p = 2: p-multigrid44.06751
Laminar cylinder Re = 40, p = 2: p-multigrid53.34489
Laminar cylinder Re = 40, p = 2: p-multigrid61.94072
Laminar cylinder Re = 40, p = 2: p-multigrid70.477141
Laminar cylinder Re = 40, p = 2: p-multigrid80.141383
Laminar cylinder Re = 40, p = 2: p-multigrid90.0735677
Laminar cylinder Re = 40, p = 2: p-multigrid100.0196538
Laminar cylinder Re = 40, p = 2: p-multigrid110.00245037
Laminar cylinder Re = 40, p = 2: p-multigrid122.8469e-05
Laminar cylinder Re = 40, p = 2: p-multigrid131.9200e-07
Laminar cylinder Re = 40, p = 2: p-multigrid141.6586e-09
Euler NACA 0012, p = 3: block Jacobi00.311716
Euler NACA 0012, p = 3: block Jacobi10.313325
Euler NACA 0012, p = 3: block Jacobi20.138704
Euler NACA 0012, p = 3: block Jacobi30.0970839
Euler NACA 0012, p = 3: block Jacobi40.074958
Euler NACA 0012, p = 3: block Jacobi50.0617388
Euler NACA 0012, p = 3: block Jacobi60.0472716
Euler NACA 0012, p = 3: block Jacobi70.0336438
Euler NACA 0012, p = 3: block Jacobi80.0197131
Euler NACA 0012, p = 3: block Jacobi90.0086172
Euler NACA 0012, p = 3: block Jacobi100.00352864
Euler NACA 0012, p = 3: block Jacobi116.8180e-04
Euler NACA 0012, p = 3: block Jacobi124.6864e-05
Euler NACA 0012, p = 3: block Jacobi136.2728e-07
Euler NACA 0012, p = 3: block Jacobi146.1512e-09
Euler NACA 0012, p = 3: block Jacobi156.0011e-11
Euler NACA 0012, p = 3: block Jacobi161.0292e-12
Euler NACA 0012, p = 3: p-multigrid00.311716
Euler NACA 0012, p = 3: p-multigrid10.313727
Euler NACA 0012, p = 3: p-multigrid20.13878
Euler NACA 0012, p = 3: p-multigrid30.0967875
Euler NACA 0012, p = 3: p-multigrid40.074779
Euler NACA 0012, p = 3: p-multigrid50.0616541
Euler NACA 0012, p = 3: p-multigrid60.0471596
Euler NACA 0012, p = 3: p-multigrid70.0336347
Euler NACA 0012, p = 3: p-multigrid80.0197123
Euler NACA 0012, p = 3: p-multigrid90.00862647
Euler NACA 0012, p = 3: p-multigrid100.00352607
Euler NACA 0012, p = 3: p-multigrid116.7471e-04
Euler NACA 0012, p = 3: p-multigrid124.6216e-05
Euler NACA 0012, p = 3: p-multigrid135.4278e-07
Euler NACA 0012, p = 3: p-multigrid143.3127e-09
Euler NACA 0012, p = 3: p-multigrid151.8404e-11

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

Matrix-free Krylov solver#

The linear solver is restarted flexible GMRES written in JAX control flow (jax.lax.while_loop). The operator is built inside the same traced function:

python
_, lin = jax.linearize(lambda V: residual_weak(V), U)   # one primal sweep, kept by XLA
A = lambda v: idm * v + lin(v)                          # tangent sweep per Krylov iteration
x, info = fgmres(A, M, rhs, x0, m=40, restarts=2, tol=1e-2)

Because linearisation and Krylov loop live in one compiled graph, the primal intermediate values are computed once and every iteration costs only a tangent evaluation. The adjoint solver is the same code with jax.vjp in place of jax.linearize: the transposed operator is exact, with no separately derived adjoint residual.

Orthogonalisation is classical Gram–Schmidt with one re-orthogonalisation pass — two dense matrix–vector products per iteration, as robust as modified Gram–Schmidt and far better suited to GPUs.

Preconditioning without assembling a matrix#

The residual kernel takes its state twice, residual(U_own, geom, th, U_nbr): U_own supplies everything element-local, U_nbr supplies every neighbour trace (and neighbour gradients). Evaluated at U_own = U_nbr = U it is the residual; differentiated with respect to U_own alone it is exactly block diagonal, one dense block per element:

Be=MeΔτe+∂Rw,e∂Ue∣neighbours frozen.\mathbf B_e = \frac{\mathbf M_e}{\Delta\tau_e} + \left.\frac{\partial \mathbf R_{w,e}}{\partial \mathbf U_e}\right|_{\text{neighbours frozen}} .

A single JVP whose tangent is the same unit vector in every element therefore returns one column of all blocks simultaneously. All blocks cost block_size batched JVPs (nvar(p+1)dn_\text{var}(p+1)^d, e.g. 64 for p=3p=3 in 2-D), independent of the number of elements, with no graph colouring and no sparse assembly. The blocks are inverted in one batched call. The linearization study multiplies these inverses with the diagonal blocks of a dense AD Jacobian and finds the identity to 10−1310^{-13}.

Block Jacobi removes high-frequency, element-local error but not smooth error, so its Krylov iteration counts grow with mesh size. Gyre wraps it in a V-cycle over polynomial degrees (default p,⌊p/2⌋,…,0p, \lfloor p/2\rfloor, \dots, 0) on the same mesh:

  • levels: full discretizations at lower degree, linearised about the L2L^2-projected state; the lowest level p=0p=0 is a finite-volume scheme on the curved cells;
  • smoother: damped block Jacobi (ω=0.7\omega = 0.7, two pre- and post-sweeps) or a few block-Jacobi-preconditioned GMRES iterations;
  • transfers: prolongation is nodal interpolation P\mathbf P; residual restriction is PT\mathbf P^{\mathsf T} — the Galerkin restriction, exact because the residual is in weak form;
  • adjoint: the transposed hierarchy uses the same transfers with transposed level operators and transposed blocks, so forward and adjoint solves share one implementation.
Solver effortNonlinear and linear solver convergence
casepreconditionerlevelsNewton stepsKrylov iterationsmax Krylov / stepconvergedfinal / reference residualterminal contraction (mean)terminal contraction (worst step)wall time [s]
Euler NACA 0012, p = 2block Jacobi2161206182yes2.8557e-120.01092350.013420358.5489
Euler NACA 0012, p = 2p-multigrid2, 1, 01614217yes1.9270e-120.0102030.0154498104.262
Laminar cylinder Re = 40, p = 2block Jacobi2151707320yes1.5243e-120.009804970.0099611148.199
Laminar cylinder Re = 40, p = 2p-multigrid2, 1, 01414319yes8.2062e-110.00763280.0086385373.4588
Euler NACA 0012, p = 3block Jacobi3161360202yes3.3017e-120.01179450.017150274.6616
Euler NACA 0012, p = 3p-multigrid3, 1, 01515423yes5.9042e-110.005823040.0061032479.4693

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

A wall-resolved mesh has cells that are hundreds or thousands of times longer than they are thick. Across the thin direction an element is coupled to its neighbours far more strongly than in the others, and a smoother that freezes the neighbours, as block Jacobi does, leaves the error along that direction almost untouched. The coarser levels of the hierarchy have the same cells and inherit the anisotropy: the piecewise-constant level makes the linear solves on such a mesh converge, but they stay slow (first rows of the table below).

With smoother: line the elements are grouped into lines that follow the strong coupling, and the smoother inverts the block-tridiagonal system of every line exactly (Mavriplis 1998; Fidkowski, Oliver, Lu and Darmofal 2005):

T=(B1U1L2B2U2⋱⋱⋱LmBm),Lj=∂Rw,ej∂Uej−1,Uj=∂Rw,ej∂Uej+1.\mathbf T = \begin{pmatrix} \mathbf B_1 & \mathbf U_1 \\ \mathbf L_2 & \mathbf B_2 & \mathbf U_2 \\ & \ddots & \ddots & \ddots \\ & & \mathbf L_m & \mathbf B_m \end{pmatrix}, \qquad \mathbf L_j = \frac{\partial \mathbf R_{w,e_j}}{\partial \mathbf U_{e_{j-1}}},\quad \mathbf U_j = \frac{\partial \mathbf R_{w,e_j}}{\partial \mathbf U_{e_{j+1}}} .
  • Lines come from the mesh. The coupling of two elements that share a face is the face area divided by the distance of their centres, the coefficient of a two-point diffusion flux. An element joins a line through the pair of opposite faces of its strongest direction if that direction is at least four times stronger than the next one (for a box-shaped cell: an aspect ratio of two), and two elements are linked only where both choose the shared face. Elements of isotropic regions belong to no line and are smoothed by block Jacobi; on a mesh without lines the option changes nothing. The lines do not depend on the flow and are the same on every level.
  • Off-diagonal blocks from the neighbour argument. The derivative of the split-argument residual with respect to U_nbr contains every coupling block. The line elements are coloured so that a tangent which is one unit vector in all elements of one colour puts into each line element a column of the block of exactly one line neighbour. Aligned lines need three colours, so the two off-diagonal blocks cost three times the diagonal ones.
  • Factorisation. D1=B1\mathbf D_1 = \mathbf B_1, Dj=Bj−LjDj−1−1Uj−1\mathbf D_j = \mathbf B_j - \mathbf L_j \mathbf D_{j-1}^{-1} \mathbf U_{j-1}, followed by one forward and one backward substitution per application. All lines are processed together; only the steps along a line are sequential.
  • Adjoint. The transposed systems are solved with the same factors, TT=(I+W)T(D+L)T\mathbf T\T = (\mathbf I + \mathbf W)\T (\mathbf D + \mathbf L)\T with Wj=Dj−1Uj\mathbf W_j = \mathbf D_j^{-1}\mathbf U_j, so forward and adjoint solves again share one implementation. The unit tests compare both with a dense solve of T\mathbf T cut out of an AD Jacobian, in two and three dimensions and with the turbulence model.
Linear solves at the converged flow (tolerance 1e-10)Line-implicit smoothing on a wall-resolved mesh
preconditionersystemKrylov iterationsresidual reductiontolerance reachedwall time of the Krylov solve, compiled, preconditioner assembled before [s]
block Jacobi, levels 2-1-0forward2388.9804e-11yes6.18439
block Jacobi, levels 2-1-0transposed (adjoint)2479.4097e-11yes10.9862
line Jacobi, levels 2-1-0forward669.5595e-11yes4.98718
line Jacobi, levels 2-1-0transposed (adjoint)709.4827e-11yes7.34169
block Jacobi, one levelforward7201.1248e-04no4.00668
block Jacobi, one leveltransposed (adjoint)7202.8702e-04no12.8319
line Jacobi, one levelforward4419.9505e-11yes5.62206
line Jacobi, one leveltransposed (adjoint)4658.7870e-11yes5.46822

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

What the option costs. The assembly of the preconditioner takes one more batch of block derivatives per colour on every level, three on aligned lines, so about four times the derivatives of block Jacobi; the measured time of one assembly is in the table below. The storage is the block-Jacobi inverse of every element plus two blocks per stored line slot; the lines are stored in a few tables by length, so the slots exceed the line elements only by the padding within a table (the summary of a case and gyre mesh info report both numbers). The study records the wall times of two small cases, one in two and one in three dimensions; nothing is claimed beyond them.

One assembly of the preconditioner at the converged flowLine-implicit smoothing on a wall-resolved mesh
preconditionerwall time, compiled, fastest of five [s]
block Jacobi, levels 2-1-00.1873
line Jacobi, levels 2-1-04.04077
block Jacobi, one level3.00486
line Jacobi, one level4.08774

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

The blocks of the smoother are the largest thing the solver stores: nvar2(p+1)2dn_\text{var}^2 (p+1)^{2d} numbers per element and level, and two more blocks per line slot with the line smoother. In three dimensions they, and the working memory of their assembly, are what limits the size of a problem on one device. They are also the one part of the solver that need not be exact: flexible GMRES converges with any preconditioner, and converges to the solution of the system it is given.

With solver.precond_precision: single

  • every column of the blocks is rounded to single precision as the batched tangents produce it, so the blocks never exist in double precision and the assembly needs half the memory as well as the storage;
  • the inverses, and the recurrence that factorises the line systems, are computed in double precision from the rounded blocks, a batch of elements at a time, and their results are rounded: a block of a wall-resolved cell with a turbulence model can have a condition number that single precision does not resolve, so the inversion is where the digits are needed;
  • the smoother is applied in single precision, to a rounded copy of the vector it is given.

The residual, the Jacobian–vector and vector–Jacobian products, the Krylov basis and the multigrid transfers stay in double precision. The discrete equations, and therefore the converged flow, the adjoint and every derivative, are unchanged; only the number of Krylov iterations could differ, and on the cases measured it does not:

Double against single precision of the smoother's blocksSingle-precision blocks in the preconditioner
caseblocksunknownsnonlinear stepsKrylov iterations, flow solvedrag coefficientKrylov iterations, forward solveKrylov iterations, transposed solvelinear solutions: relative difference to doublestored blocks [MB]
turbulent flat plate (SA)double6750355400.00575365238249—2.94
turbulent flat plate (SA)single6750355400.005753652382491.7075e-111.47
turbulent flat plate (SA), line smootherdouble6750352670.005753656663—8.8984
turbulent flat plate (SA), line smoothersingle6750352670.0057536566634.4650e-114.4492
laminar sphere (3-D), Re = 100, degree 1double384014792.134864643—1.248
laminar sphere (3-D), Re = 100, degree 1single384014792.1348646434.1034e-140.624

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

What it buys is in the record of the largest solve, which states what happened when the same problem was tried with double-precision blocks on the same device. The default stays double (decision 14).

Lines. For a face shared by the elements ee and ff the coupling is cef=Aef/(de+df)c_{ef} = A_{ef} / (d_e + d_f), with AefA_{ef} the area of the face and ded_e, dfd_f the distances of the two element centres from the face centre (so a periodic neighbour is at its true distance); centres and areas are those of the straight-sided element through the corner nodes. A quadrilateral or hexahedron has one pair of opposite faces per local direction aa; its strength is sa(e)=max⁡(ce,2a,ce,2a+1)s_a(e) = \max(c_{e,2a}, c_{e,2a+1}). The element chooses its strongest direction if sa∗≥4 sseconds_{a^*} \ge 4\, s_{\text{second}}. For a box of sizes hah_a, c∝hbhc/hac \propto h_b h_c / h_a, so the ratio of two directions is (hb/ha)2(h_b/h_a)^2 and the threshold is an aspect ratio of two. Two elements are linked where each chooses the face they share; every element then has at most two links, so the connected sets are paths, or closed loops across a periodic direction, which are cut at their weakest link. A single anisotropic layer next to isotropic cells forms no line, because its neighbour does not choose the shared face.

Colours. Let N(e)\mathcal N(e) be ee and its face neighbours that belong to lines. A tangent that is the unit vector v\mathbf v in every element of colour cc and zero elsewhere gives, differentiated through the neighbour argument,

(∂Rw∂Unbr tc)e=∑f∈N(e), colour⁡(f)=c∂Rw,e∂Uf v.\Bigl(\frac{\partial \mathbf R_{w}}{\partial \mathbf U_\text{nbr}}\,\mathbf t_c\Bigr)_e = \sum_{f \in \mathcal N(e),\ \operatorname{colour}(f) = c} \frac{\partial \mathbf R_{w,e}}{\partial \mathbf U_f}\,\mathbf v .

This is one column of the block of a line neighbour ff of ee exactly when no other member of N(e)\mathcal N(e) shares the colour of ff: that is the condition the colouring enforces, for every line element and both of its line neighbours. On a block of aligned lines the position along the line modulo three satisfies it (the line neighbours of ee have the two other colours, the neighbours across the lines have the colour of ee itself), hence three colours. Where lines of different directions meet (the wall-normal lines and the circumferential loops of an O-mesh) more can be needed, and no single heuristic is best on every mesh: a saturation-degree colouring and three greedy passes in different orders are tried, and the colouring with the fewest colours is kept. The number is part of the case summary. The cost of assembling the preconditioner is therefore (1+ncolours) nblock(1 + n_\text{colours})\,n_\text{block} batched tangents per level where block Jacobi takes nblockn_\text{block}.

Factors. Per line, with Dj\mathbf D_j from the recurrence above, Gj=Dj−1Lj\mathbf G_j = \mathbf D_j^{-1}\mathbf L_j and Wj=Dj−1Uj\mathbf W_j = \mathbf D_j^{-1}\mathbf U_j are stored, and Dj−1\mathbf D_j^{-1} takes the place of the block-Jacobi inverse of the element: two blocks per line slot in addition to the one per element that block Jacobi stores. Lines of very different length occur together (the circumferential loops in the far field of an O-mesh next to its wall-normal lines), and one table as long as the longest line would be mostly padding that is factorised, stored and swept like real slots. The lines are therefore sorted by length and cut into at most four tables so that the stored slots are fewest; a further table is added only while the slots exceed the line elements by more than a quarter. One application is

c=D−1b,zj=cj−Gjzj−1,xj=zj−Wjxj+1,\mathbf c = \mathbf D^{-1}\mathbf b,\qquad \mathbf z_j = \mathbf c_j - \mathbf G_j \mathbf z_{j-1},\qquad \mathbf x_j = \mathbf z_j - \mathbf W_j \mathbf x_{j+1},

and the transposed one, from TT=(I+W)T(D+L)T\mathbf T\T = (\mathbf I + \mathbf W)\T(\mathbf D + \mathbf L)\T,

yj=bj−Wj−1Tyj−1,uj=yj−Gj+1Tuj+1,x=D−Tu.\mathbf y_j = \mathbf b_j - \mathbf W_{j-1}\T \mathbf y_{j-1},\qquad \mathbf u_j = \mathbf y_j - \mathbf G_{j+1}\T \mathbf u_{j+1},\qquad \mathbf x = \mathbf D^{-\mathsf T}\mathbf u .

The first (last) product is taken for all elements at once and is all that elements outside the lines receive, which is block Jacobi. The two sweeps run, table by table, over the slots of the longest line of the table; each step is one batched block product over its lines.

What stays approximate. T\mathbf T contains the coupling along the lines only. The coupling across lines and between elements outside them is left to the Krylov iteration and to the other levels, so line Jacobi is a smoother with the same damping as block Jacobi, not a solver. The lines follow the mesh, not the flow: strong convection across thin cells is not what they are built for. A cell that is thin in two directions at once (the band of cells along the edge where two walls meet in three dimensions) has two strong directions of similar strength, fails the anisotropy test and gets no line: it stays with block Jacobi.

Let cRc_R be the cost of one residual evaluation. A Krylov iteration at the fine level costs about one tangent sweep (≈1\approx 1–2 cR2\,c_R) plus the V-cycle (a few fine-level operator applications for smoothing plus cheaper coarse levels): counted from the cycle, about 7.6 fine-level applications per iteration with the default sweeps and the levels 2, 1, 0 in two dimensions, against one for block Jacobi. The hierarchy therefore saves operator work only where it cuts the iteration count by more than that factor (measured per case). Building the block inverses costs block_size tangent sweeps of the split residual and is amortised over all Krylov iterations of a Newton step (or several, with precond_lag). Memory is dominated by the Krylov basis (2 × krylov_dim state vectors for flexible GMRES) and the block inverses (K⋅K \cdot block_size2^2): for p=3p=3 in 3-D the blocks are 320×320320\times320 per element, which bounds what fits on a single small GPU.

The dominant fixed cost in practice is JIT compilation: tens of seconds per new combination of mesh size and degree. Sessions exist to pay this once per design study. This cost model is an operation count, not a measurement: the solver study records Krylov iteration counts and does not show that the V-cycle lowers total work or wall time.

    ↑ ↓ moveEnter open[ ] change depthResults show section and depth level