Skip to content

feat: mixed Hermite-Legendre 1D Vlasov-Poisson solver (arXiv:2606.12322)#289

Open
joglekara wants to merge 9 commits into
mainfrom
feat/hermite-legendre-1d
Open

feat: mixed Hermite-Legendre 1D Vlasov-Poisson solver (arXiv:2606.12322)#289
joglekara wants to merge 9 commits into
mainfrom
feat/hermite-legendre-1d

Conversation

@joglekara

@joglekara joglekara commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

New ADEPTModule (hermite-legendre-1d) implementing the mixed Hermite–Legendre spectral method of Issan, Delzanno & Roytershteyn (arXiv:2606.12322) for the 1D-1V electrostatic Vlasov–Poisson system.

The electron distribution is split f = f0 + δf:

  • f0 (near-Maxwellian bulk) → asymmetrically-weighted (AW) Hermite expansion in velocity, coeffs C_n(x,t)
  • δf (strongly non-Maxwellian features: beams, plateaus, trapping structure) → Legendre expansion on a bounded velocity window [v_a, v_b], coeffs B_m(x,t)

The highest Hermite coefficient C_{Nh-1} feeds the Legendre modes (one-way), and both feed the field through Poisson. At fixed total velocity DOFs the mixed method is more accurate than pure Hermite or pure Legendre when non-Maxwellian features are localized in velocity.

Structurally modeled on BaseVlasov1D; reuses the Lawson-RK4 design of the sibling _hermite_poisson_1d.

Design notes

  • Three integrators, config-selected: lawson (explicit Lawson-RK4; both free-streaming operators are symmetric-tridiagonal so streaming+collisions are integrated exactly via prediagonalized matrix exponentials), imex (Lawson + frozen-E backward-Euler Lorentz substep; lifts the ~Nl²/width·|E| force CFL), and implicit (implicit midpoint via AD-JFNK: matrix-free GMRES with exact autodiff JVPs; A-stable, conserves mass exactly and energy to solve tolerance — required for saturated/long-time regimes, e.g. bump-on-tail, where the explicit and IMEX paths blow up).
  • JFNK preconditioning: streaming+collision factor only (per-k tridiagonal, exact for the stiff skew streaming spectrum). Preconditioning the Lorentz-force block stalls GMRES at saturation-scale fields — the force factors are strongly non-normal nilpotent ladders; measured and documented in config.md. At dt ≳ 0.05 with large |E|, unpreconditioned is the robust choice.
  • Choosing Nh (documented in config.md): keep the Hermite basis bulk-only. At large Nh a spurious k=0 mid-n velocity-space cascade (nonlinear force-ladder pump vs the weak mid-n window of the cubic collision spectrum) destroys post-saturation runs at any practical ν_H; small Nh closes the window structurally (bump-on-tail: Nh=32 matches Nh=128 field observables to 3 digits).
  • External longitudinal driver (drivers.ex, vlasov1d conventions) for driven-wave studies; never enters the Poisson solve.
  • Space is spectral (Fourier, periodic); conservation constraint J_{Nh,0..2}=0 (paper §3.4/§4) and Lenard-Bernstein artificial collisions (cubic spectrum, zero on the first three moments) implemented and config-exposed.

