-
-
Notifications
You must be signed in to change notification settings - Fork 10.9k
BUG: Fix fftn failure when both out and s parameters are given #28895
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
base: main
Are you sure you want to change the base?
Conversation
Looks like a pypy issue. Similar to #28469 what they encountered. |
numpy/fft/_pocketfft.py
Outdated
if out is not None and s is not None and any(s[i] != a.shape[axes[i]] for i in range(len(axes))): | ||
intermediate_out = None | ||
else: | ||
intermediate_out = out |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it bring any performance improvement if we pass a
itself when possible as out
in intermediate steps (when out
is originally None
)? We might not be able to do so in the first iteration tough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shibozhou - I had some comments and then realized it may be possible to be a bit smarter about intermediate results, and at the same time address @vtavana's comment about using in-place with the input where possible (which should also speed up the out=None
case). I'll push to your branch with that, but the tests still need to be improved a bit. Furthermore, the documentation for out
is now no longer correct, so that needs adjusting as well.
numpy/fft/_pocketfft.py
Outdated
@@ -734,11 +734,21 @@ def _cook_nd_args(a, s=None, axes=None, invreal=0): | ||
|
||
def _raw_fftnd(a, s=None, axes=None, function=fft, norm=None, out=None): | ||
a = asarray(a) | ||
s, axes = _cook_nd_args(a, s, axes) | ||
s, axes = _cook_nd_args(a, s, axes, function is irfft) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change? _raw_fftnd
is never called with function=irfft
.
numpy/fft/_pocketfft.py
Outdated
s, axes = _cook_nd_args(a, s, axes) | ||
s, axes = _cook_nd_args(a, s, axes, function is irfft) | ||
|
||
if out is not None and s is not None and any(s[i] != a.shape[axes[i]] for i in range(len(axes))): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that s
is already overridden here.
@@ -465,6 +465,28 @@ def test_irfftn_out_and_s_interaction(self, s): | ||
assert result is out | ||
assert_array_equal(result, expected) | ||
|
||
def test_fftn_with_out_and_s(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest to use a pytest.mark.parametrize
here to do the two tests and add more possible s
tuples, making values both bigger and smaller than the array in various positions. Also, could you move the test right below test_fftn_out_and_s_interaction
(and maybe call it test_fftn_out_and_s_interaction2
).
def test_fftn_with_out_and_s(self): | ||
# fftn works correctly with both out and s parameters | ||
rng = np.random.RandomState(42) | ||
x = np.random.rand(4, 4, 4) + 1j * np.random.rand(4, 4, 4) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be wise to ensure we never modify the input, by doing after the definition, x.flags.writeable = False
.
elif this_out is not None and n < this_out.shape[axis]: | ||
# For an intermediate step where we return fewer elements, we | ||
# can use a smaller view of the previous array. | ||
this_out = this_out[(slice(None,),)*(axis-1) + (slice(n),)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it should be axis
this_out = this_out[(slice(None,),)*(axis-1) + (slice(n),)] | |
this_out = this_out[(slice(None,),)*axis + (slice(n),)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're right - which just goes to show that this really needs additional tests!
@shibozhou - are you up for adding tests, or would you prefer that I do it?
Along a similar vein: I think axis
can actually be negative here -- they should be normalized, perhaps best in _cook_nd_args
.
Fixes #28890
Problem
When N-dim FFTs are calculated using
fftn
, the code performs a loop over all axes, applying 1D FFts in sequence. However, when bothout
ands
are specified, intermediate transforms change the array shape, making theout
parameter incompatible with these intermediate results.Solution
This pr changes the
_raw_fftnd
to only use theout
parameter for the final transformation, not for intermediate steps. The current function works correctly when bothout
ands
have different shapes than the input array.Test
The added test case verifies the fix with an input array of shape
(4, 4, 4)
and an output with shape(6, 6, 6)