Support all scipy.special ufuncs with quantities fixes #6390#19390
Support all scipy.special ufuncs with quantities fixes #6390#19390luvyarana wants to merge 1 commit intoastropy:mainfrom
Conversation
|
Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.
|
|
I investigated all scipy.special ufuncs and found that 172 were not registered in quantity_helper. Since all of these operate on dimensionless inputs and return dimensionless outputs, I grouped them by their signature (nin/nout) and added helper functions accordingly. This PR adds complete support along with test coverage. If maintainers prefer a smaller incremental approach, I would be happy to split this into multiple PRs. |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
46641a7 to
631782e
Compare
|
This PR has been rebased onto the latest main, CI issues have been resolved, and commits have been squashed into a single commit. All checks are now passing and the PR should be ready for review. The implementation systematically registers unsupported scipy.special ufuncs by grouping them according to their nin/nout signatures and mapping them to appropriate dimensionless helper functions. |
459282e to
1e866c4
Compare
|
Thanks! I will let units maintainer decide if this needs a What's New entry or not. |
1e866c4 to
07ad76c
Compare
mhvk
left a comment
There was a problem hiding this comment.
@luvyarana - thanks, this is mostly great. My comments inline are really small.
A bigger one, however, is that one does need to check carefully that the functions in fact take dimensionless input. I think most do but not all. I just looked through a bit, and found some that do not: agm creates a mean, so it should be like addition (and could use the addition helper), while cosm1 is like a cosine and should use that helper. Also, mathieu_cem takes one argument in degrees -- that would be one where quantities are particularly useful...
Anyway, I guess that unfortunately one has to go through all the function descriptions to check...
I do think it is worth a what's new, since this makes quantity support a lot more complete! (docs/whatsnew/8.0.rst).
| for name in dimensionless_to_dimensionless_sps_ufuncs: | ||
| ufunc = getattr(sps, name, None) | ||
| SCIPY_HELPERS[ufunc] = helper_dimensionless_to_dimensionless | ||
| if ufunc is not None: |
There was a problem hiding this comment.
Why do you do this test? Shouldn't the ufunc always exist? (Also below)
I realize that the original code here is a bit weird too in using getattr(sps, name, None) instead of getattr(sps, name) as done below.
There was a problem hiding this comment.
The guard is mainly there to avoid import time failures with older
SciPy versions where some ufuncs may not be present.
I kept the getattr(..., None) pattern for consistency with existing code and to
ensure compatibility, but happy to simplify this if we want to rely on a minimum
SciPy version where all listed ufuncs are guaranteed to exist.
|
Looks like someone else proposed a completely different approach at #19442 (doc update only). 🤷♀️ |
|
@mhvk Thanks, this is very helpful. You're right that not all scipy.special functions are strictly dimensionless → dimensionless. I'll also add a What's New entry as suggested. |
dc375e2 to
27a76e8
Compare
|
I definitely prefer to actually support the scipy ufuncs! And this PR is close, since most do require dimensionless quantities -- the only unfortunate thing is that one has to go through each of them to verify that. Thanks, @luvyarana, for taking this on! |
|
@mhvk • Revisited the ufunc classification to better reflect their behavior. • Standardized all helper registration loops to use • Cleaned up the code by removing unclear comments and fixing minor PEP8 issues. • Updated behavior for key cases:
• Added tests for these cases — all tests and pre-commit checks pass. (One note from verification: for mathieu_modcem/modsem, although scipy docs mention "degrees", Similarly, for lpmv, "degree" refers to the polynomial order rather than an angle unit, I’ve also added a What’s New entry for this change. |
f01255c to
6523033
Compare
|
I'll look at the actual code later, but two quick replies to your longer comment now:
I'd like to only use this pattern if there is an actual need. I think new functions get relatively rarely added to scipy, so checking whether a function exists should generally not be needed. It is also better to have an explicit scipy version check in that case, so we can clean up things when they are no longer needed.
That seems wrong. If a function expects an argument in degrees, then the point of Quantity is that if one passes in, say, arcminutes, they get properly converted. |
6523033 to
0199893
Compare
I’ve removed the getattr(..., None) guards as suggested. Just to confirm, |
|
Yes, minimum scipy version is all we guarantee astropy works for; if one wants an older one, use an older astropy too... Note that there are test failures with "oldest supported versions", so apparently there are new additions to the special functions. If there are a lot of those, I perhaps should reconsider my ideas.... |
|
Ah, I see thanks, that clarifies it. It looks like several of the ufuncs included here are not present in the oldest supported SciPy version, which explains the failures. Given that, I think restoring the getattr(sps, name, None) + guard pattern makes sense here to maintain compatibility across supported versions, especially since the ufunc set does vary. I can reintroduce the guard consistently across all loops does that sound like the preferred approach? |
a8fc8c3 to
db650c8
Compare
|
The issue was that some ufuncs in the list (e.g., ellip*, wright_bessel, log_wright_bessel) are not present in the minimum supported SciPy version (1.13). Using bare getattr(sps, name) caused an AttributeError at import time, which prevented all scipy ufuncs from being registered. I’ve restored the safe pattern: All tests and pre-commit checks are now passing. |
Classify all scipy.special ufuncs by their physical semantics: - Dimensionless: error functions, gamma functions, Bessel functions, statistical distributions, hypergeometric functions, etc. - Angle-accepting: cosm1 (radians), ellipeinc/ellipkinc (radians), mathieu_cem/sem and mathieu_modcem1/2/modsem1/2 (degrees), cosdg/sindg/tandg/cotdg (degrees). - Arithmetic-like: agm (arithmetic-geometric mean, preserves units). - Special cases: cbrt (unit transform), radian (angle conversion), round (unit-preserving). Use getattr(sps, name, None) guards throughout so that ufuncs added after the minimum supported SciPy version (1.13) are silently skipped rather than raising AttributeError at import time. Also fix pre-existing misclassification of itj0y0, it2j0y0, iti0k0, it2i0k0 as 1-output (they have 2 outputs). Fixes astropy#6390
cfa8e1c to
1a0761b
Compare
|
Hi @mhvk just a quick ping on this. I’ve addressed the earlier feedback, including restoring the guarded getattr pattern for compatibility with older SciPy versions. All tests and checks are now passing. I do see a failure in the mpldev image comparison job ( Would appreciate another look when you get a chance. |
Added quantity support for all remaining scipy.special ufuncs. They now properly convert dimensionless quantities and return dimensionless quantities. This includes functions with multiple outputs like airy and fresnel, and functions with multiple inputs.
A verification script was used to enumerate all 232 scipy.special ufuncs and confirm
their nin/nout signatures. This also revealed four pre existing misclassifications
(itj0y0, it2j0y0, iti0k0, it2i0k0) which are corrected in this PR.
After these corrections, all scipy.special ufuncs are now properly registered with
appropriate helpers for dimensionless inputs and outputs.
fixes #6390