KineticForces Module
Kinetic torque and energy calculations for perturbed equilibria. Implements neoclassical toroidal viscosity (NTV) from the PENTRC formulation (Logan & Park, 2013; Logan, 2015).
Kinetic profile file formats
Kinetic profiles are read by read_kinetic_file, which dispatches on the file extension:
- HDF5 (
.h5/.hdf5) — the GPEC kinetic schema (recommended). Datasets at the file root: requiredpsi(normalized poloidal flux),n_e,T_i,T_e,omega_E(andn_i, defaulting ton_eif omitted); optionalomega_tor,chi_e(perpendicular heat diffusivity $\chi_\perp$), andchi_phi(toroidal momentum diffusivity $\chi_\phi$). Each dataset carries aunitsattribute; the root carriesschema_versionandprovenance. Densities are m⁻³, temperatures eV, frequencies rad/s, diffusivities m²/s. Additional datasets namedn_*(e.g.n_D,n_T) are named per-species densities for multi-ion runs; names outsiden_*are reserved for future schema fields and ignored. Write files withwrite_kinetic_h5(which round-trips the per-species densities). - ASCII (
.gpeckf/.kin/.dat) — legacy six-column whitespace tablepsi_n n_i n_e T_i[eV] T_e[eV] omega_E, retained for backward compatibility. Header rows are skipped.
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.
[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 fileEach 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).
The summed total is written to KineticForces/<method>/ exactly as in a single-ion run, and each species' own contribution to KineticForces/PerSpecies/<label>/<method>/ — labels are ion_z<z>_m<m>, impurity_z<z>_m<m>, and electron (a numeric suffix is appended only if a run repeats a (z, m) pair). Every per-species group carries the same datasets and metadata as the total, so dTdpsi, T, and EnergyIntegrals/ are available per species. 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 profiles and physics parameters for sensitivity studies:
| Knob | Default | Stage | Description |
|---|---|---|---|
density_factor | 1.0 | Profile loader | Density scaling (ni, ne) |
temperature_factor | 1.0 | Profile loader | Temperature scaling (Ti, Te) |
ExB_rotation_factor | 1.0 | Profile loader | ExB rotation scaling (omegaE) |
toroidal_rotation_factor | 1.0 | Profile loader | Total toroidal rotation scaling (wphi) |
wdfac | 1.0 | Evaluation time | Magnetic drift frequency scaling |
nufac | 1.0 | Evaluation time | Collisionality scaling |
divxfac | 1.0 | Evaluation time | $\nabla \cdot \xi_\perp$ scaling |
Profile-loader knobs (density_factor, temperature_factor, ExB_rotation_factor, toroidal_rotation_factor)
These four knobs are applied during kinetic profile loading in load_kinetic_profiles, before any physics evaluation. The physical model is:
\[\omega_\phi = \omega_E + \omega_{*n,i} + \omega_{*T,i}\]
where $\omega_\phi$ is the user's measured total toroidal rotation, $\omega_E$ is the ExB rotation (the input profile), and the diamagnetic frequencies are computed from cubic spline derivatives of the unscaled profiles:
\[\omega_{*n,i} = -\frac{2\pi T_i}{\chi_1 Z_i e} \frac{1}{n_i}\frac{dn_i}{d\psi}, \qquad \omega_{*T,i} = -\frac{2\pi}{\chi_1 Z_i e} \frac{dT_i}{d\psi}\]
The scaling sequence is:
- Build first-pass cubic splines from original (unscaled) profiles
- Compute $\omega_{*n,i}$, $\omega_{*T,i}$, and $\omega_\phi$ at each grid point
- Apply
density_factorto density,temperature_factorto temperature, and update diamagnetic terms: $\omega_{*n,\text{new}} = \texttt{temperature\_factor} \cdot \omega_{*n,i}$ (density_factorcancels in $T \cdot (dn/d\psi)/n$), $\omega_{*T,\text{new}} = \texttt{temperature\_factor} \cdot \omega_{*T,i}$ - Reform ExB rotation: $\omega_E = \texttt{toroidal\_rotation\_factor} \cdot \omega_\phi - \omega_{*n,\text{new}} - \omega_{*T,\text{new}}$
- Apply ExB scaling: $\omega_E \mathrel{*}= \texttt{ExB\_rotation\_factor}$
- Recompute collisionality from scaled density and temperature
- Build final splines from scaled arrays
Evaluation-time knobs (wdfac, nufac, divxfac)
These three knobs are applied during the bounce-averaged kinetic matrix and torque calculations in KineticForces/Torque.jl and related modules. They multiply the magnetic drift, collisionality, and $\nabla \cdot \xi_\perp$ terms respectively, and do not modify the stored kinetic profile splines.
Collisionality from scaled profiles: Julia recomputes collisionality ($\nu_i$, $\nu_e$) from the scaled density and temperature arrays. Fortran PENTRC (
inputs.f90:237-246) computes collisionality from unscaled profiles. If you need independent collisionality scaling without changing the density/temperature profiles, usenufac.Consistent derivative ordering: Fortran's
inputs.f90:269-272mixes pre-scaling spline derivatives with post-scaling array values when computing thetoroidal_rotation_factorback-solve. Julia uses a clean reimplementation with consistent pre-scaling derivatives throughout.
HDF5 outputs: complex torque convention and the EnergyIntegrals layout
The method level of KineticForces/<method>/ reports the two physical scalars a user wants first: total_torque = $T_\phi$ (N·m) and total_energy = $\delta W_k$ (J). Internally both are halves of one complex quantity $T = T_\phi + 2in\,\delta W_k$ (Logan 2013), and the ψ-profiles dTdpsi and T store that complex $T$ directly — so imag(T) carries the $2n$ factor while total_energy has it divided out. The per-record EnergyIntegrals/torque and EnergyIntegrals/kinetic_energy are separate complex diagnostics of the two integrand halves at each $(\psi, \lambda, \ell)$ evaluation, which is why they are not packed into one number there.
EnergyIntegrals/ stores the variable-length integration trajectories in the flat-plus-offsets ragged layout (chosen over HDF5 VLEN types for cross-language support; Tearing/Diagnostics/* uses the same pattern). Record k spans offsets[k]+1 : offsets[k+1] (Julia, 1-based) of each *_all array:
h5open("gpec.h5", "r") do f
g = f["KineticForces/fgar/EnergyIntegrals"]
off = read(g["trajectory_offsets"])
x_k = read(g["x_all"])[off[k]+1:off[k+1]] # record k's abscissae
I_k = read(g["integrand_all"])[off[k]+1:off[k+1]] # its complex integrand
endg = f["KineticForces/fgar/EnergyIntegrals"] # h5py, 0-based
x_k = g["x_all"][g["trajectory_offsets"][k]:g["trajectory_offsets"][k + 1]]GeneralizedPerturbedEquilibrium.KineticForces.METHOD_REGISTRY — Constant
METHOD_REGISTRYSingle source of truth for the NTV calculation methods. Each entry is a NamedTuple (name, flag, kind, doc):
name— short method identifier used as the HDF5 group key and inintr.methodflag— theKineticForcesControlfield symbol that enables the methodkind— dispatch routing tag consumed bymethod_kind/Torque.jl(:garfor the GAR/matrix family,:fcgl/:rlar/:clarfor the three special-cased methods)doc— one-line description printed in verbose output
The method names/docs and the Compute.jl enable list are all derived from this tuple, and Torque.jl routes on kind, so the methods are enumerated in one place. To add a method: append an entry here and add the matching *_flag field to KineticForcesControl.
GeneralizedPerturbedEquilibrium.KineticForces.BounceData — Type
BounceDataBounce-averaged quantities as functions of pitch angle λ. Produced by compute_bounce_data(), consumed by pitch integration.
GeneralizedPerturbedEquilibrium.KineticForces.BounceScratch — Type
BounceScratch(ntheta, mpert)Per-surface scratch for the bounce-averaging inner loops, allocated once in compute_bounce_data and reused across every λ. Sizes are fixed for a flux surface (ntheta sub-grid points, mpert Fourier modes). Buffers the loops populate only partially are fill!-reset per λ.
Fields
g_wb::Vector{Float64}: lengthntheta— bounce-action integrand samplesg_wd::Vector{Float64}: lengthntheta— drift integrand samplescum_wb_arr::Vector{Float64}: lengthntheta— cumulative bounce-action integraljvtheta::Vector{ComplexF64}: lengthntheta— action integrandbj_samples::Vector{ComplexF64}: lengthntheta— action bounce-integral sampleswsamp::Vector{ComplexF64}: lengthntheta— per-mode W bounce-integral sampleswmu_mt::Matrix{ComplexF64}:mpert × ntheta— W_μ per θwen_mt::Matrix{ComplexF64}:mpert × ntheta— W_E per θexpm::Vector{ComplexF64}: lengthmpert— Fourier basis at a θpl::Vector{ComplexF64}: lengthntheta— bounce phase factorwmu_ba::Vector{ComplexF64}: lengthmpert— bounce-averaged W_μwen_ba::Vector{ComplexF64}: lengthmpert— bounce-averaged W_Ewmats_lmda::Vector{ComplexF64}: lengthnqty_matrix(mpert)— packed W outer productstspl_f::Vector{Float64}: length 5 — in-place tspl(θ) evaluationint_w::Vector{Float64},cumint_W::Matrix{Float64}: precomputed exact-cubic quadrature weights on the fixed unit θ-grid (∫ = int_w·y,cumulative = cumint_W·y); shared read-only across surfaces, see_quadrature_weights
GeneralizedPerturbedEquilibrium.KineticForces.EnergyIntegrationResult — Type
EnergyIntegrationResultResults from a single energy-space integration at one (ψ, λ, ℓ) point. Trajectory fields are only populated when ctrl.save_records=true.
GeneralizedPerturbedEquilibrium.KineticForces.EnergyParams — Type
EnergyParamsParameters for the energy integrand evaluation.
GeneralizedPerturbedEquilibrium.KineticForces.IonSpecies — Type
IonSpecies(; z, m, fraction=NaN, density="")One main-ion species in a multi-ion NTV run. z/m are the charge (e) and mass (proton masses). The density is given by exactly one of fraction or density, which select a fraction of the total n_i profile or an explicit per-species profile in the kinetic file.
GeneralizedPerturbedEquilibrium.KineticForces.KineticForcesControl — Type
KineticForcesControlUser-facing control parameters from the TOML [KineticForces] section. Configures which NTV methods to run, species parameters, tolerances, and output options.
Constructed via keyword arguments or from a TOML dict:
ctrl = KineticForcesControl(; (Symbol(k) => v for (k, v) in inputs["KineticForces"])...)Immutable: vary a field by building a new control rather than assigning to one (the multi-species loop does this per species, and check_psi_quadrature_convergence's test builds a second control for its differing tolerance).
GeneralizedPerturbedEquilibrium.KineticForces.KineticForcesInternal — Type
KineticForcesInternalInternal working state for KineticForces calculations. Holds equilibrium-derived quantities, profile interpolants, and integration results.
Fields replacing former module-level globals:
ro,bo,chi1: Equilibrium geometry parametersmthsurf,mfac: Poloidal grid infodbob_m,divx_m: Perturbation mode interpolantssing_psis: Rational-surface ψ locations (sorted, from the stability analysis), used as panel boundaries for the outer ψ torque quadrature so the resonant peaks fall on Gauss-Kronrod interval endpoints instead of driving deep adaptive bisection
Equilibrium and kinetic profile data are read directly from the PlasmaEquilibrium (equil.profiles, equil.geometry) and the externally-loaded KineticProfileSplines — no shadow copies are kept on this struct.
GeneralizedPerturbedEquilibrium.KineticForces.KineticForcesInternal — Method
KineticForcesInternal(equil; verbose=false)Construct KineticForcesInternal from a PlasmaEquilibrium, extracting the equilibrium geometry parameters needed for NTV calculations.
GeneralizedPerturbedEquilibrium.KineticForces.KineticForcesState — Type
KineticForcesStateAccumulated results from all KineticForces computations. Written to gpec.h5 under the "KineticForces" group.
GeneralizedPerturbedEquilibrium.KineticForces.MethodResult — Type
MethodResultResults for one NTV computation method across all flux surfaces.
GeneralizedPerturbedEquilibrium.KineticForces.PitchGARParams — Type
PitchGARParamsParameters for the GAR pitch-angle integrand. Uses a unified fbnce interpolant that returns [ωb, ωd, f₁, f₂, ...] at each λ, matching Fortran lambdaintgrl_lsode.
GeneralizedPerturbedEquilibrium.KineticForces.SurfaceBField — Type
SurfaceBFieldThe periodic cubic B(θ) of one flux surface, decomposed once and reused for every λ. Holds the per-cell polynomial coefficients, B at every knot, and the stationary points of B. Consecutive stationary points bound intervals on which B is monotone, so each holds at most one bounce point; within such an interval the cached knot values locate the cell by bisection and the cell's cubic is then solved in closed form.
Fields
knot::Vector{Float64}: cell boundaries, ascending,knot[1] = 0,knot[end] = 1bknot::Vector{Float64}: B at each knotpoly::Vector{NTuple{4,Float64}}: per-cell(d, c, b, a)ofS(u) = d + cu + bu² + au³, in the cell-local coordinateu = θ − knot[i]theta::Vector{Float64}: stationary points of B, ascending, in [0,1)bval::Vector{Float64}: B at each stationary point
GeneralizedPerturbedEquilibrium.KineticForces._b_at — Method
Evaluate B at θ from the cached cell polynomials.
GeneralizedPerturbedEquilibrium.KineticForces._bisect_cell — Method
Bisection on the span's monotone knot values, for when the hint does not pay off.
GeneralizedPerturbedEquilibrium.KineticForces._bounce_integrate — Method
_bounce_integrate(...)Perform bounce integrals over θ sub-grid. Computes ωbbar, ωdbar, |δJ|², and optionally W matrix outer products. Ports Fortran torque.F90 lines 674-793.
GeneralizedPerturbedEquilibrium.KineticForces._bounce_points_at_lambda! — Method
Bounce points of v_par(θ) = 1 − (λ/bo)·B(θ) for a trapped particle. B = bo/λ at a bounce point, so a monotone interval between stationary points of B contains one iff bo/λ lies strictly between its endpoint B values — a scalar test against cached values. The crossing is then solved directly from the cell's cubic coefficients: no iteration, no spline evaluation, and no blind search, since hints carries each interval's cell over from the previous λ.
Returns roots sorted descending, the order the deepest-well and marginally-trapped logic downstream assumes; fewer than two roots signals a degenerate λ and sends the caller to the fallback. hints is sized length(bf.theta) + 1, the extra slot being the far half of the interval that wraps through the seam.
GeneralizedPerturbedEquilibrium.KineticForces._build_lambda_grid — Method
Build λ grid based on method character (f/t/p). Returns (lambda, dlambda) with endpoints excluded.
GeneralizedPerturbedEquilibrium.KineticForces._cell_index — Method
Cell index holding θ ∈ [0,1], from the ascending knot vector.
GeneralizedPerturbedEquilibrium.KineticForces._cell_level_root — Method
Solve B(θ) = btarget inside cell ic, restricted to θ ∈ [θlo, θhi]. The cubic is solved in closed form and polished with one Newton step, which recovers the digits the closed form loses when two of its roots are nearly coincident. Returns NaN if no root lies in the restricted range.
GeneralizedPerturbedEquilibrium.KineticForces._cell_position — Method
Where btarget sits relative to cell ic of a monotone span: -1 before it, 0 inside, +1 past it. s carries the span's direction so one comparison serves both. The span's first and last cells are entered part-way, at the stationary points bounding it, so their outer edge value comes from ba/bb rather than from a knot.
GeneralizedPerturbedEquilibrium.KineticForces._energy_collision_frequency — Method
_energy_collision_frequency(x::Float64, p::EnergyParams) → Float64Collision frequency ν(x) at normalized energy x = E/T.
"zero": collisionless (ν = 0)"small": 1e-5 * we"krook": unmodified Krook operator"harmonic": (1 + (l/2)²) * krook * x^(-3/2)
GeneralizedPerturbedEquilibrium.KineticForces._energy_integrand_real — Method
_energy_integrand_real(x::Float64, p::EnergyParams) → ComplexF64Physical energy integrand in x-space, N(x)·exp(-x)/denom(x), evaluated on the real axis. Used both by the production integral (integrate_energy via _integrate_energy_resonant) and for diagnostics (evaluate_energy_integrand).
GeneralizedPerturbedEquilibrium.KineticForces._energy_numerator — Method
_energy_numerator(x::Float64, p::EnergyParams) → ComplexF64Numerator N(x) of the energy integrand, without the resonance denominator and without the Maxwellian weight exp(-x). The physical x-space integrand is N(x)·exp(-x)/denom(x); the residue at a pole uses N(xres)·exp(-xres).
For CGL there is no resonance denominator: N_cgl = x^2.5 / (i·n).
GeneralizedPerturbedEquilibrium.KineticForces._energy_numerator_deriv — Method
_energy_numerator_deriv(x::Float64, p::EnergyParams) → ComplexF64x-derivative dN/dx of _energy_numerator, used for the analytic regular-part limit at a real-axis (ν=0) resonance pole. CGL is excluded — the real-pole path never carries a CGL numerator (CGL has no pole).
GeneralizedPerturbedEquilibrium.KineticForces._find_bounce_points_and_grid — Method
Find bounce points for trapped/passing particles and build θ sub-grid. Returns (t1, t2, thetapoints, thetaweights).
GeneralizedPerturbedEquilibrium.KineticForces._find_deepest_well — Method
Find the deepest potential well (largest midpoint v_par) among bounce-point pairs, handling pairs that wrap through θ = 0/1.
GeneralizedPerturbedEquilibrium.KineticForces._full_idx — Method
Full-block index (column-major) within a non-Hermitian block.
GeneralizedPerturbedEquilibrium.KineticForces._jbb_deweight! — Method
_jbb_deweight!(out, jbb_modes, ft, psi, equil, mthsurf, theta_buf)JBB deweighting step: inverse DFT → divide by J(ψ,θ)·B(ψ,θ)² → forward DFT.
Matches Fortran set_peq lines 859-868: transforms JBB-weighted m-space data to θ-space, removes the J·B² weighting at each poloidal angle, and transforms back.
GeneralizedPerturbedEquilibrium.KineticForces._locate_cell — Method
Cell holding the B = btarget crossing on a monotone span running from cell ia to cell ib.
λ advances monotonically through compute_bounce_data, so btarget = bo/λ falls monotonically and each interval's crossing walks steadily along the cells in one direction. Resuming from the previous λ's cell therefore costs a step or two, the same hint idiom FastInterpolations uses for its own searches. Bisection stays as the fallback for the first λ of a surface and for the sweep near a stationary point, where B is flat and the crossing can cross many cells between consecutive λ.
GeneralizedPerturbedEquilibrium.KineticForces._monotone_segment_root — Method
Solve the single B = btarget crossing on [θa, θb], a span on which B is monotone and which does not cross the θ = 0/1 seam. Returns (θ, cell); the cell feeds back as the next λ's hint. θ is NaN when no root lies in the span.
GeneralizedPerturbedEquilibrium.KineticForces._pitch_gar_kernel_quadgk! — Method
_pitch_gar_kernel_quadgk!(out::Vector{ComplexF64}, lambda, p::PitchGARParams)In-place complex-valued kernel for quadgk!. Writes out[i] = fvals[i+2] * xint_decomposed for i in 1..nqty. QuadGK natively handles ComplexF64.
GeneralizedPerturbedEquilibrium.KineticForces._pitch_gar_kernel_quadgk_wt! — Method
_pitch_gar_kernel_quadgk_wt!(out::Vector{ComplexF64}, lambda, p::PitchGARParams)Dual-output pitch kernel. Fills a length-2*nqty buffer: out[1:nqty] — fwmm half: fvals * complex(0, imag(xint)) out[nqty+1:2*nqty] — ftmm half: fvals * complex(real(xint), 0)
One energy integration per λ; both halves share it.
GeneralizedPerturbedEquilibrium.KineticForces._powspace_antideriv — Method
_powspace_antideriv(x, pow)Analytic antiderivative of |(1-x²)|^pow for pow 1-9. Matches Fortran powspace_sub cases exactly.
GeneralizedPerturbedEquilibrium.KineticForces._quadratic_real_roots — Method
Real roots of a·u² + b·u + c = 0 in the (count, r1, r2, r3) shape of _real_cubic_roots, r3 always NaN. A discriminant within rounding of zero is a double root rather than none; a degenerate leading coefficient falls through to the linear case.
GeneralizedPerturbedEquilibrium.KineticForces._quadrature_weights — Method
_quadrature_weights(ntheta) → (int_w, cumint_W)Exact integral of the CubicFit-endpoint spline on the fixed grid range(0,1,ntheta) is a constant linear functional of the node samples, so ∫ = int_w·y and the cumulative integral is cumint_W·y. The weights are obtained once per ntheta by evaluating the public integrate / cumulative_integrate! on the unit basis vectors — bit-faithful to fitting and integrating each sample vector directly, but reducing the per-λ hot loop to a dot/mul!. Cached (build guarded by a lock); the returned arrays are read-only.
GeneralizedPerturbedEquilibrium.KineticForces._real_cubic_roots — Method
Real roots of a·u³ + b·u² + c·u + d = 0 as (count, r1, r2, r3), unused slots NaN. The count comes from a conditioned ratio test rather than the sign of the discriminant, which cancels to rounding noise when two roots nearly coincide; three real roots use the trigonometric form. A degenerate leading coefficient falls through to _quadratic_real_roots.
GeneralizedPerturbedEquilibrium.KineticForces._real_pole_regular_part — Method
_real_pole_regular_part(xr, p, leff, wb, n, wd) → ComplexF64Laurent regular part (finite limit at x → x_res) of the pole-subtracted real-axis (ν=0) integrand N(x)·exp(-x)/(i·Ω(x)) − R/(x − x_res), with Ω(x) = leff·wb·√x + n·(we+wd·x) and R = N(x_res)·exp(-x_res)/(i·Ω′).
Writing h(x) = N(x)·exp(-x), the limit is [h′(x_res) − h(x_res)·Ω″/(2Ω′)] / (i·Ω′), with h′ = (N′ − N)·exp(-x), Ω′ = leff·wb/(2√x) + n·wd, Ω″ = −leff·wb/(4·x^{3/2}).
GeneralizedPerturbedEquilibrium.KineticForces._resonance_nodes_from_frequencies — Method
_resonance_nodes_from_frequencies(wbhat_f, welec_f, wdhat_f, grid; n, nl, xeval=2.5) → Vector{Float64}Scan grid for the zeros of the resonance operator Ω_ℓ(x; ψ) = ℓ·ω_b(ψ)·√x + n·(ω_E(ψ) + ω_d(ψ)·x) at energy x = xeval, for every bounce harmonic ℓ ∈ −nl:nl, given per-ψ frequency callables. xeval defaults to 2.5 — the peak of the Maxwellian-weighted drive x^2.5·e^−x, where the resonance overlaps the most particles, so the located ψ surfaces sit on the NTV torque-density peaks (on the DIII-D case the x=2.5 nodes land ~3× closer to the measured dT/dψ spikes than the thermal-energy x=1 estimate). Roots from all harmonics are concatenated (deduplication against coincident surfaces happens in psi_panel_points).
GeneralizedPerturbedEquilibrium.KineticForces._setup_surface_state — Method
_setup_surface_state(psi, n, l, zi, mi, wdfac, electron,
equil, intr, kinetic_profiles) → NamedTuplePrivate helper for the calculated-matrix path. Reproduces the per-surface setup in tpsi! (theta-grid sampling, bounce-extremum finding, flux-function evaluation, diamagnetic/drift frequencies) without any of the perturbation- dependent bookkeeping or method dispatch.
This keeps compute_kinetic_matrices_at_psi! structurally independent of tpsi! so the matrix-only path can be evolved (e.g. QuadGK pitch in Phase C) without perturbing the perturbative torque pipeline.
GeneralizedPerturbedEquilibrium.KineticForces._surface_b_field — Method
Decompose the cubic B_vpar by walking its cells once, recording each cell's polynomial and endpoint value and solving the quadratic dS/dθ = 0 on each to get the exact stationary points. Uses only the public coeffs/CellPoly interface, so it holds for whatever θ grid the surface interpolant was built on.
GeneralizedPerturbedEquilibrium.KineticForces._tri_idx — Method
Upper-triangle index (1 ≤ i ≤ j ≤ mpert) within a triangular block.
GeneralizedPerturbedEquilibrium.KineticForces._vpar_from_spline — Method
Parallel-velocity factor v_par = 1 − (λ/bo)·B(θ) from the periodic cubic of B (B_vpar, built where the surface interpolants are constructed), keeping v_par consistent with the bounce-point roots as in Fortran's vspl.
GeneralizedPerturbedEquilibrium.KineticForces.calculate_clar — Method
calculate_clar(psi, n, l, q, epsr, wdian, wdiat, welec, nuk, bo,
bmax, bmin, n_s, T_s, mass, chrg, tspl, dbob_m_f, divx_m_f,
divxfac, wdfac)::ComplexF64Calculate CLAR (Circular Large Aspect Ratio) torque. Uses pitch-angle resolved calculations for trapped and passing particles. Includes bounce-averaged integrals over lambda (pitch angle).
Status: Partially implemented (stub for full calculation)
GeneralizedPerturbedEquilibrium.KineticForces.calculate_fcgl — Method
calculate_fcgl(psi, n, l, tspl, dbob_m_f, divx_m_f, divxfac, n_s, T_s,
equil, intr)::ComplexF64Calculate FCGL (Full Circular Gyrokinetic Landau) torque. Implements simplified energy balance equation. Only valid for bounce harmonic l=0.
Based on: [Logan et al., Phys. Plasmas 2013]
GeneralizedPerturbedEquilibrium.KineticForces.calculate_gar — Method
calculate_gar(psi, n, l, q, epsr, wdian, wdiat, welec, nuk, bo, bmax,
bmin, n_s, T_s, mass, chrg, tspl, dbob_m_f, divx_m_f,
divxfac, wdfac, method, op_wmats; kwargs...)::ComplexF64Calculate GAR (General Aspect Ratio) torque. Fully general method without aspect ratio expansion. Handles variants: FGAR (full), TGAR (trapped), PGAR (passing). Can compute torque (TMM), energy (WMM), or matrix elements (KMM/RMM).
Ports Fortran torque.F90 GAR branch (lines 529-932).
Steps
- Compute bounce-averaged quantities via
compute_bounce_data() - Build fbnce interpolant over λ, normalize for numerical stability
- Integrate over pitch angle via
integrate_pitch_gar_quadgk() - Apply torque normalization (Eq. 19, Logan et al. 2013)
- If matrix path: assemble and normalize kinetic matrices
Keyword Arguments (rex/imx override)
rex_override::Union{Nothing,Float64}: Override real-part multiplier for resonance operator. When both overrides are provided, bypasses method-string derivation.imx_override::Union{Nothing,Float64}: Override imaginary-part multiplier. Userex_override=1.0, imx_override=1.0to get full complex result for simultaneous kwmat/ktmat extraction viacompute_kinetic_matrices_at_psi!.
Reference: [Logan et al., Phys. Plasmas 20, 122507 (2013)]
GeneralizedPerturbedEquilibrium.KineticForces.calculate_rlar — Function
calculate_rlar(psi, n, l, q, epsr, wdian, wdiat, welec, wdhat, wbhat,
nueff, dVdpsi, n_s, T_s, dbob_m_f, bo, bmin)::ComplexF64Calculate RLAR (Reduced Large Aspect Ratio) torque. Uses energy space integration with pitch angle averaging. Valid for low aspect ratio tokamaks (ε << 1).
Reference: [Logan et al., Phys. Plasmas, 2013]
GeneralizedPerturbedEquilibrium.KineticForces.check_psi_quadrature_convergence — Method
check_psi_quadrature_convergence(total, quad_err, ctrl, method)Warn when the ψ torque quadrature terminated without satisfying the requested tolerances (hit maxevals_psi), or when a nonzero user-set atol_psi dominated termination — the silent-garbage scenario for weak applied fields, since NTV scales as δB².
GeneralizedPerturbedEquilibrium.KineticForces.combine_species_states — Method
combine_species_states(states) -> KineticForcesStateSum per-species KineticForcesState results into a single total (τ = Σs τs). Per method: the scalar total_torque/total_energy are summed exactly; the dT/dψ profile is summed by linearly interpolating each species' own (psi_grid, dtdpsi) arrays onto the sorted union of the species ψ grids (zero outside a species' range), and the cumulative T(ψ) is re-integrated (trapezoid) from that summed profile. All species share the same ψ-integration range (ctrl.psilims), so the grids differ only in adaptive nodes. The interpolated/trapezoid t_cumulative is a diagnostic profile — its endpoint need not equal the exactly-summed Gauss-Kronrod total_torque, especially near sharp resonances. The combined MethodResult carries only the summed scalars and profile; per-species diagnostics (torque_profile, records, panel_psis, resonance_psis) are not aggregated and are left at their defaults.
GeneralizedPerturbedEquilibrium.KineticForces.compute_bounce_data — Method
compute_bounce_data(psi, n, l, q, bo, bmax, bmin, theta_bmax,
tspl, B_vpar, mfac, chi1, ro, dbob_m_f, divx_m_f,
divxfac, wdfac, mass, chrg, T_s, method;
nlmda=128, ntheta=128,
smat=nothing, tmat=nothing, xmat=nothing,
ymat=nothing, zmat=nothing) → BounceDataCompute bounce-averaged quantities as functions of pitch angle λ. This is the core function that sets up all λ-dependent quantities needed by the pitch-angle quadrature.
Ports Fortran torque.F90 lines 530-816 (GAR branch).
Arguments
psi: Normalized poloidal fluxn: Toroidal mode numberl: Bounce harmonic numberq: Safety factor at this ψbo: On-axis toroidal field [T]bmax, bmin: Max/min of B(θ) at this ψtheta_bmax: θ location of Bmax (nodal knot; the passing-transit start)tspl: Periodic poloidal interpolant: tspl(θ) → [B, dB/dψ, dB/dθ, J, dJ/dψ]B_vpar: Periodic cubic of B(θ) used for v_par and the bounce-point roots (the Fortranvsplequivalent)mfac: Poloidal mode numbers [mlow:mhigh]chi1: 2π·ψ₀ flux normalizationro: Major radius [m]dbob_m_f: δB/B Fourier modes at this ψ (ComplexF64 vector, length mpert)divx_m_f: ∇·ξ⊥ Fourier modes at this ψ (ComplexF64 vector, length mpert)divxfac, wdfac: Scaling factorsmass: Particle mass [kg]chrg: Particle charge [C]T_s: Species temperature at this ψ [J]method: Method string (first char: f/t/p determines λ range)
Keyword Arguments
nlmda: Number of pitch angle grid points (default 128, matching Fortran pentrc nlmda)ntheta: Number of poloidal grid points per bounce (default 128)smat, tmat, xmat, ymat, zmat: Geometric matrices (mpert×mpert) for kinetic matrix path
GeneralizedPerturbedEquilibrium.KineticForces.compute_calculated_kinetic_matrices — Method
compute_calculated_kinetic_matrices(ffs_ctrl, equil, ffs_intr, metric, mats;
kf_ctrl=KineticForcesControl(),
kinetic_profiles)
→ (kw_flat, kt_flat)Drive the KineticForces matrix kernel over the ψ grid stored in metric.xs and return (kw_flat, kt_flat) arrays of shape (mpsi, np^2, 6) matching the contract that ForceFreeStates._compute_fkg_matrices consumes.
The arrays carry the six bounce-averaged kinetic energy / torque matrices (Logan 2015 Eqs 7.30–7.35) for every ψ on the equilibrium grid, packed as block-diagonal matrices over toroidal mode number n ∈ [nlow, nhigh] and flattened to (np = mpert·npert)².
This routine reads equilibrium-derived profiles (q, dV/dψ, ⟨r⟩, ⟨R⟩) directly from named splines on equil.profiles and equil.geometry, and kinetic profiles (n, T, ωE, ν) from the `kineticprofilesargument, avoiding the former shadow-copy pattern in KineticForcesInternal. The perturbation-mode interpolants (kfintr.dbobm,kfintr.divxm`) remain unwired and are tracked as follow-up work blocked on PR #196 — see the plan's "Out of scope" section.
Arguments
ffs_ctrl: ForceFreeStatesControl (carrieskinetic_factor,kinetic_source)equil: PlasmaEquilibrium with 2D interpolants and named profile/geometry splinesffs_intr: ForceFreeStatesInternal (mode indexing)metric: MetricData (provides ψ grid viametric.xs)mats: MatrixSplines (used only fornumpert_totalcross-check)
Keyword arguments
kf_ctrl: KineticForcesControl, defaults toKineticForcesControl(). Used to carry NTV-specific knobs (nl, zi, mi, wdfac, divxfac, electron) that the KineticForces kernel needs but ForceFreeStatesControl does not expose.kinetic_profiles::Equilibrium.KineticProfileSplines: Required. Named kinetic- profile splines loaded viaEquilibrium.load_kinetic_profiles.
Returns
kw_flat::Array{ComplexF64,3}: Energy matrices, shape(mpsi, np^2, 6)kt_flat::Array{ComplexF64,3}: Torque matrices, shape(mpsi, np^2, 6)
GeneralizedPerturbedEquilibrium.KineticForces.compute_kinetic_matrices_at_psi! — Method
compute_kinetic_matrices_at_psi!(kwmat, ktmat, psi, n, l, zi, mi,
wdfac, divxfac, electron, equil, intr, kinetic_profiles)Compute the six kinetic Euler-Lagrange coefficient matrices at a single flux surface and split them into kwmat and ktmat in the convention used by the DCON matrix-assembly path (Logan 2015 Eqs 7.30–7.35).
Rather than running two integrations like the reference Fortran PENTRC (one with rex=0, imx=1 for kwmat and another with rex=1, imx=0 for ktmat), this path integrates once with rex=imx=1 to get the full complex response, then decomposes by real/imag parts — equivalent math at half the work. After the -i/(2n) normalization inside kinetic_energy_matrices_for_euler_lagrange!, the full complex response splits cleanly:
kwmat← fwmm half (Fortran rex=0, imx=1 pass)ktmat← ftmm half (Fortran rex=1, imx=0 pass)
Each half is complex (not pure real / pure imag), matching Fortran's two independent integration passes at torque.F90:842-847. Per-surface matrix dumps confirm element-by-element agreement with Fortran fourfit.F:1080-1082 (kwmat_l, ktmat_l). This is the Fortran convention required by the adjoint combinations kwmat ± ktmat in ForceFreeStates/Kinetic.jl / Fortran dcon/sing.f:967-1075 for non-Hermitian Bk, Ck, E_k.
Arguments
kwmat::Array{ComplexF64,3}: Output (mpert×mpert×6), fwmm half, zeroed on entryktmat::Array{ComplexF64,3}: Output (mpert×mpert×6), ftmm half, zeroed on entrypsi, n, l, zi, mi, wdfac, divxfac, electron: Same astpsi!(divxfac unused on the matrix path — retained for call-site compatibility)equil: PlasmaEquilibriumintr::KineticForcesInternal: Internal state with mode indexing, geometric matrices (smats/tmats/xmats/ymats/zmats), and per-surface θ-grid bufferskinetic_profiles::Equilibrium.KineticProfileSplines: Named kinetic-profile splines
Reference: [Logan et al., Phys. Plasmas 20, 122507 (2013)]
GeneralizedPerturbedEquilibrium.KineticForces.compute_torque_all_methods! — Method
compute_torque_all_methods!(state::KineticForcesState, intr::KineticForcesInternal,
ctrl::KineticForcesControl, equil, kinetic_profiles)Calculate torque/energy for all enabled methods. For each method, integrates over flux surfaces using adaptive QuadGK quadrature via integrate_psi_quadgk. For multi-n calculations, loops over toroidal mode numbers and assembles block-diagonal kinetic matrices.
Arguments
state::KineticForcesState: Accumulates results for all methodsintr::KineticForcesInternal: Internal state with equilibrium datactrl::KineticForcesControl: Control parameters specifying which methods to runequil: PlasmaEquilibrium with 2D interpolantskinetic_profiles::Equilibrium.KineticProfileSplines: Named kinetic-profile splines
GeneralizedPerturbedEquilibrium.KineticForces.energy_integrand_scalar — Method
energy_integrand_scalar(x::Float64, p::EnergyParams) → ComplexF64Evaluate the physical energy integrand N(x)·exp(-x)/denom(x) at normalized energy x = E/T. Implements [Logan, Park, et al., Phys. Plasmas, 2013] Eq. (8).
GeneralizedPerturbedEquilibrium.KineticForces.evaluate_energy_integrand — Method
evaluate_energy_integrand(x_grid; wn, wt, we, wd, wb, nuk, leff, n,
nutype="harmonic", f0type="maxwellian",
nufac=1.0, ximag=0.0, qt=false) → Vector{ComplexF64}Diagnostic convenience: evaluate the physical x-space energy integrand N(x)·exp(-x)/denom(x) at specified x = E/T values. Returns the integrand value (not the integral) at each point in x_grid. Useful for plotting the energy integrand shape and verifying kinetic resonance resolution.
Example
x = 10 .^ range(-2, stop=2, length=500)
f = KineticForces.evaluate_energy_integrand(x; wn=1e3, wt=2e3, we=5e4,
wd=1e2, wb=3e4, nuk=1e3, leff=1.0, n=1)
plot(x, real.(f); xscale=:log10, xlabel="x = E/T", ylabel="Re(integrand)")GeneralizedPerturbedEquilibrium.KineticForces.find_resonance_energies — Method
find_resonance_energies(leff, wb, n, we, wd) → Vector{Float64}Real positive energies x_res where the resonance condition vanishes:
Ω(x) = leff·wb·√x + n·(we + wd·x) = 0With s = √x this is the quadratic n·wd·s² + leff·wb·s + n·we = 0. Returns the x = s² values for the positive real roots (the locations of the resonance poles of the energy integrand).
GeneralizedPerturbedEquilibrium.KineticForces.find_sign_change_roots — Method
find_sign_change_roots(f, grid) → Vector{Float64}Locate the zeros of a callable f by scanning consecutive grid nodes for strict sign changes (f(x[i])·f(x[i+1]) < 0) and refining each bracket with Roots.Brent. Returns the refined roots in grid order (empty if f never changes sign; a node value of exactly zero is not treated as a crossing).
Single source of truth for the scan-then-Brent idiom — used for dB/dθ extrema in the bounce averaging and for kinetic-resonance surfaces in the ψ quadrature paneling.
GeneralizedPerturbedEquilibrium.KineticForces.integrate_energy — Method
integrate_energy(wn, wt, we, wd, wb, nuk, ell, leff, n, psi, lambda, method;
nutype="harmonic", f0type="maxwellian", nufac=1.0,
ximag=0.0, qt=false, atol=1e-7, rtol=1e-5) → ComplexF64Integrate the kinetic resonance operator over normalized energy x = E/T.
The integral ∫₀^∞ N(x)·exp(-x)/denom(x) dx is evaluated in real x-space over [0, X_ENERGY_MAX] (the integrand and its poles decay as x^p·exp(-x), so the tail there is far below any tolerance) via _integrate_energy_resonant. Each resonance pole (root of Ω(x) = leff·wb·√x + n·(we + wd·x), shifted off the real axis by collisions to xpole = xres - i·ν/Ω′) is removed by subtracting its singular part R/(x - xpole) and adding back the analytic principal-value + residue. A single formula handles all collisionalities: the collisionless case (ν ≡ 0) is the exact ν→0 limit, with its real-axis pole resolved analytically (see `integrateenergyresonant`).
Collision operator types (nutype): "zero", "small", "krook", "harmonic". Distribution function types (f0type): "maxwellian", "jkp", "cgl".
ximag is accepted for backward compatibility but no longer used — resonance poles are now handled analytically rather than by contour deformation.
Returns
ComplexF64: energy integral value
GeneralizedPerturbedEquilibrium.KineticForces.integrate_pitch_gar_quadgk — Method
integrate_pitch_gar_quadgk(wn, wt, we, nuk, bobmax, epsr, q, fbnce, fbnce_norm,
nqty, ell, n, rex, imx, psi, method; ...) → Vector{ComplexF64}Integrate the kinetic resonance operator over pitch angle λ using adaptive Gauss-Kronrod quadrature. Uses QuadGK.quadgk! with an in-place ComplexF64 kernel buffer.
The fbnce interpolant returns [ωb, ωd, f₁, f₂, ...] at each λ, where:
- f₁ = ωb|δJ|²/ro² (scalar torque)
- f₂:end = ωb·Wouterproducts/ro² (kinetic matrix elements, if present)
Splits the domain at the trapped/passing boundary so Gauss-Kronrod resolves the kink in leff = ell + n*q (circulating) → ell (trapped). One quadgk! call writes all nqty complex quantities per λ-evaluation.
Returns
Vector{ComplexF64}of length nqty: integrated pitch-angle results
GeneralizedPerturbedEquilibrium.KineticForces.integrate_pitch_gar_quadgk_wt — Method
integrate_pitch_gar_quadgk_wt(wn, wt, we, nuk, bobmax, epsr, q, fbnce, fbnce_norm,
nqty, ell, n, psi, method; ...) → Vector{ComplexF64}Dual-output variant for the kinetic-matrix path. Emits both the wmm half (rex=0, imx=1 → Fortran kwmat) and the tmm half (rex=1, imx=0 → Fortran ktmat) in a single pitch integration, sharing one energy integration per (λ, E).
Returns a length-2*nqty packed buffer: [wmm | tmm]. The two halves each reproduce Fortran's independent-pass result at Fortran's element-by-element convention (verified via matrix-dump comparison vs Fortran dcon/fourfit.F kwmat_l/ktmat_l). Downstream kwmat ± ktmat combinations in ForceFreeStates/Kinetic.jl then reproduce sing.f:967-1075 exactly for the non-Hermitian Bk, Ck, E_k diagonals.
GeneralizedPerturbedEquilibrium.KineticForces.integrate_psi_quadgk — Method
integrate_psi_quadgk(n, nl, zi, mi, wdfac, divxfac, electron, method,
equil, intr, ctrl, kinetic_profiles; psi_min, psi_max) → NamedTupleIntegrate torque over ψ using adaptive Gauss-Kronrod quadrature with QuadGK.BatchIntegrand. Every integrand evaluation is logged, giving a diagnostic T(ψ) profile at no extra cost (the values are computed anyway — we just keep them).
Returns
NamedTuple with:
total::ComplexF64: Total integrated torquetorque_profile: NamedTuple of (psi, dtdpsi, t_cumulative) from evaluation pointsmatrix_integrated: Trapezoidal-integrated mpert×mpert×6 matrix (if matrix method)psi_nsteps::Int: Number of integrand evaluationspsi_quad_error::Float64: Quadrature error estimate for the total torquepanel_psis::Vector{Float64}: Quadrature panel boundaries actually used (bounds + interior resonant surfaces)resonance_psis::Vector{Float64}: Located kinetic-resonance ψ surfaces (Ω_ℓ(x=1)=0), for diagnostics/plotting
The integral is paneled at the rational-surface ψ locations (intr.sing_psis) and capped at ctrl.maxevals_psi evaluations; a warning is emitted if the quadrature fails to reach ctrl.rtol_psi/ctrl.atol_psi or if a nonzero atol_psi dominates termination.
GeneralizedPerturbedEquilibrium.KineticForces.kinetic_energy_matrices_for_euler_lagrange! — Method
kinetic_energy_matrices_for_euler_lagrange!(kwmat, ktmat, state, psi, n, l, wdfac, intr;
kwargs...) → nothingCompute the six kinetic Euler-Lagrange coefficient matrices of Logan 2015 Eqs 7.30–7.35 (Ak, Bk, Ck, Dk, Ek, Hk) at a single (ψ, n, ℓ) and write them into pre-allocated kwmat[mpert, mpert, 6] (fwmm half, Fortran rex=0, imx=1) and ktmat[mpert, mpert, 6] (ftmm half, rex=1, imx=0).
Matrix-only path (no scalar torque slot in the pitch-angle buffer), so nqty = mpert²·6 instead of 1 + mpert²·6.
Uses integrate_pitch_gar_quadgk_wt to emit both halves from a single energy integration per (λ, E), reproducing Fortran's two-pass semantics (torque.F90:842-847). Per-surface matrix dumps confirm element-by-element match against Fortran fourfit.F:1080-1082 (kwmat_l, ktmat_l).
For the Hermitian-outer-product blocks A/D/H stored as upper-triangles, the mirror rule differs between halves: kwmat[j,i] = conj(kwmat[i,j]) — Hermitian (Sw pure imaginary) ktmat[j,i] = -conj(ktmat[i,j]) — anti-Hermitian (St pure real) Derivation: conj(S_w) = -S_w vs conj(S_t) = S_t, combined with conj(factor) = -factor (factor = -i/(2n)). These mirrors recover Fortran's independent-slot computation at the mirrored (j,i) positions.
GeneralizedPerturbedEquilibrium.KineticForces.kinetic_resonance_psi_nodes — Method
kinetic_resonance_psi_nodes(kinetic_profiles, equil; n, nl, zi=1, mi=2, electron=false, wdfac=1.0, xeval=2.5) → Vector{Float64}ψ_N locations of kinetic resonance surfaces, for use as ψ-quadrature panel boundaries (and, per the kinetic-aware grid-packing plan, as mandatory equilibrium knots).
Locates the zeros of the trapped-branch (leff = ℓ) resonance denominator Ω(x) = leff·ω_b·√x + n·(ω_E + ω_d·x) at energy x = xeval, for every bounce harmonic ℓ ∈ −nl:nl — this is where the energy-space resonance sweeps through the drive-weighted bulk and the NTV torque density peaks (Logan & Park, Phys. Plasmas 20, 122507 (2013), §IV–V). xeval defaults to 2.5, the peak of the Maxwellian-weighted drive x^2.5·e^−x; on the DIII-D case those nodes sit ~3× closer to the measured dT/dψ spikes than the thermal x=1 estimate. The ℓ = 0 node is the ω_d-shifted ExB (superbanana-plateau) resonance.
The frequencies use the pitch-averaged large-aspect-ratio closed forms of the rlar method (tpsi! in Torque.jl / Fortran pentrc torque.F90): ω_b = (π/4)·√(ε/2)·wtran and ω_d = q·T_s/(2·ε·R₀²·Z·e·B₀)·wdfac, with ε = ⟨r⟩/⟨R⟩ clamped away from the axis where the estimate degenerates. Panel placement only needs ~peak-width accuracy, so these cheap estimates (single spline evaluations) are sufficient and no bounce averaging is performed.
GeneralizedPerturbedEquilibrium.KineticForces.method_kind — Method
method_kind(name) -> SymbolReturn the dispatch routing tag (:gar, :fcgl, :rlar, :clar) for the NTV method name, looked up from [METHOD_REGISTRY]. Errors on an unknown method.
GeneralizedPerturbedEquilibrium.KineticForces.nqty_matrix — Method
Number of packed complex entries per λ for the 6 kinetic matrices.
GeneralizedPerturbedEquilibrium.KineticForces.powspace — Method
powspace(xmin, xmax, pow, num, endpoints) → (points, weights)Generate a grid with power-law concentration near endpoints. Port of Fortran powspace_sub from equil/grid.f90.
Arguments
xmin, xmax: Grid boundspow::Int: Power of grid concentration (higher = more refined near edges)num::Int: Number of grid pointsendpoints::String: Where to concentrate: "lower", "upper", or "both"
Returns
points::Vector{Float64}: Grid point locationsweights::Vector{Float64}: Derivatives dx/dnorm (integration weights)
GeneralizedPerturbedEquilibrium.KineticForces.print_summary — Method
print_summary(state::KineticForcesState; verbose::Bool=false)Print a summary of KineticForces results to stdout.
Arguments
state::KineticForcesState: Accumulated computation resultsverbose::Bool: Print detailed per-surface results
GeneralizedPerturbedEquilibrium.KineticForces.psi_panel_points — Method
psi_panel_points(interior, x0, xout) → Vector{Float64}Build the ψ-quadrature node list [x0, interior points strictly inside (x0, xout), xout]. interior is the raw union of resonant-surface locations (rational surfaces ∪ kinetic resonances); this function owns the ordering: sort, drop near-duplicates (closer than PANEL_MERGE_ATOL, e.g. a kinetic resonance coinciding with a rational), and drop points within PANEL_MERGE_ATOL of a bound to avoid degenerate panels. Paneling the integral at these surfaces puts the resonant torque-density peaks (reg_spot/collisionally broadened, but narrow in ψ) on Gauss-Kronrod interval endpoints, which the rule handles natively instead of hunting them by adaptive bisection.
GeneralizedPerturbedEquilibrium.KineticForces.set_perturbation_data! — Method
set_perturbation_data!(kf_intr, pe_state, ffs, equil, metric)Populate perturbation data from PerturbedEquilibriumState into KineticForcesInternal.
Builds three interpolant sets from PE Clebsch displacements:
xs_m— [ξ^ψ, ∂ξ^ψ/∂ψ, ξ^α] CubicSeriesInterpolants over ψdbob_m— δB/B Fourier modes via JBB deweighting (Fortran set_peq)divx_m— ∇·ξ⊥ Fourier modes via JBB deweighting
The JBB deweighting algorithm (Fortran pentrc/inputs.f90:828-868):
- Apply geometric matrices S,T,X,Y,Z in m-space
- Inverse DFT to θ-space
- Divide by J·B² at each θ
- Forward DFT back to m-space
GeneralizedPerturbedEquilibrium.KineticForces.tpsi! — Method
tpsi!(tpsi_var, psi, n, l, zi, mi, wdfac, divxfac, electron, method, equil, intr,
kinetic_profiles; op_wmats=nothing)Toroidal torque resulting from nonambipolar transport in perturbed equilibrium. Imaginary component is proportional to the kinetic energy Im(T) = 2ndW_k.
Arguments
tpsi_var: Output complex torque valuepsi::Float64: Normalized poloidal fluxn::Int: Toroidal mode numberl::Int: Bounce harmonic numberzi::Int: Ion charge in fundamental units (e)mi::Int: Ion mass (units of proton mass)wdfac::Float64: Drift factordivxfac::Float64: Divergence factorelectron::Bool: Calculate quantities for electrons (zi,mi ignored)method::String: Integration method (RLAR, CLAR, *GAR, *TMM, *WMM, *KMM) where * = F,T,P for full,trapped,passingequil: PlasmaEquilibrium with 2D interpolants and named profile/geometry splinesintr::KineticForcesInternal: Internal state with mode indexing and perturbation splineskinetic_profiles::Equilibrium.KineticProfileSplines: Named kinetic-profile splines (ni, ne, Ti, Te, ωE, νi, ν_e) loaded fromkinetic.dat
Optional Arguments
op_wmats::Array{ComplexF64,3}: Store ForceFreeStates matrix elements
Returns
ComplexF64: Toroidal torque due to nonambipolar transport
GeneralizedPerturbedEquilibrium.KineticForces.write_integration_records! — Method
write_integration_records!(mg::HDF5.Group, records::Vector{EnergyIntegrationResult})Write variable-length integration trajectory records using offset-indexed concatenated arrays. This is the standard HDF5 ragged array pattern for storing variable-length data.
Arguments
mg::HDF5.Group: HDF5 group for this methodrecords::Vector{EnergyIntegrationResult}: Integration records to write
GeneralizedPerturbedEquilibrium.KineticForces.write_to_hdf5! — Method
write_to_hdf5!(h5file::HDF5.File, state::KineticForcesState; dVdpsi_spline=nothing,
species_label=nothing)Write KineticForces results to the "KineticForces" group in gpec.h5.
Arguments
h5file::HDF5.File: Open HDF5 file handlestate::KineticForcesState: Accumulated computation resultsdVdpsi_spline: Optional dV/dψN profile interpolant; when given, dV/dψN is written at the quadrature points so the torque density dT/dV = (dT/dψ)/(dV/dψ) is directly availablespecies_label:nothingwrites the run total toKineticForces/<method>/; a label (e.g."ion_z1_m2","electron") writes one species' contribution toKineticForces/PerSpecies/<label>/<method>/. A multi-species run calls this once per species and once for the summed total, so the group is opened-or-created each time.