PE - BUGFIX! - Regularize stored xi_s in kinetic Clebsch displacements - #407
PE - BUGFIX! - Regularize stored xi_s in kinetic Clebsch displacements#407jhalpern30 wants to merge 1 commit 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.
|
This pull request is missing a reviewer. If you are not ready to name them, mark this pull request as a draft. |
|
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 |
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 @ f1cd7b7)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
Run at
f1cd7b7bagainst the stack parentrefactor/freeze-fourfitvars(bc15b720):gal_resistive_pewas also run: it extracts N/A for all 8 quantities on both refs, i.e. it isalready broken on the parent branch, independent of this change. Flagged below.
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
refactor/freeze-fourfitvars(the head branch of #383) rather thandevelop, because thefix touches code that #383 renames. Not managed as a stack entry — merge this into
refactor/freeze-fourfitvarsdirectly; #383's own path onward is handled separately.Note for whoever merges: #383's stated claim is that it is numerically inert. Merging this into that
branch puts a numerics-moving change (kinetic PE only — see the table above) inside it, so that
claim will no longer hold for the combined branch. Ideal-path results stay bit-identical either way.