potential/backend: anchor construction-time scalar phi to float64 in EllipsoidalPotential#989
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## feat/backends #989 +/- ##
===============================================
Coverage 99.93% 99.93%
===============================================
Files 254 254
Lines 39820 40276 +456
Branches 837 837
===============================================
+ Hits 39795 40251 +456
Misses 25 25 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…EllipsoidalPotential During __init__, normalize() calls Rforce(1., 0.) with plain python scalars before _backend_compatible is set, so the @potential_physical_input boundary does not coerce them. Under a forced non-numpy backend _anchor_phi then saw a dtype-less scalar R and fell back to xp.asarray(phi, dtype=None) -- which on torch (whose CI jobs keep the float32 default, unlike jax's x64) produced a float32 phi, dropping the computed _amp to float32 and silently losing precision in every later float64-coordinate evaluation. Anchor the scalar phi to xp.float64 (galpy's interior precision) when R carries no dtype. Fixes the 6 oblate/prolate/triaxial Hernquist & Jaffe amp_mult_divide torch failures (residuals ~1e-7, float32-magnitude). The numpy path stays byte-identical (the `xp is numpy` guard short-circuits first); genuine float32 R inputs still keep float32 (the exit-cast policy is preserved); autodiff w.r.t. a backend `phi` array is untouched (is_backend_array short-circuits). Adds a forced-backend construction test pinning the normalize() _amp to float64 (reproduces the CI torch float32-default condition). Note: this was investigated under "stabilize the GL sum order", but the Gauss-Legendre quadrature was provably not the cause (vectorizing _potInt left the tests failing) -- the fix is a one-spot dtype anchor, hence the branch was renamed off "gl-stabilize". Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
f0e31f2 to
4effc36
Compare
All-backend test status (jax / torch)Commit Green is achieved via the checked-in xfail-ledger ( Overall: jax: 1056 passed · 266 xfail · 725 deferred | torch: 833 passed · 1210 xfail · 1 deferred Ledger size: 2357 entries (jax=284, torch=2073).
Per-shard counts
|
What
Anchor a construction-time python-scalar
phitofloat64inEllipsoidalPotential._anchor_phi.During
__init__,normalize()callsRforce(1., 0.)with plain python scalars before_backend_compatibleis set, so the@potential_physical_inputboundary does not coerce them. Under a forced non-numpy backend_anchor_phithen saw a dtype-less scalarRand fell back toxp.asarray(phi, dtype=None)— which on torch (whose CI jobs keep the float32 default, unlike jax's x64) produced a float32phi, dropping the computed_ampto float32 and silently losing precision in every later float64-coordinate evaluation.The fix: when
Rcarries no dtype, anchorphitoxp.float64(galpy's interior precision).Result
Fixes the 6 oblate/prolate/triaxial Hernquist & Jaffe
test_amp_mult_dividetorch failures (baseline residuals ~1e-7, i.e. float32-magnitude, vs the 1e-10 tolerance). They flip xfail→XPASS (ledger stays green viastrict=False; the stale entries are pruned in the separate ledger-regen PR, not here).Safety (adversarially reviewed, CPU-forced)
xp is numpyguard short-circuits before any new code; verified max abs diff0.0over a full evaluate/forces/2nd-deriv grid.Rarray still anchorsphito float32 (thedtype is Nonebranch is only taken for a dtype-less scalar).phiuntouched — a real backendphiarray returns via theis_backend_arrayshort-circuit (no cast/detach).test_backend_ellipsoidal.py(687 passed).test_construct_normalize_amp_is_float64reproduces the CI torch float32-default condition and pins the construction-time_ampto float64 (fails on baseline, passes with the fix). It also covers the new branch under the coverage-uploadingtest_backend*shard.Notes / divergences
_potIntleft the tests failing). The fix is a one-spot dtype anchor — the branch was renamed offgl-stabilizeto reflect that._backend_compatible=Trueafternormalize(), unlike the ~10 potentials backend: central coordinate coercion — fix systemic torch scalar-input rejection #960 reordered (only TriaxialNFW was reordered). Reordering them is the root-cause cleanup; this guard is the correct, byte-identical, independently-defensible boundary fix and keeps the branch covered.triaxialLogarithmicHaloPotential/mockRotatedAndTiltedTriaxialLogHaloPotentialtorch entries are out of scope — LogarithmicHaloPotential is not an EllipsoidalPotential subclass (already backend: central coordinate coercion — fix systemic torch scalar-input rejection #960-reordered; any residual float32 there is a different mechanism).🤖 Generated with Claude Code