Repository navigation
Conversation
…h reltol integrate_ballooning_ode set abstol=reltol^2 (1e-16, at machine epsilon) against a non-stiff DP5 solver, forcing needlessly tight step control. Profiling (warmed, no JIT noise) showed ballooning boundary search as the dominant real cost on gal-resistive-PE-style cases -- ~55% of pipeline runtime, driven by many repeated ODE shoots per flux surface (linear pre-scan + bisection to locate the marginal-stability crossing). Setting abstol=reltol=1e-8 (the standard pairing) gives a measured ~8% speedup on Force-Free States for DIIID-like_gal_resistive_pe_example, confirming reltol was already the binding constraint for most of the integration. Validated bit-for-bit identical locstab/alpha_critical (the value that actually drives stability classification) before/after; the raw ballooning_Delta_prime diagnostic drifts ~1e-5 relative, which flips a few profile checksums in the regression harness but is far below any threshold that matters physically. Full 11-case regression suite against merge-base a0cad26 is otherwise clean.
|
@adrianaghiozzi I assume this is ready to review? If not, please convert to draft |
Pulls in 171 develop commits, including the ForceFreeStates -> standalone LocalStability module extraction (Ballooning.jl moved to src/LocalStability/Ballooning.jl). Clean merge, no conflicts; Git's rename detection correctly carried this branch's ballooning abstol fix (69ad76e) over to the new path. Regression harness (develop @ 9491f89 vs merged local, 13 cases, full suite): clean across the board. 12/13 cases fully unchanged (energies, ODE step counts, equilibrium/stability quantities, all profile checksums identical). The only diff anywhere is the "ballooning Delta' profile" checksum in diiid_n1 (1 changed / 46 unchanged) -- the expected, already-validated fingerprint of this branch's own abstol=reltol fix (~1e-5 relative drift in the raw diagnostic; locstab/alpha_critical, the value that actually drives stability classification, is bit-for-bit identical). No numeric regressions introduced by the merge itself.
There was a problem hiding this comment.
Looks great! Only one minor comment is about comments lol, then its ready to merge
Also - I see your notes about benchmarking the quadgk stuff - can you also add some comment about what kind of speedup the vacuum kernel optimizations obtained just for posterity?
|
|
||
| ρ2 = x_minus2 + ζ2 | ||
|
|
||
| # Distance parameter ℛ [Chance Phys. Plasmas 1997 2161 eq. 41] |
There was a problem hiding this comment.
Claude is rather inconsistent with its comments - sometimes, it will add large, multi-line comments that saturate the repo, and other times it will remove helpful ones
Can you add back in some of these comments? For sections that you optimized and made individual terms out of date, you can just ask Claude to go through the pdf and revise whatever is necessary. This is just helpful for linking the math here directly to Chance's derivation
|
@jhalpern30 Adding for posterity this local test of the Vacuum/Kernel2D green function showing the speedup I observed for a test before and after the changes implemented here: |
|
@logan-nc What are your thoughts on the 0-finding? You are the one who knows those physics the best |
|
FYI reviewing and thinking about this is on my todo list but it's just a long list, so please be patient |
ebursch
left a comment
There was a problem hiding this comment.
This also looks good to me! Per Pharr's comment, I also defer to Nik on the 0 finding.
|
Reviewed this while scoping the follow-up performance work it recommends. Three of the four changes look good; one needs a decision before merge. Please justify or drop the Ballooning tolerance change
reltol=TOLERANCE,
- abstol=TOLERANCE^2,
+ abstol=TOLERANCE,With If it was deliberate and speeds up the ballooning solve, that's worth having — but it needs a regression-harness pass showing what moves, and the PR should be tagged The other three changes look correct and worth keeping as-is:
CI blockersTwo conventions checks will fail as-is:
On the profiling suggestions in the descriptionThanks for writing those up — they're being acted on. Two notes for whoever picks that thread up:
🤖 Generated with Claude Code |
|
@logan-nc Me and Claude updated the title and description of this PR to clearly show the regression harness was run and to justify the tolerance changes. Should be ready to go now! |
|
@jmlmir369 please be aware of the changed tolerance behavior. I cannot think of any reason the |
|
It's also worth pinging @nelsonand here → TLDR is that the ballooning calcs are much faster now. |
|
Thanks for the update. I reviewed the change, and it |

Release note
ballooning Delta' profilechecksum indiiid_n1moves ~1e-5 relative (harness @11666e33e, develop @cbe890c0c). All stability-classification-adjacent quantities (Mercier D_I, resistive interchange D_R, singular surfaces,gal α per surface) are bit-for-bit identical.Loosens the
abstolon the ballooning marginal-stability ODE solve from machine epsilon (1e-16) to matchreltol(1e-8), removing needless step-size control on a non-stiff DP5 solver (~8% speedup on Force-Free/LocalStability-heavy cases). Also fixes a real type instability in the pitch-angle quadrature (PitchIntegration.jl), adds an explicit equilibrium type annotation in twoKineticForces/Torque.jlcall sites, and refactors a common subexpression inKernel2D.jl's Green's function.Regression report
Full 13-case suite,
develop@cbe890c0cvslocal(this branch, merged with develop) @11666e33e. One expected diff, no regressions:No quantity literally named
alpha_criticalis tracked; the closest proxies (gal α per surface, Mercier D_I / resistive D_R checksums, singular-surface locations) are bit-for-bit identical throughout, consistent with the two prior validations of this same change.Notes for reviewers
On the ballooning abstol change (flagged in review): this line was carried onto this branch by an earlier
developmerge, not introduced fresh here — it originates from commit69ad76e4f(FORCEFREESTATES - OPTIMIZATION - Loosen ballooning ODE abstol to match reltol), which ran an 11-case regression harness against merge-basea0cad260and validatedlocstab/alpha_critical-adjacent quantities bit-for-bit identical, with only the rawballooning_Delta_primediagnostic drifting ~1e-5. It was re-validated again after theLocalStabilitymodule extraction ind266e7976, and now a third time above after merging in 155 moredevelopcommits (including the KineticForces tolerance fixes in #390/#391/#392) — no new diffs from any of that.gal_resistive_pecoverage gap (unrelated to this PR): this case reports N/A for all 8 tracked quantities (rational-q values, island half-widths, Chirikov parameter, resonant/penetrated b-field, PE Δ' per surface) on bothdevelopandlocal. Since it's symmetric it isn't a regression from this branch, but it means the case is currently providing zero regression coverage — worth a separate ticket to find out why the DRIVEN/RPEC coil-driven singular-coupling output isn't populating (possibly an HDF5 group rename or extraction-path mismatch between the harness case TOML and current output schema).On the profiling suggestions in the original PR description: those findings (raw content below) predate #390/#391/#392, and #392 in particular changed the energy-integration tolerances inside the same
tpsi!chain the ~84% figure was measured against, so that share needs re-measuring before anything gets scoped off it. Thefind_zeros→find_sign_change_rootsswap is not a safe drop-in substitution for the reason already given below (find_sign_change_rootsonly catches sign changes between adjacent grid nodes); a follow-up would instead enumerate the exact stationary points of the B spline once per surface and do bracketed Brent solves only on the monotone intervals that bracketbo/λ— same completeness guarantee as the adaptive scan, without the per-λ whole-interval walk. That's out of scope for this PR and should be its own branch offdevelop.Original profiling report (superseded context above)
This PR introduces some modest performance improvements by explicitly typing a function argument that was being inferred in Torque.jl, refactoring the green function in Kernel2D and fixing a type instability in the integrate_pitch_gar_quadgk/integrate_pitch_gar_quadgk_wt functions.
The source of much of the compute time for several of the examples goes back to the quadgk step, and the tpsi! function in Torque.jl. Claude has some suggestions about how this tpsi! function could be improved, but it included some caveats that seem worthy of human-review/discussion before implementing and therefore are not pushed onto this branch. Claude's ideas below:
Profiling results — diiid_n1 full pipeline
Ran Julia's statistical profiler across the entire main() call (equilibrium → stability → PE → KineticForces → ballooning). Headline finding:
tpsi! (KineticForces/Torque.jl) and its call chain account for ~84% of all profiled samples — despite KineticForces looking like a modest post-processing stage in the wall-clock logs (~55s of ~133s), it dominates actual CPU-thread time by a wide margin once you account for the equilibrium/stability stage being comparatively cheap per-thread.
#1 hotspot: Roots.find_zeros in bounce-point finding — ~26% of total pipeline time
src/KineticForces/BounceAveraging.jl:434, inside _find_bounce_points_and_grid:
This is a generic adaptive full-interval root scanner, called once per trapped-particle λ point — per ψ, per bounce harmonic, per species — so it fires an enormous number of times. It alone accounts for ~9,400 of the ~36,300 total samples.
The interesting part: tpsi! (the caller's caller) already computes theta_bmin/theta_bmax a few lines earlier using a lighter-weight utility that already exists in the codebase — find_sign_change_roots(f, grid) in src/KineticForces/Utils.jl:19, explicitly documented as "Single source of truth for the scan-then-Brent idiom" and already used elsewhere in the same file. _find_bounce_points_and_grid doesn't use it, reaching for the heavier Roots.find_zeros instead.
Why I didn't just swap it: find_zeros's adaptive scan is more robust against closely-spaced multiple roots (the code already handles nbpts > 2 for multi-well B(θ) profiles in _find_deepest_well), whereas find_sign_change_roots only catches sign changes between adjacent grid nodes — too coarse a grid could silently miss a well. This is a physics-sensitive change (bounce-point location feeds the NTV torque/kinetic-energy integrals directly), so it needs a grid-resolution choice validated against find_zeros' output before trusting it, plus a regression-harness pass afterward.
Smaller, secondary costs (for context, not obviously worth chasing)
Set an assignee and at least one human reviewer. Per this project's policy, this PR requires third-party human reviewer approval before merge — the clean regression run above does not substitute for review.