potential/backend: coerce scalar coords for backend-compatible scalar-only methods#990
Conversation
…-only methods The @check_potential_inputs_not_arrays scalar-only compute methods (e.g. RotateAndTiltWrapperPotential, DoubleExponentialDiskPotential) still do real backend arithmetic on their coordinates -- xp.isinf(R), xp.cos(phi), xp.sin(phi) -- which torch rejects on a plain python float under a forced backend (TypeError: isinf(): argument 'input' ... must be Tensor, not float). Coerce R, z, phi onto the active backend once, in the shared decorator, mirroring the potential_physical_input boundary -- but ONLY for _backend_compatible potentials, so an unmigrated scalar-only potential (AnyAxisymmetricRazorThinDiskPotential, whose internals are bare scipy.integrate.quad / numpy) keeps its python-float inputs and does not regress under a forced backend. t is left uncoerced (it may be a hashable cache key downstream). Flips the RotateAndTilt-wrapped test_amp_mult_divide torch entries (mockRotatedAndTiltedMWP14WrapperPotential[wInclination] standalone; the TriaxialLogHalo-wrapped one composes with #987's phi-default coercion). The numpy path is byte-identical (coerce_coords is an object-identical pass-through when xp is numpy; the gate skips it entirely for unmigrated potentials). Adds a forced-backend regression test (plain python floats -> decorator coerces, fails at the isinf line without the fix) and a gate-guard test (unmigrated AnyAxiRazorThin must stay on numpy under a forced backend). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## feat/backends #990 +/- ##
==============================================
Coverage 99.93% 99.93%
==============================================
Files 254 254
Lines 39833 39836 +3
Branches 834 840 +6
==============================================
+ Hits 39808 39811 +3
Misses 25 25 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
All-backend test status (jax / torch)Commit Green is achieved via the checked-in xfail-ledger ( Overall: jax: 1057 passed · 265 xfail · 725 deferred | torch: 883 passed · 1160 xfail · 1 deferred Ledger size: 2357 entries (jax=284, torch=2073).
Per-shard counts
|
What
Coerce scalar coordinates onto the active backend inside
check_potential_inputs_not_arrays, for_backend_compatiblepotentials only.The scalar-only compute methods of migrated potentials (
RotateAndTiltWrapperPotential,DoubleExponentialDiskPotential) still do real backend arithmetic on their coordinates —xp.isinf(R),xp.cos(phi),xp.sin(phi)— which torch rejects on a plain python float under a forced backend:This was the root cause of the
mockRotatedAndTilted*test_amp_mult_dividetorch failures (the wrapper'snormalize()and_evaluatereach the scalar-only methods with python-floatR/z/phi).The gate (caught by adversarial review)
An earlier unconditional version of this coercion regressed the unmigrated
AnyAxisymmetricRazorThinDiskPotential— its barescipy.integrate.quad/ numpy internals reject a 0-d tensor ('<' not supported between numpy.ndarray and Tensor), turning ~9 currently-green forced-torch tests red. The fix mirrors the existingpotential_physical_inputgate (conversion.py:if xp is not numpy and _check_backend_compatible(Pot)): only coerce for_backend_compatiblepotentials, so unmigrated scalar-only potentials keep their python-float inputs.tis left uncoerced (it may be a hashable cache key downstream).Result
test_amp_mult_dividetorch entries:mockRotatedAndTiltedMWP14WrapperPotential[wInclination]standalone; theTriaxialLogHalo-wrapped one composes with potential/backend: centrally coerce forced-backend scalar leaks #987's phi-default coercion (all 4 pass onfeat/backends+potential/backend: centrally coerce forced-backend scalar leaks #987).coerce_coordsis an object-identical pass-through whenxp is numpy, and the gate skips it entirely for unmigrated potentials.test_backend_disk.py+test_backend_diskexpansion.py(DoubleExp) 331✓,test_backend_wrappers.py480✓, AnyAxiRazorThin forced-torch restored to baseline (the one ledgeredtest_normalize_potentialxfail unchanged).strict=False; pruned in the separate ledger-regen PR).Tests
test_scalar_only_python_float_inputs_forced_backend(test_backend_wrappers.py): passes plain python floats to RotateAndTilt's_evaluate/_Rforce/_zforce/_phitorque/_densunder a forced backend, so the decorator coercion is what's under test. Fails at theisinfline without the fix.test_scalar_only_gate_spares_unmigrated_potential(test_backend_conventions.py): assertsAnyAxisymmetricRazorThinDiskPotential(not_backend_compatible) still evaluates under a forced backend — the regression guard for the gate.The new coercion lines run on the numpy path too (pass-through), so they are covered by existing numpy tests.
🤖 Generated with Claude Code