feat: native 1D Hermite-Poisson solver (_hermite_poisson_1d)#287
Merged
Conversation
New module `adept/_hermite_poisson_1d/` implements a clean 2D (Nn, Nx)
Hermite-Fourier × Fourier-space Poisson solver, bypassing the 6D
Spectrax tensor layout (Np, Nm, Nn, Ny, Nx, Nz) that has unnecessary
overhead for strictly 1D problems.
Key components:
- vector_field.py: FreeStreamingExp1D (eigendecomposition), DiagonalExp1D
(hypercollision + hyper-diffusion), PoissonSolver1D, TransverseWaveDriver,
HermitePoisson1DVectorField (Lawson-RK4 + WaveSolver, Stepper convention)
- modules.py: BaseHermitePoisson1D(ADEPTModule) lifecycle with wave-CFL
timestep, sponge support, static-ion Poisson neutralization
- storage.py: save functions for fields {e, a, da}, hermite coefficients,
and scalar diagnostics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… tracer errors float(self.x_a[-1]) inside __call__ fails under JAX tracing because x_a is a JAX array whose elements become abstract tracers. Move all static scalar extraction to __init__. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… config
DiagonalExp1D now accepts hou_li_strength and hou_li_col_1d; applies
exp(-strength * (n/Nn)^order * s) per Lawson substep in addition to the
existing hypercollisional nu term.
BaseHermitePoisson1D.init_diffeqsolve reads drivers.hermite_filter
{enabled, strength, order} and passes the computed profile to DiagonalExp1D.
Previously the hermite_filter config key was silently ignored.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…avenumber scale Two physics-breaking bugs, both now locked by tests: 1. _hermite_e_coupling read C[n+1] where the AW-Hermite force term (Parker & Dellar 2015 eq. 3.11) requires C[n-1]. With the inversion, a uniform E field applied to a Maxwellian drove zero current — the momentum equation was structurally absent, Landau resonance did not exist, and E never did work on the plasma. The error was transcribed from spectrax1d shift_multi's then-incorrect docstring (fixed separately) rather than from its behavior. 2. FreeStreamingExp1D divided the wavenumber by Lx a second time (modules.py already passes physical kx = 2*pi*fftfreq*Nx/Lx), making free-streaming Lx-times too slow. Together these explain the spurious field-free low-n instability that killed every gradient-SRS production run at 1.2-1.4 ps regardless of filter shape, x-resolution, or integrator. Also adds an optional density.perturbation config (single-mode cosine on the electron density) so dispersion tests can run the production path. New tests (tests/test_hermite_poisson_1d/): - uniform-E-on-Maxwellian current drive, one-sided ladder structure, density-never-forced, and exact match against the validated spectrax1d Lorentz term - full-path EPW dispersion: frequency and Landau damping vs kinetic theory at klambda_D = 0.30, 0.35 — both now agree to <=0.2% Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… term with tests; cover Dopri8 path The shift_multi code has always been correct (dn=-1 -> source at n-1), but its docstring and an inline comment stated the OPPOSITE mapping. Code written against that wording instead of the behavior shipped an inverted E.dv f coupling in _hermite_poisson_1d (fixed in the previous commit). The docs now state the verified semantics and warn against re-implementing from the description. New tests (test_shift_and_lorentz.py) pin behavior so docs and code cannot silently diverge again: - shift_multi direction semantics with distinguishable values - uniform-E-on-Maxwellian responds only at n=1 with (q/m)*sqrt(2)/alpha*E*C0 - the force ladder is one-sided upward (C2 -> C3 only) test_landau_damping.py: the docstring advertised explicit (Dopri8) legs that were never parametrized — the explicit integrator path went untested for its entire history. Added a Dopri8 static-ions leg (passes: 1% frequency, 8% damping error) and corrected the docstring. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Blown-up runs produce inf in the saved scalars/fields; matplotlib's log tickers raise OverflowError on inf, which aborted post_process before the netCDF/metric upload — losing diagnostics for exactly the runs that need them most. Sanitize non-finite values to nan (masked by matplotlib) and wrap each figure in try/except so plotting can never block the upload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The E/ponderomotive coupling multiplies two fields in real space; without truncating both factors to |k| <= Nx//3 and masking the result, beyond-Nyquist products alias back into the resolved band. The spectrax1d base applies its mask23 to every Lorentz product and the vlasov1d reference runs a grid-scale Hou-Li filter in x for the same reason; the HP transcription had dropped both. Suspected trigger for the trapping-onset blowups in gradient-SRS runs: n-space filter escalation (o8s36 -> o4s64 -> o4s256 -> o3s8) only delayed death while the death amplitude stayed pinned at the trapping threshold (Ex ~ 1.5e-3), i.e. the detonation tracks the appearance of spatial harmonics, not ladder truncation. EPW dispersion tests unchanged to 4 digits (single-k linear physics is alias-free); all HP tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The default hypercollision profile (cubic, normalized to the truncation edge) provides no dissipation in the Landau-resonance band — the collisionless truncated AW-Hermite hierarchy then detonates at trapping onset (amplitude-triggered; insensitive to filter shape/strength, Nx, 2/3 dealiasing, and a 4x dt range; see kinetic-srs gradient-SRS campaign notes). LB is the principled regularization: Hermite functions are exact LB eigenfunctions, so col[n] = n gives gamma_n = nu*n — smooth velocity diffusion at all kinetic scales including the resonance/vortex region, the Hermite analog of the grid-scale velocity dissipation the vlasov1d reference gets from its spline advection. Damping is gated off n = 0, 1, 2 (standard conserving truncation, cf. Parker & Dellar 2015) so collisions conserve density, momentum, and energy exactly and impose no drag on the EPW's fluid content. Config: physics.collision_model = "hypercollision" (default, unchanged) | "lenard-bernstein". Unit tests verify exact exp(-nu*n*t) decay for n >= 3 and exact conservation of the first three moments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The vlasov1d reference seeds SRS with continuous white-in-time Gaussian
force noise on Ex (kinetic_srs StochasticVlasovMaxwell): drawn fresh each
step via fold_in(seed, round(t/dt)), density-weighted, injected into the
v-advection force only (never Poisson or the wave solver). The HP module
only had a one-shot initial density perturbation, which velocity-space
dissipation (LB collisions) can starve before SRS amplifies it.
Same schema (stochastic_noise: {enabled, amplitude, seed}), same
determinism, same density weighting from the initial electron state.
Lawson-specific detail: the realization is drawn once at the step head
and frozen across the four RK4 stages (like a_frozen) — per-stage draws
would round t/dt inconsistently mid-step.
Tests: draw determinism + amplitude scaling, stage-freezing, and a
drives-kinetic-response/no-spurious-source pair.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…file=knee)
Scan 17 produced the first genuine seeded SRS (gamma~3.7e-3, intensity-
ordered) but exposed a scale-separation problem: the LB nu that regularizes
the n~40-300 filamentation cascade also damps the resonant EPW mode
(n~vphi^2/2~24) at nu*24, pushing the SRS threshold above 1.442e14 (vlasov
has 17% there). The (n/Nn)^order Hou-Li profile can't separate them — at
Nn=1024 the resonance and cascade sit at n/Nn=0.023 vs 0.05.
Add profile="knee": col(n) = 0.5*(1+tanh((n-n_knee)/width)), an absolute-n
step ~0 below n_knee and ~1 above. Paired with a low LB nu it preserves the
resonance while strongly damping the cascade. Config: drivers.hermite_filter
{profile: knee, strength, n_knee, width}; default profile "houli" unchanged.
Tests: profile shape (resonance<0.02, cascade>0.95, monotonic), houli default
intact, and a dynamic k=0 run showing n=24 preserved (>0.95) while n=80
decays (<0.2) over 20 wp^-1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a LongitudinalElectricFieldDriver that injects a prescribed Ex into
the velocity-space force (alongside the Poisson field, ponderomotive
force, and stochastic noise), matching adept._vlasov1d's driver:
E_drive(x,t) = Σ env(x,t) (w0+dw0) a0 sin(k0 x − (w0+dw0) t)
Reads the flat cfg["drivers"]["ex"] pulse dict (same format as the ey
driver), evaluated at each Lawson-RK4 substep so its time variation is
captured within a step. Exposed as the "de" diagnostic field in the
state and the "fields" save.
Also add α to ruff allowed-confusables (physics notation, like the
existing γ) and drop a useless static_ions ternary surfaced by RUF034.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Formatting-only (line-wrapping); these files were committed unformatted and were failing the Code linting CI job. No logic changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
adept/_hermite_poisson_1d/— a native 2D(Nn, Nx)Hermite-Poisson solver that avoids the 6D Spectrax tensor overhead for strictly 1D problemsBaseHermitePoisson1D(ADEPTModule)base class with Lawson-RK4 integration, WaveSolver for the vector potential, PoissonSolver1D for electrostatics, and sponge boundary supportFreeStreamingExp1D) directly on 2D(Nn, Nx)arrays instead of the full 6D spectrax machineryadept.hermite_poisson_1d.BaseHermitePoisson1Das a clean public APIMotivation
The existing Spectrax-1D path runs with 6D tensors
(Np=1, Nm=1, Nn, Ny=1, Nx, Nz=1)even for degenerate 1D problems. This new module eliminates the degenerate-axis overhead, which is significant at largeNx(e.g. 20k spatial cells × 64+ Hermite modes).Test plan
uv run python -c "from adept.hermite_poisson_1d import BaseHermitePoisson1D"passeskinetic_srs/hermite_poisson.py) subclassesBaseHermitePoisson1Dand imports cleanly🤖 Generated with Claude Code