Spectral Fourier–Helmholtz elliptic solve¶
solvax.elliptic inverts a separable elliptic operator of Helmholtz type on a
tensor-product grid with one periodic axis (\(z\)) and one bounded axis (\(x\)):
with metric weights \(g_{11}(x)\), \(g_{33}(x)\) and a right-hand-side scale \(s(x)\) that depend only on the bounded coordinate. This is the \(\nabla_\perp^2\phi=\rho\) potential inversion used by reduced drift-plane and vorticity models, where \(\phi\) is the electrostatic potential and \(\rho\) the generalized vorticity.
Method¶
Because the coefficients are independent of \(z\), a real FFT diagonalizes the periodic direction [Swa77]. For each Fourier mode \(k_z\),
a tridiagonal system along \(x\) after second-order finite differencing. All
\(n_z/2+1\) modes are solved simultaneously through one call to
solvax.tridiagonal.tridiagonal_solve() with the \(x\) axis leading and the
mode index batched, so the whole inversion costs one FFT, one batched Thomas
sweep of \(O(n_xn_z)\) work, and one inverse FFT — direct, not iterative.
The \(x\) boundaries use a reflected closure consistent with the reduced drift-plane potential solve; the discrete operator applied to the returned solution reproduces the (scaled, transformed) right-hand side mode by mode, which is how the implementation is pinned in the test suite.
Usage¶
Build the operator once for a fixed geometry, then reuse it across right-hand sides (e.g. every timestep of a turbulence simulation):
operator = sx.build_fourier_helmholtz_operator(
dx=dx, dz=dz, g11=g11, g33=g33, rhs_scale=rhs_scale, nz=nz,
)
phi = sx.solve_fourier_helmholtz(rho, operator=operator)
Input |
Meaning |
|---|---|
|
length- |
|
periodic spacing and length; |
|
real |
|
tridiagonal backend; |
The result is the real (nx, nz) solution. method="auto"/"lax" selects
the fused vendor kernel on JAX builds whose complex tridiagonal solve supports
it; the pure-lax.scan Thomas default is portable and bit-reproducible.
Transforms and differentiation¶
Both routines are pure JAX: the solve composes with jit, batches under
vmap (e.g. over stacked right-hand sides), and differentiates through the
FFTs and the tridiagonal sweep with jax.grad — so the geometry weights
g11/g33 and the right-hand side are all valid differentiation targets.
Comparison with alternatives¶
Compared with FGMRES/PCG on the assembled 2-D operator, the spectral solve is direct: no iteration count, no preconditioner tuning, exact separable inverse. Use the Krylov path instead when the coefficients depend on \(z\) (non-separable geometry) or the stencil is not Helmholtz-like.
Compared with
cyclic_tridiagonal_solveper \(z\) line, the Fourier route treats the periodic direction spectrally and keeps the bounded direction tridiagonal, which is the natural split when \(g_{11}, g_{33}\) vary in \(x\).As a preconditioner, the operator built from a frozen or simplified geometry is an effective coarse inverse for FGMRES on the full non-separable problem.
API summary¶
Runnable counterpart: examples/22_fourier_helmholtz.py.