Repository: cupy/cupy

Issue:
`linalg.lstsq` is broken for complex types
### Code to reproduce

```python
import numpy
import cupy


rng = numpy.random.default_rng(seed=0)

def randn(*size):
    return rng.normal(size=size) + 1j * rng.normal(size=size)


a = randn(3, 2)
b = randn(3)


for xp in [numpy, cupy]:
    print(xp.__name__)
    for k, v in zip(
            ['x', 'residuals', 'rank', 's'],
            xp.linalg.lstsq(xp.array(a), xp.array(b))):
        print(f"{k}: {v!r}")
```

### Error messages, stack traces, or logs
```
numpy
x: array([-0.09662162+0.22554901j,  0.02235572+0.55805196j])
residuals: array([6.45488709])
rank: 2
s: array([2.23053055, 0.99041569])
cupy
x: array([ 0.28998701-0.46292976j, -0.22706893-0.40440626j])
residuals: array([11.23488879+6.0890958j])
rank: array(2)
s: array([2.23053055, 0.99041569])
```



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
@@ -4,7 +4,7 @@
 from numpy import linalg
 
 import cupy
-from cupy import core
+from cupy.core import internal
 from cupy_backends.cuda.libs import cublas
 from cupy_backends.cuda.libs import cusolver
 from cupy.cuda import device
@@ -179,7 +179,7 @@ def tensorsolve(a, b, axes=None):
         a = a.transpose(allaxes)
 
     oldshape = a.shape[-(a.ndim - b.ndim):]
-    prod = cupy.core.internal.prod(oldshape)
+    prod = internal.prod(oldshape)
 
     a = a.reshape(-1, prod)
     b = b.ravel()
@@ -187,6 +187,12 @@ def tensorsolve(a, b, axes=None):
     return result.reshape(oldshape)
 
 
+def _nrm2_last_axis(x):
+    real_dtype = x.dtype.char.lower()
+    x = cupy.ascontiguousarray(x)
+    return cupy.sum(cupy.square(x.view(real_dtype)), axis=-1)
+
+
 def lstsq(a, b, rcond='warn'):
     """Return the least-squares solution to a linear matrix equation.
 
@@ -247,7 +253,7 @@ def lstsq(a, b, rcond='warn'):
     if m != m2:
         raise linalg.LinAlgError('Incompatible dimensions')
 
-    u, s, vt = cupy.linalg.svd(a, full_matrices=False)
+    u, s, vh = cupy.linalg.svd(a, full_matrices=False)
 
     if rcond is None:
         rcond = numpy.finfo(s.dtype).eps * max(m, n)
@@ -262,20 +268,16 @@ def lstsq(a, b, rcond='warn'):
     s1[sing_vals] = 0
     rank = s.size - sing_vals.sum(dtype=numpy.int32)
 
-    if b.ndim == 2:
-        s1 = cupy.repeat(s1.reshape(-1, 1), b.shape[1], axis=1)
     # Solve the least-squares solution
-    z = core.dot(u.transpose(), b) * s1
-    x = core.dot(vt.transpose(), z)
+    # x = vh.T.conj() @ diag(s1) @ u.T.conj() @ b
+    z = (cupy.dot(b.T, u.conj()) * s1).T
+    x = cupy.dot(vh.T.conj(), z)
     # Calculate squared Euclidean 2-norm for each column in b - a*x
-    if rank != n or m <= n:
-        resids = cupy.array([], dtype=a.dtype)
-    elif b.ndim == 2:
-        e = b - core.dot(a, x)
-        resids = cupy.sum(cupy.square(e), axis=0)
+    if m <= n or rank != n:
+        resids = cupy.empty((0,), dtype=s.dtype)
     else:
-        e = b - cupy.dot(a, x)
-        resids = cupy.dot(e.T, e).reshape(-1)
+        e = b - a.dot(x)
+        resids = cupy.atleast_1d(_nrm2_last_axis(e.T))
     return x, resids, rank, s
 
 
@@ -409,7 +411,7 @@ def pinv(a, rcond=1e-15):
     cutoff = rcond * s.max()
     s1 = 1 / s
     s1[s <= cutoff] = 0
-    return core.dot(vt.T, s1[:, None] * u.T)
+    return cupy.dot(vt.T, s1[:, None] * u.T)
 
 
 def tensorinv(a, ind=2):
@@ -445,7 +447,7 @@ def tensorinv(a, ind=2):
         raise ValueError('Invalid ind argument')
     oldshape = a.shape
     invshape = oldshape[ind:] + oldshape[:ind]
-    prod = cupy.core.internal.prod(oldshape[ind:])
+    prod = internal.prod(oldshape[ind:])
     a = a.reshape(prod, -1)
     a_inv = inv(a)
     return a_inv.reshape(*invshape)


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
@@ -187,8 +187,8 @@ def test_invalid_shape(self):
 @testing.gpu
 class TestLstsq(unittest.TestCase):
 
-    @testing.for_float_dtypes(no_float16=True)
-    @testing.numpy_cupy_allclose(atol=1e-3, contiguous_check=False)
+    @testing.for_dtypes('fdFD')
+    @testing.numpy_cupy_allclose(atol=1e-3)
     def check_lstsq_solution(self, a_shape, b_shape, seed, rcond, xp, dtype,
                              singular=False):
         a = testing.shaped_random(a_shape, xp, dtype=dtype, seed=seed)