Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEP: signal: raise error using medfilt and order_filter with float128 and object dtypes #19673

Merged
merged 14 commits into from
May 23, 2024

Conversation

j-bowhay
Copy link
Member

Reference issue

follow up to #18341

What does this implement/fix?

It has been two releases since float128 and object types were deprecated to deduplicate code, should be fine to raise an error now.

Additional information

@j-bowhay j-bowhay added scipy.signal deprecated Items related to behavior that has been deprecated labels Dec 10, 2023
@j-bowhay j-bowhay requested a review from ev-br December 10, 2023 22:06
@@ -1115,7 +1106,7 @@ def test_invalid_dtypes(self, dtype):
def test_none(self):
# gh-1651, trac #1124. Ensure this does not segfault.
with pytest.warns(UserWarning):
assert_raises(TypeError, signal.medfilt, None)
assert_raises(ValueError, signal.medfilt, None)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there was some sort of special casing here for None. Have it lumped in with the others that raise ValueError but could special case so this remains a TypeError

@j-bowhay j-bowhay mentioned this pull request Dec 10, 2023
29 tasks
@j-bowhay j-bowhay added this to the 1.13.0 milestone Dec 10, 2023
Copy link
Member

@h-vetinari h-vetinari left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a mismatch between the PR title and the scope of what it does.

scipy/signal/_signaltools.py Show resolved Hide resolved
scipy/signal/_sigtoolsmodule.c Outdated Show resolved Hide resolved
@lucascolley
Copy link
Member

I'm also a bit confused since gh-18341 said "The goal here is to reduce duplication between ndimage and signal, and get rid of https://github.com/scipy/scipy/blob/main/scipy/signal/_lfilter.c.in entirely (700 LOC of fairly obscure C code). Cannot just remove them, so deprecate first."

@j-bowhay
Copy link
Member Author

The point of the original deprecation was that we currently have two sets of C code for doing the same thing, one in signal and one in ndimage. The signal functions were modified to use the ndimage code under the hood so signal C code could be removed as it was decided the ndimage code was more maintainable. However ndimage did not support object or float128 dtypes hence the deprecation cycle before all the signal C code could be removed.

@j-bowhay
Copy link
Member Author

There are some relevant test failures that need resolving though

@lucascolley
Copy link
Member

Ah okay, so does all of _lfilter.c.in need to stay?

@j-bowhay
Copy link
Member Author

Ah okay, so does all of _lfilter.c.in need to stay?

As far as I could tell it was still needed but I also might have just got lost the maze. Maybe @ev-br could confirm?

if volume.dtype in [np.bool_, np.complex64, np.complex128, np.clongdouble,
np.float16]:
np.float16, np.object_, 'float128']:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that you want to use longdouble as the dtype name, because that's the canonical one (float128 may not exist, and then the alias float96 will exist)

@ev-br
Copy link
Member

ev-br commented Dec 19, 2023

Ah okay, so does all of _lfilter.c.in need to stay?

Indeed. Thank you Jake for chasing down where this dead code is despite the typo in the original PR which introduced the deprecation.

@j-bowhay
Copy link
Member Author

Updated to use longdouble and removed more dead code

Copy link
Member

@h-vetinari h-vetinari left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some relatively minor nits, otherwise looking good (thanks for the context on _order_filterND).

scipy/signal/_signaltools.py Outdated Show resolved Hide resolved
scipy/signal/tests/test_signaltools.py Outdated Show resolved Hide resolved
scipy/signal/tests/test_signaltools.py Outdated Show resolved Hide resolved
Copy link
Member

@h-vetinari h-vetinari left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last details. Not sure why the doc build is failing, but at least win/macos jobs need to be fixed.

Comment on lines 1549 to 1551
if volume.dtype in [np.bool_, np.complex64, np.complex128, np.clongdouble,
np.float16, np.object_, np.longdouble]:
raise ValueError(f"dtype={volume.dtype} is not supported by medfilt")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think np.longdouble here is incorrect, because on windows that just ends up being float64 and then causes

FAILED scipy\signal\tests\test_signaltools.py::TestMedFilt::test_types[float64] - ValueError: dtype=float64 is not supported by medfilt

IMO it would be better to formulate this as:

Suggested change
if volume.dtype in [np.bool_, np.complex64, np.complex128, np.clongdouble,
np.float16, np.object_, np.longdouble]:
raise ValueError(f"dtype={volume.dtype} is not supported by medfilt")
if volume.dtype not in <list_of_allowed_types>:
raise ValueError(f"dtype={volume.dtype} is not supported by medfilt")

Copy link
Member

@h-vetinari h-vetinari left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there, but still not yet. 🙃

Comment on lines 1549 to 1551
if volume.dtype in [np.bool_, np.complex64, np.complex128, np.clongdouble,
np.float16, np.object_, "float128", "float96"]:
raise ValueError(f"dtype={volume.dtype} is not supported by medfilt")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still prefer explicitly naming the types for which we have support in medfilt (non-binding).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If starting from scratch I would entirely agree with you however I am slightly hesitant to do this as, given all the longdouble confusion, I think it might be quite easy to mess up and introduce a change that was not within the scope of the original deprecation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that it should be trivial to specify the types we support - those are the one we actually implement (resp. template over), and that's IMO easier and more useful than playing whack-a-mole with weird/rare types that we somehow forgot to mention here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up again! I just looked through again and it looks good, though I'd still want to allowlist certain types here, rather than blocklist an arbitrarily long list of dtypes (as an example taken at random, we're probably not failing correctly for datetime types)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a look, hopefully this has been addressed in the latest commit

Comment on lines 1101 to 1102
if in_typed.dtype is np.float64:
pytest.mark.skip("Platform does not support `float128`")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The combination of if-condition and skip-message makes no sense is confusing. It would be much better not to parametrize on an ambiguous type (np.longdouble) in the first place, but if that's unavoidable, one way to clarify would be (feel free to rewrite):

Suggested change
if in_typed.dtype is np.float64:
pytest.mark.skip("Platform does not support `float128`")
if in_typed.dtype is np.float64:
pytest.mark.skip("Platform treats `np.longdouble` as `np.float64`; "
"we support the latter, but not the former (in general).")

Copy link
Member Author

@j-bowhay j-bowhay Jan 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agh, the tests are still failing on windows and macos arm, neither of which I have access to, so this clearly is not quite right. @ev-br would you be able to point me in the right direction here as I am getting quite confused!

Copy link
Member

@ev-br ev-br Jan 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think if dtype is np.float64 is correct really. I suspect you're hitting #18341 (comment), so can you dispatch on in_typed.dtype.char == 'g' instead?

I can check it in a couple of days unless you beat me to it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just don't parametrize the test with np.longdouble, but rather float96 & float128.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I am misunderstanding but I don't think we can use float128 directly here as it doesn't exist on some platforms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you'll need to skip it on platforms that don't have it. I made another suggestion that's IMO easier to follow (and allowing to delete the if...skip after in_typed entirely), because there's no need to invert any logic (of the kind "we support everything but these types" or "we support np.longdouble except when it's actually np.float64).

Comment on lines 1097 to 1100
np.clongdouble, np.float16, np.longdouble,
np.object_])
def test_invalid_dtypes(self, dtype):
in_typed = np.array(self.IN, dtype=dtype)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
np.clongdouble, np.float16, np.longdouble,
np.object_])
def test_invalid_dtypes(self, dtype):
in_typed = np.array(self.IN, dtype=dtype)
np.clongdouble, np.float16,
np.object_, "float96", "float128"])
def test_invalid_dtypes(self, dtype):
if system_does_not_have_large_float and dtype in ["float96", "float128"]:
pytest.mark.skip("Platform does not support floating point types larger than 64bit")
in_typed = np.array(self.IN, dtype=dtype)

system_does_not_have_large_float is left as an exercise to the reader. ;-)

Comment on lines 1101 to 1102
if in_typed.dtype is np.float64:
pytest.mark.skip("Platform does not support `float128`")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you'll need to skip it on platforms that don't have it. I made another suggestion that's IMO easier to follow (and allowing to delete the if...skip after in_typed entirely), because there's no need to invert any logic (of the kind "we support everything but these types" or "we support np.longdouble except when it's actually np.float64).

@h-vetinari
Copy link
Member

I think we can wait for 1.14 with this? Unless you want to get this in urgently, I think we should give it a year as usual.

@j-bowhay
Copy link
Member Author

Closing until we branch for 1.14

@j-bowhay j-bowhay closed this Jan 17, 2024
@j-bowhay j-bowhay removed this from the 1.13.0 milestone Jan 17, 2024
@h-vetinari
Copy link
Member

Branch has happened, reopening.

@h-vetinari h-vetinari reopened this Mar 19, 2024
@h-vetinari
Copy link
Member

@j-bowhay, do you think you'll be able to rebase this in the coming weeks?

@j-bowhay j-bowhay added the needs-work Items that are pending response from the author label Apr 26, 2024
@j-bowhay j-bowhay added this to the 1.14.0 milestone Apr 26, 2024
@j-bowhay j-bowhay removed the needs-work Items that are pending response from the author label May 18, 2024
@j-bowhay j-bowhay requested a review from h-vetinari May 21, 2024 09:10
[skip ci]

Co-authored-by: h-vetinari <h.vetinari@gmx.com>
Copy link
Member

@h-vetinari h-vetinari left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@h-vetinari
Copy link
Member

@j-bowhay, do you want to keep some of the commit granularity (would probably need a clean-up), or are you fine with a squash-merge?

@j-bowhay
Copy link
Member Author

@h-vetinari happy to squash merge

@h-vetinari h-vetinari merged commit b421cd6 into scipy:main May 23, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
deprecated Items related to behavior that has been deprecated scipy.signal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants