Skip to content

KineticForces - FEATURE - Per-bounce-harmonic torque density output - #484

Open
logan-nc wants to merge 1 commit into
developfrom
feature/kf-per-harmonic-torque
Open

logan-nc wants to merge 1 commit into
developfrom
feature/kf-per-harmonic-torque

Conversation

@logan-nc

@logan-nc logan-nc commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

Why

The torque density is already computed harmonic by harmonic — the ℓ loop in integrate_psi_quadgk
fills harm_vals and then reduces it — but only the sum was ever kept.

Multi-code NTV benchmarking wants the breakdown. The ITPA TC-24 activity is comparing the
contributions from ℓ = 0, ±1 … ±5 across GPEC, NTVTOK and MARS-K, and the Fortran parent already
emits dTdpsi_fgar(psi, ell), so the Julia code could not answer that request at all.

What

KineticForces/<method>/ gains dTdpsi_ell on a new ell dimension scale. Shape is (npsi, nell)
in Julia and (nell, npsi) to a C-ordered reader, matching the Fortran layout. Summing over ℓ
reproduces dTdpsi. Per-species groups get it with no extra code.

Three things that needed care

  • The per-ℓ log is permuted by the same sortperm as psi. QuadGK visits nodes in refinement
    order, not in ψ order, so an unsorted log would misalign with the grid silently rather than
    erroring.
  • harm_vals is reused at every ψ node, so the log stores a copy.
  • combine_species_states interpolates onto a union grid, and multi-ion runs need per-ℓ on the
    summed total as well as per species. The interpolation is now a shared helper used by both the
    total and each ℓ column, and the combined dtdpsi is taken as the row sum of the per-ℓ matrix —
    which makes the sum rule exact by construction rather than true to interpolation roundoff. A
    species without per-ℓ data leaves the combined matrix empty instead of erroring.

Caveats, documented in the annotation

Quadrature adapts on the summed integrand, so a sharply resonant individual harmonic can be less
well resolved than the total. Profiles still come from the first toroidal mode only, so for
npert > 1 the sum over ℓ matches dtdpsi but neither integrates to total_torque.

Testing

runtests_multiion.jl 64/64 and runtests_kinetic.jl 1845/1845 pass. New assertions cover the sum
rule through combine_species_states, the on-disk shape and orientation, the ℓ-sum on disk, the
dimension-scale attachment, and the mixed case where one species lacks per-ℓ data.

The on-disk orientation was checked from both sides: HDF5.jl reads (npsi, nell) and h5py reads
(nell, npsi), with values indexed as intended.

The torque density is already computed harmonic by harmonic -- the l loop in
integrate_psi_quadgk fills harm_vals and then reduces it -- but only the sum
was ever kept. Multi-code NTV benchmarking wants the breakdown: the ITPA TC-24
activity compares contributions from l = 0, +/-1 ... +/-5 across codes, and
the Fortran parent already emits dTdpsi_fgar(psi, ell), so Julia could not
answer that request at all.

KineticForces/<method>/ gains dTdpsi_ell on a new ell dimension scale, shape
(npsi, nell) in Julia and (nell, npsi) to a C-ordered reader. Summing over l
reproduces dTdpsi.

Three things needed care:

- The per-l log is permuted by the same sortperm as psi. QuadGK visits nodes
  in refinement order, not in psi order, so an unsorted log would misalign
  with the grid silently rather than erroring.
- harm_vals is reused at every psi node, so the log stores a copy.
- combine_species_states interpolates onto a union grid, and the benchmark
  runs multi-species, so the summed total needs per-l as well as the
  per-species groups. The interpolation is now a shared helper used by both
  the total and each l column, and the combined dtdpsi is taken as the row
  sum of the per-l matrix, which makes the sum rule exact by construction
  rather than true to interpolation roundoff. A species without per-l data
  leaves the combined matrix empty instead of erroring.

Quadrature adapts on the summed integrand, so a sharply resonant individual
harmonic can be less well resolved than the total; the annotation says so.
Profiles still come from the first toroidal mode only, so for npert > 1 the
sum over l matches dtdpsi but neither integrates to total_torque.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FabuzB6rkkv638ZUCzaAA2
@github-actions github-actions Bot added the feature New capability label Oct 1, 2026
@logan-nc logan-nc added this to the GPEC v2.0.0 milestone Oct 6, 2026
@logan-nc
logan-nc requested a review from matt-pharr October 6, 2026 14:02
@logan-nc logan-nc self-assigned this Oct 6, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant