Repository navigation
Use SOLVAX Newton-Krylov with a Hermite line preconditioner in implicit midpoint - #21
rogeriojorge wants to merge 29 commits into
Conversation
Codecov Report❌ Patch coverage is
🚀 New features to boost your workflow:
|
|
Production validation completed on the exact SPECTRAX/SOLVAX heads combined with species sharding: |
84ccf22 to
5de0421
Compare
vth_s (default alpha/sqrt(2), the previous behaviour) gives the closed form c^n H_n(d/c) / sqrt(2^n n!) with c^2 = 1 - 2 vth^2 / alpha^2, computed by a three-term recurrence (no factorials). A thin beam can then share a wide basis, where its expansion stays convergent as it spreads.
For odd Nx the last RFFT coefficient has an omitted conjugate and needs weight 2. hermite_tail_fraction reports the perturbation norm fraction in the outer Hermite modes, using the same Parseval weights.
…ling An under-resolved run (too few Hermite modes or too weak hypercollisions for the filamentation reaching the cutoff, or a species whose Hermite width is narrower than it becomes) makes the adaptive step collapse; the solve then crawls for hours towards the hard-coded 1e6 steps. dtmin (off by default) stops it with an error at the collapse; max_steps is now a parameter.
The logo was a 4.7 MB 1576x1476 screenshot with embedded metadata; the README shows it at 30% width. It is now 800 px wide with a 256-colour palette and no metadata (193 KB). Tracked __pycache__ files are removed; __pycache__/ is already ignored.
spectrax imports diffrax (time integration) and orthax (inverse Hermite transform), but pyproject.toml did not list them, so pip install left the package unimportable in a clean environment.
simulation(throw=False) returns and puts diffrax's sol.result in output['solver_result'].
5de0421 to
567e0c4
Compare
The unrolled loop compiled in 21 s at Nn=256; the scan compiles in well under a second. Adds a finiteness test at Nn=256.
|
Rebased as a stack so the PRs merge in sequence without conflicts: merge (with a merge commit) #48, #46, #50, #51, #9, #12, #13, #34, #52, #53, then #18, #21, #44. Until the earlier ones land this diff also shows their commits. The conflicts with #13/#50 (same |
|
Where implicit midpoint beats Dopri8 at matched accuracy: when Dopri8's step is stability-limited (its step count does not change with tolerance) and the physics is slow.
Dopri8 still wins on 1D Landau, two-stream and 2D Landau at shipped-like parameters, so it stays the default. Full table in the description. |
Depends on #13 (structured tuple state) and #18. Their commits are included here until they merge; merge #13, then #18, then this.
Problem
Unpreconditioned Newton-GMRES in
ImplicitMidpointneeds thousands of Krylov iterations once the timestep exceeds the Hermite streaming/collision time scales, and gives no iteration diagnostics.Change
solvax.newton_krylov(PyPI; deps onlyjax+equinox, already pulled by diffrax;solvax>=0.8.0added topyproject.toml/requirements.txt, tested with 0.8.0 and 0.26.0). New optionslinear_rtol,linear_atol,linear_restart,linear_max_restarts,preconditioner.collision_diffusion_x_streaming_preconditioner(args, dt): exact inverse of the collision + diffusion + x-streaming Hermite tridiagonal lines (jax.lax.linalg.tridiagonal_solve).simulation()returnsmidpoint_stats(total/max Newton and GMRES iterations) when the solver isImplicitMidpoint.solverstaysDopri8(see below).Effect
1D Landau, Nx=33, Nn=128, nu=20, t=10, x64, same machine, interleaved runs (min of 3). Error = max |Fk - Fk_ref| / max |Fk_ref| against Dopri8 at tol 1e-11.
jax.scipyGMRES (incremental), no preconditionerjax.scipyGMRES, line preconditionerjax.scipyGMRES, line preconditionerjax.scipy.sparse.linalg.gmresat the same preconditioner and iteration counts, and it reports GMRES iteration counts.Where implicit midpoint wins
Dopri8's step is set by stability (step count does not change between tol 1e-4 and 1e-8), so implicit midpoint wins when the dynamics of interest are slow compared with the collision or streaming rates. Runs on 6 pinned cores (
taskset), min of 2 runs, compile time excluded (implicit ~9 s, Dopri8 <3 s). Error is max over saved times of |Fk - Fk_ref| / max |Fk_ref|; the reference is Dopri8 at tol 1e-12. Implicit uses the line preconditioner withrtol=1e-8, linear_rtol=1e-6.while_loop); use Dopri8 (gradient matches finite differences to 8e-7).Tests
test_implicit_midpoint_simulation_reports_iterations(preconditioned run, stats present) andtest_collision_diffusion_x_streaming_line(matches a dense solve). Full suite passes with and withoutJAX_ENABLE_X64=1.Review 2026-10-03 (branch not changed; head
9c066cc)Scope. The only #21-specific commit is
567e0c4. The rest of the 28-commit head is accumulated #9/#12/#13/#18/#34/#46/#48/#50/#51 history.Restacking needed. #18 was rewritten (now
806c2e4) with an outer-residual and finiteness fix. This branch must be rebuilt onto the new #18 (and #13) before review of the diff itself.Failure handling.
solvax.newton_krylovstops on a nonfinite residual or threshold withconverged=False, and decides on the residual of the current iterate. So a NaN is not reported as success here. Two gaps remain:y_erroris not set to inf on failure, so an adaptive controller may accept a failed step's estimate before the failure ends the solve. Corrected Fix implicit midpoint time, convergence reporting and pytree states #18 sets it to inf.‖F‖ ≤ max(atol, rtol‖F(y_Euler)‖)is unscaled and relative to the predictor's residual. That is a different contract from the scaled per-component test in Fix implicit midpoint time, convergence reporting and pytree states #18.Coupling. The preconditioner reads parameters by negative positions in
args(args[-20:-18], …). That is fragile against any change of theargstuple.AD. No reverse-mode AD: the Newton loop is an early-exit
while_loop. SOLVAX'sfixed_work=Truescan path is not used. Forward mode was not tested.Matched-error profile
input_1D_landau_damping.tomlwith Nx=3, Nn=64, ν=2000, t=20), consistent initial field.rtol=1e-8.linear_rtol=1e-6with the line preconditioner.atol=1e-10.Recommendation
Hold for the owner's numerical and performance review. Restack it on the corrected #18, then:
y_error = infon failure;argsreads with named parameters;