PENTRC: omega_D in the GAR branch carries a spurious major radius - #281
PENTRC: omega_D in the GAR branch carries a spurious major radius#281krystophny wants to merge 4 commits into
Conversation
energy_imaxis selects the integration contour inside xintgrnd -- cx = i*x on the imaginary-axis leg against cx = x + i*ximag on the real one -- and xintgrl_lsode flips it between its two legs (energy.f90:190, :223). It is per-integration state exactly like energy_wn, energy_wd, energy_leff and the rest, and it was the only one of them missing from the threadprivate list. Shared, one thread's imaginary leg silently switches another thread's integrand from a decaying exponential to an oscillation, so values from deep in the Maxwellian tail come back four orders of magnitude too large and the pitch integrand stops being a function of lambda: neighbouring lambda separated by 1e-8 differ by 130% on average. lambdaintgrl_lsode then shrinks its step without bound and aborts with 'too many steps in lambda required'. That is why the failure resisted every numerical explanation -- tolerances, xmax, ximag, nl, compiler, kinetic profiles: it does not depend on the numerics, it depends on the thread count. Decks that set the deprecated and ignored openmp_threads = 1 silently ran on every available core. Gate: the ITER case that previously aborted now runs unmodified and gives bit-identical pentrc_tgar_n3.out and pentrc_pgar_n3.out at 1 and 16 threads, all 29 columns, including the pitch-resolved netCDF. Note for reviewers: recorded integrations (op_record=.true.) additionally mutate the shared energy_record object without synchronization. Production recording is serial so this does not affect the fix above, and it is left alone here.
pentrc_interface aliased both xatol/xrtol and lambdaatol/lambdartol to the single atol_xlmda/rtol_xlmda pair. The pitch integral's integrand IS the energy integral, so this is a nested adaptive quadrature, and with one tolerance for both the outer integrator chases the inner one's quadrature error -- which is not a smooth function of lambda. LSODE then shrinks its step without bound. New pent_control entries atol_x/rtol_x carry the energy tolerances. Their default of -1 means 'derive from the pitch tolerances', as nested_tolerance_margin = 1e-2 times them. This DOES change numerical behaviour for every deck that does not set the new controls: the effective energy tolerances go from the formerly aliased 1e-6/1e-3 to 1e-8/1e-5, i.e. tighter. Decks can restore the old values explicitly by setting atol_x = 1e-6, rtol_x = 1e-3. Note that energy.f90's own module defaults are tighter still, 1e-12/1e-9; those were overridden by the aliasing before this change and remain overridden by the derived values after it, which is a separate question about what the right energy tolerance actually is. This is NOT what unblocked the previously-aborting ITER case; the OpenMP race on energy_imaxis was. It is a real defect in its own right and is kept separate. Dropped from an earlier version of this change: an exclusion of both endpoints of the RLAR trapped pitch space. lmdamin = max(1/(1+epsr), bo/bmax) and lmdamax = min(1/(1-epsr), bo/bmin) are degenerate only when the bo/b term wins; when the cylindrical epsr bound wins the endpoint has two regular bounce points, and excluding it unconditionally would shrink the physical interval and discard nonsingular contributions.
wdbar reused wbbar, which is omega_b/bhat and therefore carries one factor of ro that bhat takes back out again. dhat cancels only the ro**2 written explicitly, so the third ro survived and omega_D came out with units of m/s rather than 1/s. The operator itself is correct and is not touched: bspl%fsi(:,2) is exactly d/dPsi of the parallel action integrand at fixed lmda -- the 1 - 1.5*lmda*b/bo coefficient follows from sqrt(vpar) = vpar/sqrt(vpar) in the product rule -- so the bounce average is the canonical precession -(1/chrg)(dJ_par/dpsi_p)/tau_b = -4 pi (T/chrg) I2/I1, the 4 pi because chi1 = twopi*psio makes the tabulated derivatives d/dPsi_pol while the canonical momentum wants d/dpsi_p. Only the prefactor changes: ro, not ro*ro. Verified against an independently validated canonical operator on the same equilibrium, as the least-squares slope over all pitches on a surface (a per-point ratio is ill conditioned at the omega_D zero crossing): -6.494 before against the predicted -ro = -6.411, and -1.021 after against -1, over 7 surfaces. omega_b, built from the same bspl(:,1), agrees to better than 0.4% before and after and is untouched. The dimensional argument is what makes this a length rather than a missing 2 pi, which is only 2% away numerically: I2/I1 has units 1/Wb whatever normalisation b and J carry, since any constant rescaling of J*b cancels in the ratio, so (T/chrg) I2/I1 is already a frequency. Confirmed that no hidden normalization supplies the extra radius: rzphi stores physical R**2 and a physical-coordinate Jacobian, eqfun stores B in tesla, ro is in metres. A second witness is internal: omega_d_rlar in the same output file is wdhat = q T/(2 eps ro**2 chrg bo), dimensionally a frequency, reproducing to 8 digits, and it sits at 0.78x the canonical operator while the GAR bounce average sat at 8.26x it. SCOPE OF THE SIGN CLAIM. Only the magnitude is established here. The overall sign of the canonical form depends on whether the thin-orbit canonical momentum is +chrg*psi_p or -chrg*psi_p, and GPEC cannot settle that from its stored radial coordinate: EFIT input signs are normalized away in equil/read_eq.f and the native toroidal coordinate reaches the laboratory frame only through phi_CCW = -helicity*(2 pi zeta + dphi) in coil/field.F. This change removes one positive factor of ro and nothing else; no sign is altered, and the residual orientation question is independent of it. On the ITER case this moves the trapped superbanana resonance from x ~ 4.3 to x ~ 28, so TGAR torque falls from -11.8 to -0.5 N m and the balance becomes passing-dominated. Output stays bit-identical at 1 and 16 threads.
71e57fc to
1ad89ff
Compare
|
Rebased onto current Scope of the sign claim, corrected. The review is right that the PR asserted more than GPEC can support. The relation follows only if the thin-orbit canonical momentum contains So this PR establishes the magnitude and nothing about orientation. It removes one positive factor of What the review independently confirmed, having been asked specifically to refute it:
Also dropped from the stack below this one: the degenerate-endpoint change in the RLAR branch, which the review showed is unsound when the cylindrical |
The PR body was corrected to claim only a magnitude, since the familiar minus sign needs P_phi = +chrg*psi_p and GPEC does not establish that convention. The source comment still asserted the signed identity, which is the exact claim the body disclaims. No executable change.
|
Final stacked production revalidation (after #280, this PR, and the independent periodic-spline #284 fix): all four The clean production endpoints are:
These are executable-native cumulative GAR totals on the GPEC field. Publication remains magnitude-only until the manufactured complex-mode helicity gate closes. One-thread and 16-thread outputs for the repaired failing lane are bit-for-bit identical. |
|
@claude review this pr |
|
Claude finished @logan-nc's task in 2m 26s —— View job Reviewing PR #281
SummaryThis PR's own contribution (commits -wdbar = ro*ro*bo*wdfac*wbbar*2*(2-sigma)*bspl%fsi(bspl%mx,2)
+wdbar = ro*bo*wdfac*wbbar*2*(2-sigma)*bspl%fsi(bspl%mx,2)( Verification performed
Assessment
|
Stacked on #280 (
agent/pentrc-nested-quadrature-tolerances); please read that one first, since the ITER case cannot run at all without it.What this changes
One factor on one line in
pentrc/torque.F90, in the general-aspect-ratio bounce average:Why
The operator is correct and is not touched. With
vpar = 1 - lmda*b/bo, the secondbsplintegrand is exactlyd/dPsiof the first's action companion at fixedlmda:which is the line at
torque.F90:708. So withdl = J b dthetaandJ_par = oint m v_par dl, the bounce average is the canonical precessionthe
4 pibecausechi1 = twopi*psiomakes the tabulatedeqfun_fx(1)/chi1andrzphi_fx(4)/chi1**2derivatives with respect to the full poloidal flux, while the canonical momentum wantspsi_p = Psi_pol/(2 pi).The overall sign is a convention question and this PR does not settle it. The familiar form
omega_phi = -(1/chrg)(dJ_par/dpsi_p)/tau_bneedsP_phi = +chrg psi_p; withP_phi = -chrg psi_pthe sign reverses. GPEC cannot be read off as one or the other from here: EFIT input signs are normalised away inequil/read_eq.f:666-680, and the native toroidal coordinate is mapped to the laboratory angle withphi = -helicity*(2 pi zeta + dphi)incoil/field.F:169-190, withhelicitydeclared incoil.inrather than derived from the equilibrium. So what is established below is the magnitude, and with it the removal of exactly one positive factorro. The patch multiplies by a positive number and changes no sign; the sign question is tracked separately.The prefactor is not. Substituting the code's own definitions,
wbbarisomega_b/bhat, so it carries a factorrothatbhatremoves again. Reusing it insidewdbarimports thatroa third time, anddhatcancels only the two written explicitly.The surviving factor is a length, not a
2 pi. This matters becausero = 6.411 mand2 pi = 6.283are only 2% apart.I1is in metres sinceJ b dtheta = dl; inI2every term carries the sameJ*bproduct divided byPsi, soI2/I1has units1/Wbwhatever internal normalisationbandJcarry — any constant rescaling ofJ*bcancels in the ratio. WithT/chrgin volts andV/Wb = 1/s, the correct expression is already a frequency andomega_Das computed has units of m/s. No dimensionless constant can repair that.Verification
Against an independently validated canonical precession operator (checked against an integrated guiding-centre orbit) on the same equilibrium, read through a Boozer description rather than DCON's chart. Metric is the least-squares slope of
omega_Dagainst the reference over all pitches on a surface — a per-point ratio is ill conditioned whereomega_Dcrosses zero.-6.494-6.795to-6.087-ro = -6.411-1.021-1.063to-0.965-1.000The minus signs in the predicted column are the observed relative orientation of the two operators carried across, not a derived one — see the convention caveat above. What the measurement establishes is that the ratio's magnitude goes from
roto1.omega_b, built from the samebspl%fsi(:,1), agrees to within[0.9964, 1.0011]both before and after. That is the control: it pinsI1to SI metres, and with it the units ofI2/I1, and it shows the change is confined to the precession prefactor.Residual
1–2%is the difference between the two equilibrium descriptions for this operator, independently measured at0.6–5.3%.Supporting checks:
chi1is SI, not assumed to be. Integratingdpsi_pol/ds = |iota| Phi_edge/(2 pi)over the Boozer file givespsi_p(edge) = 11.8809 Wbagainst DCON'schi1/(2 pi) = 11.8828 Wb, agreeing to1.6e-4. Had the2 pisat elsewhere in the chain the prediction would have been2 pi ro = 40.3, which the measurement excludes.Lambdain[0.9064, 1.0773]atrho_tor = 0.251;botimes the Boozer chart's own[1/Bmax, 1/Bmin]is[0.9063, 1.0777].omega_d_rlariswdhat = q T/(2 eps ro**2 chrg bo), which is dimensionally a frequency and reproduces to 8 digits (591.1770263computed,5.91177020E+002written). It sits at0.78xthe canonical operator, while the GAR bounce average sat at8.26xit. The two are not the same quantity, so only the order of magnitude is meant — but the dimensionally correct estimator inside PENTRC lands where the reference does.Physical consequence on the ITER case
The superbanana resonance sits at
x = -omega_E/omega_D. Withomega_E ~ -6.4e3 rad/s, the correctedomega_D ~ 2.3e2puts it atx ~ 28, out in the Maxwellian tail, where an inflatedomega_Dhad put it atx ~ 4.3.Same deck, byte-identical
pentrc.in, only the binary differing, over0.2 <= rho_tor <= 0.8:-11.762 N m-0.493 N m-0.544 N m-0.917 N mThe trapped channel drops by
24xand the balance becomes passing-dominated, which is what the drift-kinetic codes we compare against report for this case.Gates
pentrc_tgar_n3.outandpentrc_pgar_n3.outbit-identical at 1 and 16 threads, so PENTRC: fix OpenMP data race on the energy contour flag (and a nested-quadrature tolerance defect) #280's race fix is undisturbed.omega_Dstill opposes the reference operator evaluated on a right-handed chart; that is a separate open question about GPEC's runtime helicity and is deliberately left alone here.Adjacent, reported and not changed
The reduced-large-aspect-ratio branch has its own dimensional oddities —
wbbarthere carriessqrt(bo), and unlike the GAR branchbhatis not divided byro(torque.F90:457-461). Our deck exercises TGAR/PGAR, so I have not touched RLAR; flagging it in case it is of interest.Charge-verified TC24 production revalidation (2026-07-26)
The corrected single-D campaign was rerun from Xingting's archive (SHA-256
655ffbf16e35f6218d1b40f58b2e2c4c16fbbcd8b1fce58c3adad515a7f17585) at this exact head,2e031ae675205ec9d69bb0298855155737f72579. It usesni=ne=supplied density, physicalTi/Te,mi=2,zi=1, and the pairedidcon_equil.outpsi_pol -> s_tormap.Over the exact
0.2 <= rho_tor <= 0.8window, the executable-native endpoints are:The channel sum reconstructs the native cumulative torque. These numbers supersede the earlier old-profile physical-consequence table as TC24 comparison evidence. No sign was transformed: the audit retains native signs, while publication remains magnitude-only until the exact runtime helicity and manufactured nonzero complex-mode torque/power gate close.