Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
76370a6
KineticForces - BUG FIX - Coulomb logarithm uses natural log, not log10
logan-nc Jul 29, 2026
ee5d211
KineticForces - NEW FEATURE - ion_fraction for multi-main-ion NTV (fg…
logan-nc Jul 29, 2026
a3760fc
KineticForces - NEW FEATURE - multi_ion_composition (Zeff/zpitch for …
logan-nc Jul 29, 2026
09f7d9c
KineticForces - NEW FEATURE - IonSpecies list + per-species profile b…
logan-nc Jul 29, 2026
23c82d6
KineticForces - NEW FEATURE - internal multi-species NTV loop + summe…
logan-nc Jul 29, 2026
de8e266
KineticForces - BUG FIX - accept Vector{Any} for ion_species TOML con…
logan-nc Jul 29, 2026
2b1be4c
KineticForces - NEW FEATURE - unified multi-species sum (KF quadratur…
logan-nc Jul 29, 2026
2fe72e4
ForceFreeStates - BUG FIX - Pass direction arg to IntegrationChunk in…
logan-nc Jul 29, 2026
ee20627
KineticForces - NEW FEATURE - explicit per-species density profiles (…
logan-nc Jul 29, 2026
976a362
KineticForces - TEST - multi-ion NTV unit tests (25)
logan-nc Jul 29, 2026
34d4895
KineticForces - TEST - multi-ion NTV regression case (solovev_kinetic…
logan-nc Jul 29, 2026
bc1c395
KineticForces - CLEANUP - annotate zpitch domain + electron view dens…
logan-nc Jul 29, 2026
851b4d2
KineticForces - BUG FIX - combine_species_states summed profile was a…
logan-nc Jul 29, 2026
2edbb12
KineticForces - CLEANUP - concise Coulomb-log annotation (CLAUDE.md c…
logan-nc Jul 29, 2026
66cc9de
ForceFreeStates - CLEANUP - pass real ising to placeholder Integratio…
logan-nc Jul 30, 2026
5f9f40e
KineticForces - IMPROVEMENT - Address multi-ion NTV review: test-part…
logan-nc Jul 31, 2026
b864d38
AGENTS - MEMORY - Record physics-reviewer findings from multi-ion NTV…
logan-nc Jul 31, 2026
9d9349b
Merge develop into feature/multi-ion-ntv
krystophny Aug 6, 2026
61912a0
Merge remote-tracking branch 'origin/develop' into feature/multi-ion-ntv
logan-nc Aug 14, 2026
4012325
KineticForces - IMPROVEMENT - Multi-ion input guards: fraction sums, …
logan-nc Aug 14, 2026
5dd1e78
KineticForces - BUG FIX - write_kinetic_h5 round-trips species densit…
logan-nc Aug 14, 2026
28f189a
KineticForces - IMPROVEMENT - electron=true always adds electrons in …
logan-nc Aug 14, 2026
63c6235
KineticForces - CLEANUP - Multi-ion docs: wire docstrings to behavior…
logan-nc Aug 14, 2026
9fe8a60
KineticForces - IMPROVEMENT - :static kinetic-matrix threading + type…
logan-nc Aug 14, 2026
e305099
KineticForces - TEST - solovev_kinetic_calculated exercises multi-ion…
logan-nc Aug 14, 2026
42ca6aa
AGENTS - MEMORY - Correct zpitch·n_main vs n_e·Zeff numeric claim in …
logan-nc Aug 14, 2026
4f99d25
KineticForces - TEST - note D-T multi-ion deck in solovev_kinetic_nuz…
logan-nc Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/agent-memory/fortran-physics-reviewer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
- [KineticForces (NTV) Audit Checklist](kinetic_ntv_map.md) — pentrc->KineticForces map, Logan 2015 matrices, what to verify
- [InnerLayer (Resistive) Audit Checklist](resistive_layer_map.md) — rmatch->InnerLayer map, GGJ Wasow basis / Δ′, what to verify
- [Galerkin Δ′ Assembly Map](galerkin_assembly_map.md) — gal.f<->GalerkinAssembly.jl; resonant sign chain verified; PASS
- [reg_spot vs singfac_min audit](reg_spot_regularization.md) — reg_spot field-recon smoothing CORRECT; distinct from singfac_min ODE gate
57 changes: 57 additions & 0 deletions .claude/agent-memory/fortran-physics-reviewer/kinetic_ntv_map.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,60 @@ the Fortran `pentrc/` sources (file mapping in fortran_correspondence_map.md).
- **Normalization**: torque Im(T)=2n·δW_k (Eq. 19) and diamagnetic-frequency sign/factor conventions match Fortran `torque.F90`.
- **Mode indexing**: m, n ranges and block packing over n stay consistent with ForceFreeStates.
- **Method variants**: FGAR/TGAR/PGAR/RLAR/CLAR/FCGL/TMM/WMM each present, not stubbed to a single fallback.

## Multi-ion (D-T) NTV — composition/collisionality (2026-07)
Both Fortran PENTRC (`inputs.f90:236-243`, `read_kin`) and Julia (`KineticProfiles.jl:261-278`)
support ONE main ion (zi,mi) + ONE impurity (zimp,mimp) per run. Correct multi-main-ion NTV is
an EXTENSION beyond Fortran, but consistent with Logan-Park 2013's pitch-angle (Lorentz) model.
- Zeff = Σ_s Z_s² n_s / n_e; quasineutrality n_e = Σ_s Z_s n_s (all ions incl. impurity).
- Bug in current D-T split (run D, then T, each with ni=Ni/2): the `ni` column sets BOTH species
density AND Zeff via z = zimp-(n_i/n_e)zi(zimp-zi). With ni=Ni/2 it treats the missing half as
high-Z impurity → Zeff≈3.7 instead of true ≈1–1.4. Corrupts zpitch → nue,nui.
- Correct collisionality: ν_a ∝ Z_a² lnΛ · Σ_b n_b Z_b² /(√m_a T_a^{3/2}) = ∝ Z_a² lnΛ·(n_e·Zeff_true).
The zpitch(Zeff) polynomial is a main-ion+impurity closure (momentum-restoring correction); the
MINIMAL fix is to feed the TRUE (full-composition) Zeff into the existing zpitch/ν formulas.
- Additivity: τ = Σ_s τ_s. Lorentz operator is additive over field species; species couple only
through shared δB, shared ω_E, shared Zeff/ν. Additive at this theory's order.
- What changes single→multi: Zeff, zpitch, nue, nui (→nueff). UNCHANGED for equal-shape D-T:
wdian/wdiat (log-derivative, density factor cancels), wtran/wbhat/wdhat/wgyro (already per-species),
and the resonant-density prefactor (Ni/2 per species is correct).
- ASIDE (separate fidelity bug, same code block): Fortran `inputs.f90:238` uses natural log for lnΛ;
Julia `KineticProfiles.jl:270` uses log10 — diverges away from the n=1e20,T=1keV reference point.

## Multi-ion NTV — full-composition species set (2026-07 audit, PASS-with-caveats)
Reviewed `resolve_ntv_species` (KineticProfiles.jl ~214-266) + `compute_calculated_kinetic_matrices`
(CalculatedKineticMatrices.jl ~96-151). Verdict: physics is sound.
- ν_s field-density RECONCILIATION (supersedes the earlier "should be n_e·Zeff" note above):
code uses `ν_s = (zpitch/3.5e17)·z_s²·n_main·lnΛ/(√m_s·T_i^1.5)`, i.e. field density = zpitch·n_main
(n_main = Σ MAIN-ion densities, no impurity, unweighted). This is CORRECT and MORE faithful to
single-ion PENTRC than n_e·Zeff: PENTRC's design is zpitch·n_i, NOT Σ_b n_b Z_b². The two are NOT
numerically equal (Zeff=1.5, C6, n_i/n_e=0.9 → zpitch·n_i = 1.225·n_e vs n_e·Zeff = 1.5·n_e, ~18%
apart); that gap IS the intended momentum-restoring design difference that zpitch(Zeff) carries.
n_main vs n_e·Zeff is a deliberate PENTRC-fidelity choice, exact in the z=1 main-ion domain.
- z_s² test-particle factor: CORRECT and correctly placed (deflection freq ∝ test charge²). Single-ion
had no z² only because zi=1. Reduces EXACTLY to single-ion nui for one z=1,fraction=1 ion (verified).
- Impurity as its own test species: field density zpitch·n_main is NOT undercounting — the impurity's
z_imp² contribution is already folded into Zeff inside zpitch. CAVEAT: zpitch is strictly a main-ion
momentum-restoring closure; reusing it for the impurity/electron ν is an approximation beyond the
single-ion theory. Acceptable (impurity δW ∝ n_imp is small); worth a one-line annotation.
- Electron descriptor passes `ns[1]` (first ion's density) as its `ni_spline` — HARMLESS: the kernel
`_setup_surface_state` (Torque.jl:659-664) reads `ne_spline` (full shared n_e) when electron=true,
never ni_spline. Electron n_s = full n_e (correct). Cosmetic smell only.
- Self-consistent δW summation (CalculatedKineticMatrices.jl:112-148): kw_flat/kt_flat are PURELY the
kinetic matrices (Logan 7.30-7.35), every term ∝ species phase-space density n_s·f0_s — NO species-
independent baseline. Fluid F,K,G added ONCE downstream in _compute_fkg_matrices!, outside the
species loop. So `+=` over species is clean additivity; NOTHING in kw is wrongly ×species-count.
→ TC-24 n=3 δW +0.066(D) → −0.10(D+T+C+e) sign flip is PLAUSIBLE physics near marginal stability,
NOT a double-count. Dominant driver: the newly-ON electron channel (full n_e, opposite precession).
DECISIVE cheap diagnostic to confirm: run D(½)+T(½) with electron=OFF, impurity absent — should
return ≈ +0.066 (single-ion D). If D+T alone ≈ +0.066, the whole shift is electron+impurity = physical.

## Single-ion nui vs multi-species z_s² (#339, 2026-07 decision)
Fortran PENTRC `inputs.f90:240-241` single-ion nui = (zpitch/3.5e17)·n_i·lnΛ/(√mi·T_i^1.5) has
NO explicit zi² (implicitly assumes zi=1, main ion hydrogenic). Julia `load_kinetic_profiles`
(~L416) is a faithful exact port. Multi-species `_nu` (~L253) adds explicit test-particle z_s²
(the physically correct pitch-angle/Lorentz form; zpitch is the field-side momentum-restoring
factor of Zeff, independent of test charge — no double count). RECOMMENDATION: option (a) ADD
zi² to single-ion nui. It is numerically identical for the default zi=1 (regression byte-identical),
makes single-ion the true 1-species special case of `_nu`, and only "deviates" from Fortran in the
exotic zi≠1 case where Fortran is physically wrong anyway. Annotate as a documented improvement.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: reg_spot vs singfac_min regularization audit
description: Verdict on GPEC-Julia reg_spot (field-reconstruction smoothing) port and its distinction from singfac_min (ODE crossing gate)
metadata:
type: project
---

## Verdict (audited FieldReconstruction.jl, no local Fortran repo available)
`reg_spot` port judged CORRECT-WITH-CAVEATS (caveat = Fortran verified from GPEC `xm*` convention + physics + known default, not from on-disk source).

## reg_spot — field-reconstruction smoothing (PerturbedEquilibrium)
- Factor form: `reg_factor = singfac²/(singfac²+reg_spot²)`, singfac = m - n·q. Correct GPEC form. → 0 at rational surface, → 1 away. Applied in singfac-space, NOT ψ-space.
- Default 5e-2 = 0.05 (PerturbedEquilibriumStructs.jl:50). Matches GPEC gpec_input namelist default.
- Quantities regularized (matches GPEC `xm*` modified-quantity convention):
- xmp1 = ξ^ψ' (FieldReconstruction.jl:393-395)
- xms (clebsch_alpha) computed FROM regularized xmp1 via xms=-A⁻¹(B·xmp1_reg+C·xsp) (line 398-415, gpeq_sol)
- xmt/xmz = regularized ξ^θ,ξ^ζ (line 584-594, gpeq_contra); xwt/xwz kept unregularized
- b^θ_reg,b^ζ_reg inherit reg via xmp1/xms (compute_modified_field_modes)
- LEFT UNREGULARIZED: ξ^ψ primitive (xsp / clebsch_psi = copy, line 356; xwp uses raw xsp). CORRECT — ξ^ψ (normal disp) is finite at rational surface; only 1/singfac-divergent tangential/derivative quantities are softened.

## singfac_min — DISTINCT mechanism (ForceFreeStates ODE crossing gate)
- Default 1e-4 (ForceFreeStatesStructs.jl:267), "Matches Fortran STRIDE". Gates singular-surface crossings during EL ODE integration (EulerLagrange.jl:466-519, GeneralizedPerturbedEquilibrium.jl:426).
- SingularCoupling.jl:204 uses `spot = 5e-4` as an offset (5e-4/|n·q1|) to evaluate bwp1 at lpsi/rpsi — labeled "matches Fortran default singfac_min". This is the ψ-offset for one-sided derivative eval, a THIRD distinct use, not reg_spot.
- reg_spot (0.05, singfac-space smoothing of reconstructed fields) and singfac_min (1e-4, ODE crossing gate) are separate; both ported.
48 changes: 47 additions & 1 deletion docs/src/kinetic_forces.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ extension:
`chi_phi` (toroidal momentum diffusivity ``\chi_\phi``). Each dataset
carries a `units` attribute; the root carries `schema_version` and
`provenance`. Densities are m⁻³, temperatures eV, frequencies rad/s,
diffusivities m²/s. Write files with `write_kinetic_h5`.
diffusivities m²/s. Additional datasets named `n_*` (e.g. `n_D`, `n_T`)
are named per-species densities for multi-ion runs; names outside `n_*`
are reserved for future schema fields and ignored. Write files with
`write_kinetic_h5` (which round-trips the per-species densities).
- **ASCII (`.gpeckf`/`.kin`/`.dat`)** — legacy six-column whitespace table
`psi_n n_i n_e T_i[eV] T_e[eV] omega_E`, retained for backward
compatibility. Header rows are skipped.
Expand All @@ -25,6 +28,49 @@ The NTV calculation consumes `n_i, n_e, T_i, T_e, omega_E`; `chi_e`/`chi_phi`,
when present, are carried for the resistive-layer (SLAYER) analysis and ignored
here.

## Multi-ion runs

A plasma may declare an arbitrary list of main-ion species; the NTV is computed
per species under one shared full-composition ``Z_\mathrm{eff}`` and summed
(``\tau = \sum_s \tau_s``) over the main ions, the quasineutrality-closing
impurity, and (with `electron = true`) the electrons. This applies to both NTV
paths: the post-PE ψ-quadrature diagnostic and the self-consistent
`kinetic_source = "calculated"` matrices.

```toml
[KineticForces]
kinetic_file = "kinetic.h5" # n_i column/dataset = TOTAL main-ion density
electron = true # add electron NTV in addition to the ion species
zimp = 6 # impurity charge (closes quasineutrality)
mimp = 12 # impurity mass

[[KineticForces.ion_species]]
z = 1
m = 2
fraction = 0.5 # this species' share of the total n_i
[[KineticForces.ion_species]]
z = 1
m = 3
density = "n_T" # or: explicit n_* dataset from the HDF5 kinetic file
```

Each species sets exactly one of `fraction` (share of the file's total `n_i`)
or `density` (a named `n_*` dataset). An all-fraction list must sum to 1; in a
mixed list the impurity content is set by the file's `n_i`/`n_e` deficit, not
by a fraction shortfall, and fractions may sum below (never above) 1. Every
main-ion charge must satisfy `z < zimp`. An empty `ion_species` list runs the
single main ion from `zi`/`mi` (with `electron = true` still adding the
electron species — the electron flag always means *in addition to* the ions).

Per-species results are written to HDF5 groups `kinetic_forces_<label>`
(e.g. `kinetic_forces_ion1_z1_m2`, `kinetic_forces_impurity_z6_m12`,
`kinetic_forces_electron`) alongside the summed total in `kinetic_forces`. The
summed cumulative torque profile is a diagnostic (linear interpolation onto the
union grid); the summed `total_torque` scalar is the exact Gauss-Kronrod value.

The profile-scaling knobs below are not supported together with a multi-ion
`ion_species` list (they error).

## Profile Scaling Knobs

Seven scaling factors are available on `KineticForcesControl` to modify kinetic
Expand Down
21 changes: 18 additions & 3 deletions examples/Solovev_kinetic_calculated_example/gpec.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Solovev analytical equilibrium — n=1 ideal stability with calculated kinetic matrices.
# Exercises the real KineticForces (NTV) physics path at full strength; kinetic regression
# fixture (et[1]≈1.894-1.525i). The equilibrium is generated analytically from the embedded
# [SOL_INPUT] section, with n·T tied to the Solovev P₀≈4.27e4 Pa via the local kinetic.dat.
# Exercises the real KineticForces (NTV) physics path at full strength, with the main ion
# declared as a 50/50 D-T mix so the self-consistent kinetic matrices are accumulated over
# the multi-ion species loop (pure plasma, n_i = n_e, so no impurity species arises). The
# equilibrium is generated analytically from the embedded [SOL_INPUT] section, with n·T tied
# to the Solovev P₀≈4.27e4 Pa via the local kinetic.dat.

[Equilibrium]
eq_type = "sol" # Type of the input 2D equilibrium file
Expand Down Expand Up @@ -48,6 +50,19 @@ ucrit = 1e3 # Maximum fraction of solutions allowed before re-
write_outputs_to_HDF5 = true # Write outputs to the HDF5 file
verbose = false # Enable verbose logging

[KineticForces]
kinetic_file = "kinetic.dat" # Kinetic profile file: psi_n, n_i, n_e, T_i, T_e, omega_E columns

[[KineticForces.ion_species]]
z = 1 # Species charge (fundamental units)
m = 2 # Species mass (proton masses) — Deuterium
fraction = 0.5 # This species' share of the total main-ion density n_i

[[KineticForces.ion_species]]
z = 1 # Species charge (fundamental units)
m = 3 # Species mass (proton masses) — Tritium
fraction = 0.5 # This species' share of the total main-ion density n_i

# Solovev analytic equilibrium parameters (eq_type = "sol"); see SolovevConfig in src/Equilibrium.
[SOL_INPUT]
mr = 128 # Number of radial grid zones
Expand Down
5 changes: 5 additions & 0 deletions examples/Solovev_kinetic_multiion_example/forcing.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Forcing data for perturbed equilibrium calculations
# normalization: normal_field_T
# Format: n m amplitude_real amplitude_imag
# Single mode test case: n=1, m=2, amplitude=1e-4 T
1 2 1e-4 0.0
Loading