Repository: cupy/cupy

Issue:
`overwrite_x=True` in cupyx.scipy.fft* doesn't do in-place transform for contiguous views
Reproducer:
```python
>>> import cupy as cp
>>> import cupyx.scipy.fftpack as cufft  # can also use cupyx.scipy.fft
>>> a = cp.random.random((64, 128, 128))+1j*cp.random.random((64, 128, 128))
>>> a.data.ptr
140130136358912
>>> b = a[:16, ...]  # create a C-contiguous view
>>> b.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : False
>>> b.data.ptr  # this is the same as a's
140130136358912
>>> b = cufft.fftn(b, overwrite_x=True)
>>> b.data.ptr  # expected to unchanged
140130041987072
```
This behavior is not expected, since the FFT of a contiguous view should (and can) be done in-place. (BTW, it doesn't matter whether a precomputed plan is used or not.) The underlying reason for the unexpected behavior is, curiously, the same for #3033: 
> This is due to an internal copy of the view that does not respected contiguity

This behavior is also against what we suggested in the doc (in a loose sense):
https://github.com/cupy/cupy/blob/016e4ce34723d44a7af613a76acf0baba53b6d3d/docs/source/reference/fftpack.rst#L36 
So we fix either the doc or the code. My preference is the latter, as it doesn't seem to be hard at first glance.


Discussion:

Resolving patch:
diff --git a/cupy/fft/fft.py b/cupy/fft/fft.py
--- a/cupy/fft/fft.py
+++ b/cupy/fft/fft.py
@@ -356,9 +356,6 @@ def _exec_fftn(a, direction, value_type, norm, axes, overwrite_x,
     if fft_type not in [cufft.CUFFT_C2C, cufft.CUFFT_Z2Z]:
         raise NotImplementedError('Only C2C and Z2Z are supported.')
 
-    if a.base is not None:
-        a = a.copy()
-
     if a.flags.c_contiguous:
         order = 'C'
     elif a.flags.f_contiguous:


Test patch:
diff --git a/tests/cupyx_tests/scipy_tests/fftpack_tests/test_fftpack.py b/tests/cupyx_tests/scipy_tests/fftpack_tests/test_fftpack.py
--- a/tests/cupyx_tests/scipy_tests/fftpack_tests/test_fftpack.py
+++ b/tests/cupyx_tests/scipy_tests/fftpack_tests/test_fftpack.py
@@ -1,6 +1,7 @@
 import unittest
 import pytest
 
+import cupy
 from cupy import testing
 import cupyx.scipy.fftpack  # NOQA
 from cupy.fft.fft import _default_fft_func, _fftn
@@ -578,3 +579,64 @@ def test_irfft_overwrite(self, xp, scp, dtype):
         x = testing.shaped_random(self.shape, xp, dtype)
         return scp.fftpack.irfft(x, n=self.n, axis=self.axis,
                                  overwrite_x=True)
+
+
+@testing.parameterize(
+    {'shape': (32, 16, 4), 'data_order': 'F'},
+    {'shape': (4, 32, 16), 'data_order': 'C'},
+)
+class TestFftnView(unittest.TestCase):
+
+    @testing.for_complex_dtypes()
+    def test_contiguous_view(self, dtype):
+        # Fortran-ordered case tests: https://github.com/cupy/cupy/issues/3079
+        a = testing.shaped_random(self.shape, cupy, dtype)
+        if self.data_order == 'F':
+            a = cupy.asfortranarray(a)
+            sl = [Ellipsis, 0]
+        else:
+            sl = [0, Ellipsis]
+
+        # transform a contiguous view without pre-planning
+        view = a[sl]
+        expected = cupyx.scipy.fftpack.fftn(view)
+
+        # create plan and then apply it to a contiguous view
+        plan = cupyx.scipy.fftpack.get_fft_plan(view)
+        with plan:
+            out = cupyx.scipy.fftpack.fftn(view)
+        testing.assert_allclose(expected, out)
+
+    @testing.for_complex_dtypes()
+    def test_noncontiguous_view(self, dtype):
+        a = testing.shaped_random(self.shape, cupy, dtype)
+        if self.data_order == 'F':
+            a = cupy.asfortranarray(a)
+            sl = [Ellipsis, slice(None, None, 2)]
+        else:
+            sl = [slice(None, None, 2), Ellipsis]
+
+        # transform a non-contiguous view without pre-planning
+        view = a[sl]
+        expected = cupyx.scipy.fftpack.fftn(view)
+
+        # create plan and then apply it to a non-contiguous view
+        plan = cupyx.scipy.fftpack.get_fft_plan(view.copy())
+        with plan:
+            out = cupyx.scipy.fftpack.fftn(view)
+        testing.assert_allclose(expected, out)
+
+    @testing.for_complex_dtypes()
+    def test_overwrite_x_with_contiguous_view(self, dtype):
+        # Test case for: https://github.com/cupy/cupy/issues/3079
+        a = testing.shaped_random(self.shape, cupy, dtype)
+        if self.data_order == 'C':
+            # C-contiguous view
+            b = a[:a.shape[0] // 2, ...]
+        else:
+            # F-contiguous view
+            a = cupy.asfortranarray(a)
+            b = a[..., :a.shape[-1] // 2]
+        b_ptr = b.data.ptr
+        out = cupyx.scipy.fftpack.fftn(b, overwrite_x=True)
+        assert out.data.ptr == b_ptr