Repository: cupy/cupy

Issue:
incorrect FFT results for Fortran-order arrays?
* Conditions (from `python -c 'import cupy; cupy.show_config()'`)

Tested in two environments with different CuPy versions:
```bash
CuPy Version          : 4.4.1
CUDA Root             : /usr/local/cuda
CUDA Build Version    : 9010
CUDA Driver Version   : 9010
CUDA Runtime Version  : 9010
cuDNN Build Version   : 7102
cuDNN Version         : 7102
NCCL Build Version    : 2115
```
and (this CuPy is built from the latest master branch)
```
CuPy Version          : 6.0.0b1
CUDA Root             : /usr/local/cuda
CUDA Build Version    : 9010
CUDA Driver Version   : 9010
CUDA Runtime Version  : 9010
cuDNN Build Version   : None
cuDNN Version         : None
NCCL Build Version    : None
```
* Code to reproduce
```python
import numpy as np
import cupy as cp

AXES=[(0,), (1,), (2,), (0,1), (1,2), (0,2), (0,1,2)]
a_np = np.random.random((3,4,5))+1j*np.random.random((3,4,5))

print("In C order:")
a_np = np.ascontiguousarray(a_np)
a_cp = cp.asarray(a_np)
a_cp = cp.ascontiguousarray(a_cp)
assert np.allclose(cp.asnumpy(a_cp), a_np)
for axes in AXES:
    result_np = np.fft.fftn(a_np, axes=axes)
    result_cp = cp.fft.fftn(a_cp, axes=axes)
    print(axes, ":", np.allclose(cp.asnumpy(result_cp), result_np))

print("\nIn F order:")
a_np = np.asfortranarray(a_np)
a_cp = cp.asarray(a_np)
a_cp = cp.asfortranarray(a_cp)
assert np.allclose(cp.asnumpy(a_cp), a_np)
for axes in AXES:
    result_np = np.fft.fftn(a_np, axes=axes)
    result_cp = cp.fft.fftn(a_cp, axes=axes)
    print(axes, ":", np.allclose(cp.asnumpy(result_cp), result_np))
```
* Error messages, stack traces, or logs

The outputs from both environments are identical:
```bash
In C order:
(0,) : True
(1,) : True
(2,) : True
(0, 1) : True
(1, 2) : True
(0, 2) : True
(0, 1, 2) : True

In F order:
(0,) : False
(1,) : True
(2,) : False
(0, 1) : True
(1, 2) : False
(0, 2) : False
(0, 1, 2) : True
```
But it's expected to be `True` for all of the axes choices. It seems to me the bug is not introduced by the recent changes in adding support for cuFFT plans (#1669, #1745, #1746) but by something much older. For now I have not yet tracked down the problem, will update here if I find it. I hope I didn't do something stupid in the test...

Thanks.


Discussion:
If I try from current master so that it uses the recently merged `PlanNd` for most of these cases, the result is better.  Only one of the 1D cases still fails: 

```
In C order:
(0,) : True
(1,) : True
(2,) : True
(0, 1) : True
(1, 2) : True
(0, 2) : True
(0, 1, 2) : True

In F order:
(0,) : True
(1,) : True
(2,) : False
(0, 1) : True
(1, 2) : True
(0, 2) : True
(0, 1, 2) : True
```

@grlee77 but in my second environment I also used the most recent master...
hmm. I tried on a system with CUDA 8.0, but can try later on a different system with CUDA 9.2 to see if I get the same result.
I could reproduce the situation with @grlee77 .
```
CuPy Version          : 5.1.0
CUDA Root             : /home/user/.local/cuda/cuda-9.2
CUDA Build Version    : 9020
CUDA Driver Version   : 10000
CUDA Runtime Version  : 9020
cuDNN Build Version   : 7401
cuDNN Version         : 7401
NCCL Build Version    : 2307
```

```
In C order:
(0,) : True
(1,) : True
(2,) : True
(0, 1) : True
(1, 2) : True
(0, 2) : True
(0, 1, 2) : True

In F order:
(0,) : True
(1,) : True
(2,) : False
(0, 1) : True
(1, 2) : True
(0, 2) : True
(0, 1, 2) : True
```

Resolving patch:
diff --git a/cupy/fft/fft.py b/cupy/fft/fft.py
--- a/cupy/fft/fft.py
+++ b/cupy/fft/fft.py
@@ -76,7 +76,7 @@ def _exec_fft(a, direction, value_type, norm, axis, overwrite_x,
     if axis % a.ndim != a.ndim - 1:
         a = a.swapaxes(axis, -1)
 
-    if a.base is not None:
+    if a.base is not None or not a.flags.c_contiguous:
         a = a.copy()
 
     if out_size is None:


Test patch:
diff --git a/tests/cupy_tests/fft_tests/test_fft.py b/tests/cupy_tests/fft_tests/test_fft.py
--- a/tests/cupy_tests/fft_tests/test_fft.py
+++ b/tests/cupy_tests/fft_tests/test_fft.py
@@ -84,6 +84,45 @@ def test_ifft(self, xp, dtype):
         return out
 
 
+@testing.parameterize(*testing.product({
+    'shape': [(10, 10), (10, 5, 10)],
+    'data_order': ['F', 'C'],
+    'axis': [0, 1, -1],
+}))
+@testing.gpu
+@testing.with_requires('numpy>=1.10.0')
+class TestFftOrder(unittest.TestCase):
+
+    @testing.for_all_dtypes()
+    @testing.numpy_cupy_allclose(rtol=1e-4, atol=1e-7, accept_error=ValueError,
+                                 contiguous_check=False)
+    def test_fft(self, xp, dtype):
+        a = testing.shaped_random(self.shape, xp, dtype)
+        if self.data_order == 'F':
+            a = xp.asfortranarray(a)
+        out = xp.fft.fft(a, axis=self.axis)
+
+        # np.fft.fft alway returns np.complex128
+        if xp == np and dtype in [np.float16, np.float32, np.complex64]:
+            out = out.astype(np.complex64)
+
+        return out
+
+    @testing.for_all_dtypes()
+    @testing.numpy_cupy_allclose(rtol=1e-4, atol=1e-7, accept_error=ValueError,
+                                 contiguous_check=False)
+    def test_ifft(self, xp, dtype):
+        a = testing.shaped_random(self.shape, xp, dtype)
+        if self.data_order == 'F':
+            a = xp.asfortranarray(a)
+        out = xp.fft.ifft(a, axis=self.axis)
+
+        if xp == np and dtype in [np.float16, np.float32, np.complex64]:
+            out = out.astype(np.complex64)
+
+        return out
+
+
 @testing.gpu
 class TestDefaultPlanType(unittest.TestCase):