InnerLayer (GGJ) - NEW - rotated-ray collocation backend (:ray), robust to |Q| ~ 500#323
InnerLayer (GGJ) - NEW - rotated-ray collocation backend (:ray), robust to |Q| ~ 500#323matt-pharr wants to merge 7 commits into
Conversation
…location method implementation of GGJ that avoids Delta poles on the imaginary Q axis and can solve for Delta_even adn Delta_odd up to very high (at least ~500) |Q|
|
@logan-nc @d-burg this is something I have been working on with claude for a few weeks. It has a number of advantages over the galerkin GGJ solver. It uses collocation which is also a finite element method, but I learned that the reason that the galerkin solver sometimes freaks out is because there are poles to the GGJ solutions in Q space, and when you get near those poles the solutions become super oscillatory on the real axis. However, since we are solving this in a complex domain, you can show that the GGJ solutions should be entire in the complex plane, so the mercier terms in the large |X| limit are the same polynomial + coefficient even off of the real-axis (which is where the physically-relevant part of the solution lies). So you can get a much cleaner/quicker exponential decay of the non-limiting terms if you integrate on a ray that is rotated off of the real axis. This also gives way to an entirely different numerical stability check, which is to re-integrate for multiple rotation angles and see that you get the same Delta_even and Delta_odd. Overall this method is far superior to the galerkin one in all of my testing so far, and can safely go up to Q ~ 500i with a maximum error in the final Delta of ~1e-4 which is pretty good for this problem. Error stays ~1e-7 for more reasonable Q. This enables scanning to lower resistivities (Q ~ 1/eta^1/3) and higher rotation rates (or to absurd growth rates). It also makes the solve more robust for lower |Q|. I will do some more testing on this branch before I merge but I am opening it for review now. |
…ayer :ray page with provenance-tracked figures Establishes a repeatable "Journal of Computational Physics"-style documentation standard and applies it to the new GGJ rotated-ray (:ray) inner-layer backend. Standard and figure infrastructure: - docs/DOC_STANDARD.md: module-page template (equations -> numerical method -> validation figures -> API), figure-organization principle, provenance policy, and the regenerate-only-when-`depends`-change rule. - docs/figure_tools.jl: save_doc_figure stamps each PNG with the git commit/date, writes a machine-readable manifest.toml with per-figure `depends`, and centralizes the step_series spectrum helper. - Content figures now live in docs/src/figures/<module>/ (script + PNG together); docs/src/assets/ is reserved for Documenter chrome. .gitignore updated. InnerLayer page (docs/src/inner_layer.md, stub -> full page) with four computed, provenance-stamped figures: backend accuracy vs :ray along the imaginary axis, the rotated-ray/pseudo-resonance geometry, the seamless collocation<->asymptotic field match, and the Q=500i convergence error bar. Ballooning figures migrated to docs/src/figures/ballooning/ as the second exemplar and registered legacy (generators predate the provenance system). Hardening and review fixes: - regression-harness case ggj_ray_q500i (+ runner template) pinning the :ray matching data at Q=500i on the q=4 benchmark surface. - test/runtests_innerlayer.jl: internal-machinery unit tests (cheblobatto, ode_matrix, parity_rows, decaying_pair, profile diagnostics, delta_convergence). - GGJParameters.jl: corrected the rescale_delta docstring exponent (X0^(-2*sqrt(-D_I)), was mislabeled with the reciprocal sign). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documentation for the
|
|
@matt-pharr I was having trouble understanding/trusting all your claims from the pure text descriptions above. As such, I am taking this opportunity to initiate a new, more-complete docs standard that includes figures. Figures were already creeping in (see legacy ballooning figures) and a standard was needed anyways - I figured I might as well lump it in here so i could actually see what was happening 😝 |
|
@matt-pharr can you check the "Review-related fixes folded in". |
…eld from the inner layer. Calculates penetrated field from a B_pen object that gets passed in from forcefreestates. If resistive, this comes from the inner layer model. If ideal, this is set to exactly 0. This is carried through in PerturbedEquilibrium and used for all B_pen outputs.
…_mn for gal -> PE
…ive matching default
Summary
Adds a third interchangeable solver to the GGJ inner-layer model —
GGJModel{:ray}— and makes it the default. It returns the same parity-projected matching data(Δ₁, Δ₂)in the same deltac convention as:galerkinand:shooting, but remains accurate far into the regime where both existing backends fail: validated to Q = 500i, the territory resistivity scans (|Q| ∝ η^(−1/3)) and rotation physics actually visit.Method (one paragraph)
The layer equations are continued analytically onto the complex ray x = e^{iθ}s with θ = arg(Q)/4, which makes the parabolic-cylinder WKB exponent exactly real (no oscillation along the contour) and moves the coefficient pseudo-resonance x² ≈ −Q²(G+KF) — which sits on the real axis for imaginary Q — off the path. Δ is an analytic invariant of the contour angle within the Stokes sector, which doubles as a built-in error diagnostic (outward θ-consistency check). On [0, s_m] a global Chebyshev spectral-element collocation BVP is solved in plain variables with parity conditions at the origin and (Δ, c₁, c₂) as bordered unknowns of one sparse LU per parity (rank-3 Woodbury correction for the second parity). The far-field condition reuses the existing, validated inps Wasow kernel (
InnerAsymptotics.jl) — evaluated at complex x via new methods inRayAsymptotics.jl, construction untouched — applied at the series radius S and transported inward by an L-stable 2-stage Radau IIA march in the quotient modulo the decaying exponential pair. The damped-zone march arithmetic runs inComplex{Double64}: at large S the near-parallel power-pair geometry amplifies the structured backward error of the ill-conditioned implicit solves into Δ-mixing ~1e-4 at |Q| = 500 in Float64 (a floor generic to any inps-based method handed to double-precision linear algebra at large S).Validation
Developed and validated in a standalone package before porting; highlights:
:galerkincross-check at moderate Q: 1e-5–1e-9.test/runtests_innerlayer.jlgains a:raytestset (agreement with:galerkinat the GW2020 Eq. 55 paper point; q4 pins at 500i; θ-invariance). 19/19 pass.Default backend change
GGJModel()now defaults to:ray. Blast radius checked: there are no bareGGJModel()call sites in the repo — all 17 benchmark uses and the regression harness passsolver=:galerkinexplicitly. The backends take different numerical-knob keywords, so any stale galerkin-knob call fails loudly rather than returning a silently wrong Δ.Dependencies
Random(stdlib, zero footprint) promoted from test-extras to[deps]— seeds the decaying-pair extraction (provably Δ-neutral; kept fixed for bit-reproducibility).DoubleFloatswas already a project dependency; only theusingline is new.Files
src/InnerLayer/GGJ/RayAsymptotics.jl(new) — complex-x evaluation methods for the existing inps kernel.src/InnerLayer/GGJ/Ray.jl(new) — ODE system, WKB-graded mesh, decaying-pair extraction, Radau march, bordered collocation solve, refinement,delta_convergenceerror-bar battery, profile reconstruction, and thesolve_inner(::GGJModel{:ray}, …)interface method.GGJ.jl/InnerLayer.jl— backend registration, exports, docs.A complete replication-grade methods write-up (strong form, cell-by-cell matrix assembly, march schedule derivation, finite-precision floor analysis) exists and will accompany the paper in preparation.
Update (2026-07-10): RPEC matching + PerturbedEquilibrium integration
The branch has grown beyond the standalone backend. Additions, each an independently validated commit:
Documentation (with a correctness pass): JCP-style doc standard, full InnerLayer page with four provenance-tracked figures, a
ggj_ray_q500iregression case, and fixes from review — pole-locus physics (Δ's poles lie on/near the real-Q axis; the imaginary-axis difficulty is the on-path pseudo-resonance), per-backend Eq.-55 validation values, platform-aware regression thresholds (the damped-zone structured roundoff is BLAS-dependent at ~1e-5 relative).Penetrated field from the inner layer (bug fix + new): the physical penetrated (reconnected) resonant field is the layer-center
Ψ(0)— the outer large solution ~|δψ|^(1/2−p₁) makes any pointwise evaluation bracket-offset dependent. The ported match.f-style scale omitted a −2π/v₁ factor (surface-dependent error, 3.5–7× on the DIII-D benchmark); the corrected scale is derived from the code's own outer b-convention chained with GWP2016 Eqs. 16/A17 and validated by an overlap test (the matched inner b^ψ profile lies on the outer eigenfunction through the matching region, magnitude and phase, all four DIII-D surfaces). The layer value is now the officialC_penetrated_area_weighted_fieldvia the standard C_coeffs contraction; ideal runs (either integrator) produce exactly zero (perfect shielding); kinetic shooting remains on the pointwise fallback.Coefficient-based Δ_mn: computed from the matched solution's asymptotic coefficients (per-surface unit-small-solution kernels at the conventional ±5e-4/(n|q′|) brackets × the small-solution content bookkeeping) instead of finite-differencing the outer solution across its near-surface divergence. Validates against the finite-jump evaluation to ~1e-5 on the ideal gal path. Dispatch mirrors B_pen: a ForceFreeStates-provided object replaces the jump; shooting runs keep the fallback (now with analytic gal derivatives where available) until the planned non-galerkin ideal Δ object lands.
Ray as the RPEC matching default: new
gal_inner_solvercontrol ("ray" default | "galerkin"), consistent withGGJModel()defaulting to:ray. Certified Δ from the optimal-θ contour; profile quantities from a θ=0 re-solve (the on-axis pseudo-resonance is a regular point, resolved by the BVP refinement — tested clean to |Q| = 100 imaginary); the θ=0-vs-θopt Δ agreement is logged as a runtime certificate. b_pen agrees with the galerkin backend to 1.8e-4 → 6e-6 across the DIII-D surfaces with identical normalization. No settings are read from environment variables anywhere in the project.Output additions:
galerkin/match/inner_params/{E,F,G,H,K,M,taua,taur,v1}(the per-surface layer coefficients, S = taur/taua),galerkin/match/{delta_mn, inner/b_$i}, andsingular_coupling/{penetrated_field_inner, C_penetrated_field_inner, forcing_solution_weights, rational_area}.Physics motivation for the matching work: on a DIII-D k_η scan the legacy galerkin inner layer produces a non-monotonic-in-η jump of the matched resonant field at the lowest resistivity (|Q| ≳ 1); the ray backend restores a smooth, monotonic shielding family — the inner-layer solver choice materially changes a physical prediction exactly where |Q| grows beyond the legacy regime.
🤖 Generated with Claude Code