Repository: cupy/cupy

Issue:
`cupy.linalg.solve` returns wrong output for complex inputs
```
>>> a
array([[ 1.03310883-0.87701077j, -0.35775642-0.94918977j,
         2.63837642+0.29920745j],
       [-0.88041189+0.97322198j,  0.26771522-0.56788898j,
        -0.47020123+0.63015159j],
       [-0.05746688-0.16238003j, -0.18490837-0.00778188j,
         1.61550173+1.13397892j]])
>>> b
array([ 0.92632861+1.45552766j, -0.54307601-0.62108012j,
       -0.23769241+2.91680425j])
>>> cupy.linalg.solve(a, b)
array([ 0.07332535+0.27916782j, -1.61809337-0.62108012j,
       -0.23769241+2.91680425j])
>>> numpy.linalg.solve(cupy.asnumpy(a), cupy.asnumpy(b))
array([0.04332182-0.34957548j, 1.6260605 -0.2383031j ,
       0.88753974+1.15498982j])
>>> cupy.__version__
'5.0.0b4'
```

#1524 would fix the issue.


Discussion:

Resolving patch:
diff --git a/cupy/linalg/solve.py b/cupy/linalg/solve.py
--- a/cupy/linalg/solve.py
+++ b/cupy/linalg/solve.py
@@ -53,37 +53,55 @@ def solve(a, b):
     else:
         dtype = numpy.find_common_type((a.dtype.char, 'f'), ())
 
+    cublas_handle = device.get_cublas_handle()
+    cusolver_handle = device.get_cusolver_handle()
+
     a = a.astype(dtype)
     b = b.astype(dtype)
     if a.ndim == 2:
-        return _solve(a, b)
+        return _solve(a, b, cublas_handle, cusolver_handle)
+
     x = cupy.empty_like(b)
     shape = a.shape[:-2]
     for i in six.moves.range(numpy.prod(shape)):
         index = numpy.unravel_index(i, shape)
-        x[index] = _solve(a[index], b[index])
+        x[index] = _solve(a[index], b[index], cublas_handle, cusolver_handle)
     return x
 
 
-def _solve(a, b):
+def _solve(a, b, cublas_handle, cusolver_handle):
     a = cupy.asfortranarray(a)
     b = cupy.asfortranarray(b)
     dtype = a.dtype
     m, k = (b.size, 1) if b.ndim == 1 else b.shape
-    cusolver_handle = device.get_cusolver_handle()
-    cublas_handle = device.get_cublas_handle()
     dev_info = cupy.empty(1, dtype=numpy.int32)
 
     if dtype == 'f':
         geqrf = cusolver.sgeqrf
         geqrf_bufferSize = cusolver.sgeqrf_bufferSize
         ormqr = cusolver.sormqr
+        trans = cublas.CUBLAS_OP_T
         trsm = cublas.strsm
-    else:  # dtype == 'd'
+    elif dtype == 'd':
         geqrf = cusolver.dgeqrf
         geqrf_bufferSize = cusolver.dgeqrf_bufferSize
         ormqr = cusolver.dormqr
+        trans = cublas.CUBLAS_OP_T
         trsm = cublas.dtrsm
+    elif dtype == 'F':
+        geqrf = cusolver.cgeqrf
+        geqrf_bufferSize = cusolver.cgeqrf_bufferSize
+        ormqr = cusolver.cormqr
+        trans = cublas.CUBLAS_OP_C
+        trsm = cublas.ctrsm
+    elif dtype == 'D':
+        geqrf = cusolver.zgeqrf
+        geqrf_bufferSize = cusolver.zgeqrf_bufferSize
+        ormqr = cusolver.zormqr
+        trans = cublas.CUBLAS_OP_C
+        trsm = cublas.ztrsm
+    else:
+        raise NotImplementedError(dtype)
 
     # 1. QR decomposition (A = Q * R)
     buffersize = geqrf_bufferSize(cusolver_handle, m, m, a.data.ptr, m)
@@ -95,7 +113,7 @@ def _solve(a, b):
     _check_status(dev_info)
     # 2. ormqr (Q^T * B)
     ormqr(
-        cusolver_handle, cublas.CUBLAS_SIDE_LEFT, cublas.CUBLAS_OP_T,
+        cusolver_handle, cublas.CUBLAS_SIDE_LEFT, trans,
         m, k, m, a.data.ptr, m, tau.data.ptr, b.data.ptr, m,
         workspace.data.ptr, buffersize, dev_info.data.ptr)
     _check_status(dev_info)


Test patch:
diff --git a/tests/cupy_tests/linalg_tests/test_solve.py b/tests/cupy_tests/linalg_tests/test_solve.py
--- a/tests/cupy_tests/linalg_tests/test_solve.py
+++ b/tests/cupy_tests/linalg_tests/test_solve.py
@@ -11,23 +11,21 @@
 @unittest.skipUnless(
     cuda.cusolver_enabled, 'Only cusolver in CUDA 8.0 is supported')
 @testing.gpu
+@testing.fix_random()
 class TestSolve(unittest.TestCase):
 
-    @testing.for_float_dtypes(no_float16=True)
-    @condition.retry(10)
-    def check_x(self, a_shape, b_shape, dtype):
-        a_cpu = numpy.random.randint(0, 10, size=a_shape).astype(dtype)
-        b_cpu = numpy.random.randint(0, 10, size=b_shape).astype(dtype)
-        a_gpu = cupy.asarray(a_cpu)
-        b_gpu = cupy.asarray(b_cpu)
-        a_gpu_copy = a_gpu.copy()
-        b_gpu_copy = b_gpu.copy()
-        result_cpu = numpy.linalg.solve(a_cpu, b_cpu)
-        result_gpu = cupy.linalg.solve(a_gpu, b_gpu)
-        self.assertEqual(result_cpu.dtype, result_gpu.dtype)
-        cupy.testing.assert_allclose(result_cpu, result_gpu, atol=1e-3)
-        cupy.testing.assert_array_equal(a_gpu_copy, a_gpu)
-        cupy.testing.assert_array_equal(b_gpu_copy, b_gpu)
+    @testing.for_dtypes('fdFD')
+    # TODO(kataoka): Fix contiguity
+    @testing.numpy_cupy_allclose(atol=1e-3, contiguous_check=False)
+    def check_x(self, a_shape, b_shape, xp, dtype):
+        a = testing.shaped_random(a_shape, xp, dtype=dtype, seed=0)
+        b = testing.shaped_random(b_shape, xp, dtype=dtype, seed=1)
+        a_copy = a.copy()
+        b_copy = b.copy()
+        result = xp.linalg.solve(a, b)
+        cupy.testing.assert_array_equal(a_copy, a)
+        cupy.testing.assert_array_equal(b_copy, b)
+        return result
 
     def test_solve(self):
         self.check_x((4, 4), (4,))