PerturbedEquilibrium - BUGFIX! - Regularize stored xi_s in kinetic Clebsch displacements - #407
PerturbedEquilibrium - BUGFIX! - Regularize stored xi_s in kinetic Clebsch displacements#407jhalpern30 wants to merge 3 commits into
Conversation
compute_clebsch_displacements factorized the active A with cholesky!(Hermitian(amat, :L)). In a kinetic run that A is the kinetic A (amat + kwmat[:,:,1] + ktmat[:,:,1]), which is non-Hermitian, so the upper triangle was silently discarded. The run completed without error and returned wrong regularized quantities. Mirror Fortran gpeq.f:117-123: under kin_flag GPEC scales the stored xi_s by singfac^2/(singfac^2 + reg_spot^2) and inverts nothing. It never holds a kinetic A here — idcon_matrix rebuilds A analytically from the metric tensors, ideal-only. Our stored xi_s is the right analogue; compute_node_xi_s! already builds it with the correct LU. The ideal branch is unchanged and now reads mats.ideal explicitly. Kinetic PE results move: xi_clebsch_alpha by 18%, and Jb_theta_reg/Jb_zeta_reg and Jxi_theta_reg/Jxi_zeta_reg by order their own magnitude. Unregularized quantities and reg_spot = 0 are unaffected. The branch had never executed in any example or regression case, so add [ForcingTerms]/[PerturbedEquilibrium] to Solovev_kinetic_calculated_example and track xi_clebsch_alpha, dxi_clebsch_psidpsi and Jb_theta_reg in its regression case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
logan-nc
left a comment
There was a problem hiding this comment.
huh. The more we dig the more skeletons we find, eh?
This diff looks fine and clean to me. Assuming the description is correct in that this matches the fortran (I don't have time to confirm), then yeah we should just merge and match fortran for now. I suppose an issue should be raised asking someone to do the task of benchmarking the impact of correctly implementing the full factorization approach for kinetic cases. Issues are piling up, but that is no reason not to record things like this there.
|
Great, thanks for looking into it. I have Claude looking into benchmarking the kinetic (and also potential ideal issue it flagged) as a side task and will confirm that before I merge anything |
The metadata job read only requested_reviewers, which holds pending review requests. GitHub removes a reviewer from that list as soon as they submit a review, so the check went quiet while nobody had reviewed and fired once somebody did -- nagging precisely the pull requests furthest along. Treat a submitted review from anyone other than the author as evidence that a reviewer was named. Assignment alone still suffices; the reviewer never has to act. Verified against all open pull requests: silences #390, #391, #392, #407, leaves every pending-request PR untouched, and still nags the nine with nobody named. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dh1NCejnd3fYMmcRKoQRcG
…eq.f kin_flag analog) The Clebsch xi_alpha fed to the NTV kernel was re-solved from the IDEAL A,B,C even in kinetic runs, inconsistent with the self-consistent kinetic tangential response exactly where resonant layers live. Mirror Fortran: scale the stored xi_s by the singfac regularization factor and invert nothing (analog of the #407 fix, adapted to the FourFitVars API; kinetic-ness detected via populated kwmats pending proper flag threading). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzbLFQKyuRE5DYZmLokKmk
…eq.f kin_flag analog) The Clebsch xi_alpha fed to the NTV kernel was re-solved from the IDEAL A,B,C even in kinetic runs, inconsistent with the self-consistent kinetic tangential response exactly where resonant layers live. Mirror Fortran: scale the stored xi_s by the singfac regularization factor and invert nothing (analog of the #407 fix, adapted to the FourFitVars API; kinetic-ness detected via populated kwmats pending proper flag threading). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzbLFQKyuRE5DYZmLokKmk
…eq.f kin_flag analog) The Clebsch xi_alpha fed to the NTV kernel was re-solved from the IDEAL A,B,C even in kinetic runs, inconsistent with the self-consistent kinetic tangential response exactly where resonant layers live. Mirror Fortran: scale the stored xi_s by the singfac regularization factor and invert nothing (analog of the #407 fix, adapted to the FourFitVars API; kinetic-ness detected via populated kwmats pending proper flag threading). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzbLFQKyuRE5DYZmLokKmk
…eq.f kin_flag analog) The Clebsch xi_alpha fed to the NTV kernel was re-solved from the IDEAL A,B,C even in kinetic runs, inconsistent with the self-consistent kinetic tangential response exactly where resonant layers live. Mirror Fortran: scale the stored xi_s by the singfac regularization factor and invert nothing (analog of the #407 fix, adapted to the FourFitVars API; kinetic-ness detected via populated kwmats pending proper flag threading). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzbLFQKyuRE5DYZmLokKmk
…eq.f kin_flag analog) The Clebsch xi_alpha fed to the NTV kernel was re-solved from the IDEAL A,B,C even in kinetic runs, inconsistent with the self-consistent kinetic tangential response exactly where resonant layers live. Mirror Fortran: scale the stored xi_s by the singfac regularization factor and invert nothing (analog of the #407 fix, adapted to the FourFitVars API; kinetic-ness detected via populated kwmats pending proper flag threading). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzbLFQKyuRE5DYZmLokKmk
…eq.f kin_flag analog) The Clebsch xi_alpha fed to the NTV kernel was re-solved from the IDEAL A,B,C even in kinetic runs, inconsistent with the self-consistent kinetic tangential response exactly where resonant layers live. Mirror Fortran: scale the stored xi_s by the singfac regularization factor and invert nothing (analog of the #407 fix, adapted to the FourFitVars API; kinetic-ness detected via populated kwmats pending proper flag threading). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzbLFQKyuRE5DYZmLokKmk
…eq.f kin_flag analog) The Clebsch xi_alpha fed to the NTV kernel was re-solved from the IDEAL A,B,C even in kinetic runs, inconsistent with the self-consistent kinetic tangential response exactly where resonant layers live. Mirror Fortran: scale the stored xi_s by the singfac regularization factor and invert nothing (analog of the #407 fix, adapted to the FourFitVars API; kinetic-ness detected via populated kwmats pending proper flag threading). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzbLFQKyuRE5DYZmLokKmk
|
@jhalpern30 @logan-nc how did the harness for kinetic return everything unchanged if it was a bug that changes what the A matrix is for kinetic? Shouldn't that affect delta W? |
The bug in this PR only affected the FieldReconstruction files, so its only the compute_clebsch_displacements outputs that mattered |
|
Ohhh, I understand now. OK, is it ready for merge? It is reviewed so we should get it in if so. Looks like just a toml conflicts. |
|
Yeah it was just waiting on the rest of the stack and then I didn't have a chance to fix the merge conflicts yesterday. I'll just note that I couldn't get an exact benchmark between the Fortran and Julia on this - I had Claude look into it but it came back saying it couldn't get the ideal case to agree so I left it there. I basically stopped there so it doesn't necessarily mean its broken though |
One conflict, in examples/Solovev_kinetic_calculated_example/gpec.toml: both sides rewrote the header comment. Took develop's description (the deck is now a 50/50 D-T mix accumulated over the multi-ion species loop) and appended this branch's note that it is the only deck running [PerturbedEquilibrium] on a kinetic solve. Dropped the stale et[1] value that develop had already removed for that reason. The [ForcingTerms]/[PerturbedEquilibrium] sections, the three tracked PE quantities in the regression case, and FieldReconstruction.jl carried over unchanged.
|
Hm. I've done a couple "check that julia gets the same result" runs for an ITPEA benchmarking exercise recently and it was coming up ok... I'd be interested to a figure of how this is changing the clebsch xi profiles fed to KF. You could over plot ideal, kinetic pre-fix and kinetic post-fix even if there is no fortran comparison... but I don't see WHY there wouldn't be a fortran comparison if you toggle the As a reminder, I recommend this in your |
…ment the Cholesky invariant The three PE quantities added to solovev_kinetic_calculated used noise_threshold = 1e-12 on norms of order 1e-4 to 1e-3, i.e. ~1e-9 relative and four to six orders tighter than every other norm quantity in the harness (1e-6 or 1e-8). Re-pin them to 1e-8. The q0/q95 scalars in the same case keep 1e-12. Restore the invariant note at the cholesky! site: the guard that makes the Hermitian factorization legal is now an early return ~50 lines above, so the call site no longer said why it is safe.
|
@logan-nc — both of your asks, answered. Short version: you were right that the Fortran comparison was available, and the 1. Fortran parity, verified at the sourceYou don't actually need the run comparison to answer this one — the Fortran source is unambiguous.
IF (reg_flag) THEN
xmp1_mn=xsp1_mn*(singfac**2/(singfac**2+reg_spot**2))
IF (kin_flag) THEN
xms_mn=xss_mn*(singfac**2/(singfac**2+reg_spot**2))
ELSE
xms_mn=-MATMUL(bmat,xmp1_mn)-MATMUL(cmat,xsp_mn)
ENDIFUnder And amat(ipert,jpert)=twopi**2*(nn*nn*g22(dm)
$ +nn*(m1+m2)*g23(dm)+m1*m2*g33(dm))with no kinetic term and no This also confirms the asymmetry is faithful, not an artifact: Fortran's ideal branch feeds the regularized One nuance worth recording: Fortran gates all of this on 2. The figure — and the answer is more specific than "18%"Overplotted ideal / kinetic pre-fix / kinetic post-fix,
All-m L2 change: 13.6%. So the buggy Cholesky path was inflating the non-resonant sideband structure of the ξ^α spectrum fed to PENTRC, while leaving the resonant harmonics essentially intact. Since NTV consumes the whole spectrum, that is where the downstream consequence lives — worth knowing if you are looking at torque rather than at the resonant surfaces. Also notable: the pre-fix run does not throw Caveat, stated plainly: the ideal curve sits ~71% away from the kinetic one regardless of the fix, because 3. Why the earlier Fortran benchmark didn't agreeWorth recording, since it was left as "couldn't get the ideal case to agree." Diffing the two decks turned up several mismatches, of which two would each independently wreck the comparison:
Plus An aligned deck is prepared, but the run comparison is not finished — kinetic DCON was still integrating when I stopped. Given that item 1 answers the question at the source, I'd rather not block on it. Flagging one limit for whenever someone does run it: Fortran's kinetic matrices come from PENTRC ( Regression harness at |
|
@logan-nc I am getting a bit out of my depth here physics-wise - what do you think of the plots? My interpretation is that this fix is an actual bugfix, but doesn't always produce drastically different results. And that everything I can find indicates that this is faithful to the Fortran. It also pointed out differences between the Fortran and Julia examples, which I am not sure were intentional or not |
|
Isn't the solovev kinetic example pretty garbage for the sake of speeding the harness? I'd suggest using DIII-D for any benchmark. I also don't see fortran overlayed so it's pretty hard to determine which is "right". I am getting a bit confused by "So both branches mirror Fortran, not just the one being fixed." - Does this mean claude is overly proactive and it was all matching fortran fine to start with? Note that this is a bit a goose chase if we automatically turn reg_flag off for the (self regularizing) kinetic runs anyways... but I am surprised to see the profiles changing far from the rational (reg_spot should mean the "regularization" is only impacting a small region near the rational) - so that does indeed need to be chased down. |

Release note
xi_clebsch_alphaby 18%,Jb_theta_reg/Jb_zeta_regandJxi_theta_reg/Jxi_zeta_regby order their own magnitude. Ideal runs andreg_spot = 0are bit-identical. (harness @ 0fc6b4a)In a kinetic run the Clebsch displacements were computed by Cholesky-factorizing the kinetic A matrix, which is not Hermitian — the upper triangle was silently discarded and the run completed without error, returning wrong regularized displacements and fields. Kinetic PerturbedEquilibrium results produced before this fix should be treated as suspect.
Regression report
Ran at
0fc6b4a2, 8 cases,origin/develop(d7e6e6fe) vs working tree:136 tracked quantities, 0 changed.
The 3 N/A are the PE quantities this PR adds. They have no baseline because
develop's copy ofthe deck has no
[PerturbedEquilibrium]section, so nothing is produced to compare against.Values on this branch:
solovev_kinetic_nuzeroreturning 14/14 unchanged is the direct check that adding[PerturbedEquilibrium]to the shared deck does not perturb that case's tracked quantities — itonly costs it the extra PE runtime.
Their
noise_thresholdwas re-pinned1e-12->1e-8: every otherextract = "norm"quantity inthe harness uses
1e-6or1e-8, and1e-12on norms of order1e-4is ~1e-9relative, tightenough to trip spuriously.
Notes for reviewers
What was broken
compute_clebsch_displacementsfactorized the active A withcholesky!(Hermitian(amat, :L)).In a kinetic run that A is the kinetic A —
amat + kwmat[:,:,1] + ktmat[:,:,1](
src/ForceFreeStates/Kinetic.jl:145) — which is non-Hermitian;Kinetic.jlitself factorizes itwith
lu, and Fortranfourfit.F:1153comments it! invert non-hermitian a matrix.Hermitian(amat, :L)discards the upper triangle, andcholesky!then presumes definiteness.It fails quietly. On the Solovev kinetic deck the unfixed code exits 0 — no
PosDefException—so this was silent corruption, not a latent crash. Pre- vs post-fix on that deck:
xi_clebsch_alphaJb_theta_reg/Jb_zeta_regJxi_theta_reg/Jxi_zeta_regxi_clebsch_psi,dxi_clebsch_psidpsi,xi_psiPre-existing, not introduced by the stack: on
developthe same line readffit.amats, which in akinetic run was already the kinetic-overwritten A.
The fix, and why not just swap in an LU
gpeq.f:117-123: underkin_flagGPEC applies the scalar regularizer directly to the storedxss_mnand inverts nothing. It never holds a kinetic A at this site at all —idcon_matrix(
idcon.f:773-777) rebuildsamatanalytically from the metric tensors, ideal-only. Our storedξ_s is the right analogue:
compute_node_xi_s!already builds it with the correct LU on thekinetic A.
Swapping
cholesky!forlu!and keeping the-A⁻¹(B·xmp1 + C·xsp)form would be a Julia-onlydeviation rather than a restoration, and the two are not equivalent at finite
reg_spot: theLU form regularizes
xsp1before B multiplies it and leavesC·xspunregularized, whereas Fortranregularizes the assembled ξ_s afterward. These do not commute; they agree only as
reg_spot → 0.@logan-nc — this is the main thing I want your read on. Is "regularize the assembled ξ_s" what
PENTRC should be consuming, or does the ordering matter for how you use these downstream?
Coverage
There was none. No example ran kinetic + PE, so this branch had never executed in any example or
regression case. Added
[ForcingTerms]/[PerturbedEquilibrium]+forcing.dattoSolovev_kinetic_calculated_exampleand three quantities to its regression case. Costs ~8 s there,and the same on
solovev_kinetic_nuzero, which reuses the deck.Second question for @logan-nc: happy with that, or would you rather have a separate deck to keep
those cases lean?
Out of scope — worth separate issues
cholesky!where Fortran useszhetrf(Bunch-Kaufman, Hermitianindefinite) while deliberately using
zpbtrfforfmatin the same routine — and has anoperator-facing error at
idcon.f:800: "zhetrf: amat singular at psi = ..., reducedelta_mband". Same answer when A is positive definite; a bare
PosDefExceptioninstead of thatactionable message when it is not.
gal_resistive_peextracts N/A for all quantities on both refs — already broken on the parentbranch.
Base branch
Targets
develop. Earlier revisions targetedrefactor/freeze-fourfitvars(the head branch of#383), because this fix touches code that #383 renames. #383 has since merged into
developasd7e6e6fe, sodevelopis now the correct base and the stack note no longer applies — includingthe caveat about placing a numerics-moving change inside a branch claimed to be numerically inert.