What's included

  • adept/_hermite_legendre_1d/{vector_field,modules,storage}.py + public entry; registered in _base_ dispatch and adept.__init__
  • Configs: configs/hermite-legendre-1d/{linear-advection,two-stream,two-stream-imex,bump-on-tail}.yaml
  • Docs: docs/source/solvers/hermite_legendre_1d/config.md + quick links
  • CI: test-hermite-legendre-1d job (plus the previously-missing test-hermite-poisson-1d job from feat: native 1D Hermite-Poisson solver (_hermite_poisson_1d) #287)

Physics validation

  • Linear advection (§4.2): reconstructed f = f0 + δf matches analytic f(x−vt, v, 0) to <2% L2 before recurrence; streaming-matrix eigenvalues match Gauss nodes; J_{Nh,m} parity matches eqns 27/29/34.
  • Driven Landau damping (kλ_D = 0.30, 0.35): free-ringdown frequency to <1% and damping to ~10% of the kinetic dispersion root; with the window on the resonance, Nh=128 reproduces the root to 4 digits (ω 1.1597 vs 1.1598, γ −0.01261 vs −0.01262).
  • Two-stream: conservation gate (mass/momentum ~1e-13); IMEX stable at dt where explicit blows up, saturating at the converged field energy; implicit midpoint mass-exact and energy-conserving at dt=0.05.
  • Bump-on-tail (n_b=0.01, v_d=10, Chapurin-style params; the hardest case): runs through saturation to tmax=120 with integrator: implicit. Measured on E(k=1): γ=0.0895 vs kinetic theory 0.0898, ω=0.9058 vs 0.9052; field energy saturates at 2.7 (t≈94) relaxing to 1.55; energy drift 2e-16; Nh=32 vs Nh=128 field observables agree to 3 digits.
  • Driven nonlinear EPW (exploratory, on-branch scripts): trapping physics captured — O'Neil damping shutoff (γ_eff ≈ γ_L/4), bounce-period envelope oscillation matching τ_b = 2π/√(kE), Morales–O'Neil-like frequency downshift.
  • uv run pytest tests/test_hermite_legendre_1d17 passed.

GPU validation (Perlmutter A100, 1 GPU)

Canonical run of the shipped configs/hermite-legendre-1d/bump-on-tail.yaml through ergoExo on a Perlmutter A100 — MLflow run (experiment hermite-legendre-1d, run c92312ac).

Mac CPU (M-series) A100
per implicit step (Nh=32, Nx=Nl=128) ~100–220 ms 30 ms
full tmax=120 through saturation ~35–60 min 3.0 min
per step (Nh=128) ~224 ms 37–41 ms
  • Physics identical to CPU to 4 digits: field energy saturates at 2.726 (t≈94) → 1.548 (t=120) vs 2.727 → 1.548 on CPU; energy drift 3.4e-16; mass exact.
  • The complex-banded tridiagonal preconditioner solve (jax.lax.linalg.tridiagonal_solve) works on the GPU/cuSPARSE path.
  • The Nh=128 mid-n cascade reproduces identically on GPU — platform-independent semi-discrete dynamics, consistent with the config.md guidance.

Test plan

  • uv run pytest tests/test_hermite_legendre_1d -q (17 passed locally; now in CI)
  • Bump-on-tail through saturation via ergoExo/MLflow (config as shipped)
  • uv run run.py --cfg configs/hermite-legendre-1d/two-stream → phase-space vortex (Fig 8), flat conservation diagnostics (Fig 7)

🤖 Generated with Claude Code

joglekara and others added 6 commits June 14, 2026 22:24
New ADEPTModule implementing the mixed Hermite-Legendre spectral method of
Issan, Delzanno & Roytershteyn (arXiv:2606.12322) for the 1D-1V electrostatic
Vlasov-Poisson system. The electron distribution is split f = f0 + df: the
near-Maxwellian bulk f0 is expanded in the AW-Hermite basis, while the
strongly non-Maxwellian part df is expanded in the Legendre basis on a bounded
velocity window. The two are coupled one-way (highest Hermite mode -> Legendre)
and through Poisson.

Modeled structurally on BaseVlasov1D and reusing the Lawson-RK4 design of
_hermite_poisson_1d. Both free-streaming operators are symmetric-tridiagonal in
mode index, so each is integrated exactly via a prediagonalized matrix
exponential; the E-field force, the Legendre Dirichlet penalty, and the
Hermite->Legendre coupling are advanced explicitly. Space is spectral (Fourier,
periodic). An explicit integrator is used by choice (the paper's implicit
midpoint has a large memory footprint): mass and momentum are conserved to
machine precision, energy to the time-integrator order (dt-convergent).

- adept/_hermite_legendre_1d/{vector_field,modules,storage}.py + public entry
- registered as solver "hermite-legendre-1d" in _base_ dispatch and adept.__init__
- conservation constraint J_{Nh,0..2}=0 and Lenard-Bernstein artificial collisions
- configs: linear-advection, two-stream, bump-on-tail (paper parameters)
- tests: streaming/J-integral units, linear advection vs analytic, conservation
- docs: solvers/hermite_legendre_1d/config.md + quick links

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…u test

Adds to the mixed Hermite-Legendre solver:

- External longitudinal `ex` driver (ExternalExDriver), mirroring the vlasov1d /
  hermite_poisson `ex` driver: a prescribed E added to the velocity-space force
  (never enters Poisson), e.g. a resonant EPW kick. Saved as `de` in fields.

- IMEX integrator (grid.integrator: imex). The explicit Lawson-RK4 step blows up
  once the E.d_v f Lorentz force gets stiff (operator norm ~Nl^2/width*|E| for the
  Legendre block, the dominant term). IMEX keeps streaming/collisions/closure-flux
  explicit and advances the Lorentz force with an unconditionally stable frozen-E
  Backward-Euler substep (first-order Lie split), mirroring _spectrax1d/imex_E.py.
  Two-stream then runs stably at dt=0.02 (vs explicit's 0.002), mass conserved to
  ~1e-12. Current impl uses a per-x dense solve; structured (bidiagonal/Woodbury)
  solve noted as the large-Nx optimization.

- Driven Landau-damping test (test_landau_damping): drives a uniform Maxwellian at
  the resonant (k0, w0) and measures E_x(k1) ringing -- frequency matches the
  kinetic dispersion to <1%, damping to ~5-9% (finite-Nh Hermite).

- IMEX stability test (test_imex): explicit blows up at dt=0.01, IMEX stays finite
  and conserves mass.

- Fix: _hermite_function_values overflowed float64 at Nh>=171 (formed 2^n n!
  directly), silently zeroing the J_{Nh,m} coupling -- now a stable normalized
  recurrence. Seed the t=0 field diagnostics from the actual Poisson field.

- two-stream.yaml dt 0.01 -> 0.002 (explicit CFL); add two-stream-imex.yaml.

Suite: 16 passed. Two-stream works in both explicit and IMEX; bump-on-tail (the
hardest case: asymmetric domain, weak instability, long time) still blows up near
saturation in both -- needs the implicit-midpoint path (next commit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… preconditioner

Adds `grid.integrator: implicit` -- the implicit-midpoint rule
y1 = y0 + dt F((y0+y1)/2) solved by Jacobian-free Newton-Krylov, with the Jacobian
applied as an EXACT autodiff JVP (jax.linearize) to a matrix-free GMRES
(jax.scipy.sparse.linalg.gmres). The Jacobian is never assembled (memory is the state
plus a few Krylov vectors), so it is the laptop-feasible method the paper uses, here
realized with autodiff. Complex coefficients are carried as real (re,im) pytree leaves
so the Krylov inner products are the standard real ones.

Implicit midpoint is A-stable (no CFL) and conserves quadratic invariants: on two-stream
it conserves mass exactly and energy to the solve tolerance (2nd order in dt), and is
stable far past where explicit/IMEX die.

Preconditioning (`grid.precondition: true`, default) is essential -- unpreconditioned
GMRES stalls on the skew streaming spectrum and Newton then injects energy. The
preconditioner M = I - dt/2(L_stream + L_force) is applied as a composition of cheap
structured solves: streaming is block-diagonal in k and tridiagonal in mode (per-k
tridiagonal solve); the Hermite force is lower-bidiagonal and the Legendre force is
dominated by the lower-triangular derivative matrix (per-x triangular solves; the rank-2
Dirichlet penalty is left to GMRES). This takes bump-on-tail's linear-phase energy drift
from 928% (unpreconditioned) to ~1e-9.

Config knobs: integrator, precondition, newton_iters, gmres_restart/maxiter/tol.

bump-on-tail.yaml -> integrator: implicit, precondition, dt=0.02. The bump saturation
phase needs a small step for the nonlinear solve to converge (the paper uses dt=0.01);
large-dt robustness at saturation is the motivation for a learned preconditioner
(follow-up). test_implicit gates mass-exact / energy-conserving implicit midpoint on
two-stream. Suite: 17 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n-tail via bulk-only Nh

The implicit-midpoint JFNK stalled at bump-on-tail saturation. Two causes,
both measured at production resolution (128^3, dt=0.02):

1. The force preconditioner applied its per-x solves to k-space arrays
   (no ifft/fft), pairing E(x_j) with Fourier mode k_j -- exact only for
   uniform E, and it zeroed GMRES progress at saturation fields. Even the
   FFT-corrected factor diverges as a preconditioner: the force factors are
   strongly non-normal nilpotent ladders (norm ~ Nl^2/width * |E|), and
   inverting them scrambles an otherwise well-conditioned system. Removed;
   `precondition: true` now means the (exact) streaming+collision factor
   only, and unpreconditioned GMRES is the robust choice at dt >~ 0.05.

2. With the solve fixed, large Nh developed a spurious k=0 velocity-space
   cascade at mid-n (pump |E|sqrt(2n)/alpha beats the cubic hyper-collision
   damping in its weak mid-n window) that detonates shortly after
   saturation at any practical nu_H (0, 10, 30 all measured to fail).
   Keep the Hermite basis bulk-only: Nh=32 reproduces the Nh=128 field
   observables to 3 digits with machine-precision energy conservation.

Bump-on-tail now runs to tmax=120 through saturation: gamma=0.0895 and
omega=0.9058 on E(k=1) vs kinetic-theory 0.0898 / 0.9052, field energy
saturating at 2.7 (t~94) and relaxing to 1.55, energy drift 2e-16.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neither module's test suite was wired into cpu-tests.yaml (the
hermite-poisson gap dates to #287). Both pass locally: 17 tests
in ~2 min, 19 tests in ~30 s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
joglekara and others added 3 commits July 8, 2026 17:02
…process

When both hermite and legendre coefficient saves are present, reconstruct
f(x,v,t) = sum C_n psi_n + sum B_m xi_m at up to six snapshot times and emit
(1) a phase-space facet figure f(x,v) on a shared log scale, (2) a
times x {|C_n(x)|, |B_m(x)|} coefficient-magnitude facet figure, and
(3) the reconstruction as a netCDF dataset -- all logged as MLflow artifacts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iagnostics

axes/idx zip is strict=False by design: the facet grid can have more axes
than snapshots (extras are hidden).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant