Repository: cupy/cupy

Issue:
Documentation for `cupy.linalg.matrix_power()` has incorrect warning
In master, the documentation for `cupy.linalg.matrix_power()` states that "M must be of dtype float32 or float64.". However, the function is only implemented using `cupy.linalg.matmul()` which does support integer matrices, i.e. the following works fine:

```python
cupy.linalg.matrix_power(cupy.arange(1, 26).reshape(5, 5), 5)
```

It is possible that this was originally intended to be implemented using an eigenvalue decomposition, however, eigenvalue decomposition isn't well supported with CUDA unless the matrix is symmetric, and thus it is *not* implemented using that setup. Additionally, in the future if the eigenvalue decomposition method is implemented, the current method could still be used for integer matrices.


Discussion:
> algorithm

`scipy.linalg.fractional_matrix_power` uses some matrix decomposition.
https://github.com/scipy/scipy/blob/v1.7.0/scipy/linalg/_matfuncs_inv_ssq.py#L596-L668

For integer `n`, I suppose iterating matmul is faster.
https://github.com/numpy/numpy/blob/v1.21.0/numpy/linalg/linalg.py#L656-L664



Resolving patch:
diff --git a/cupy/linalg/_product.py b/cupy/linalg/_product.py
--- a/cupy/linalg/_product.py
+++ b/cupy/linalg/_product.py
@@ -6,8 +6,8 @@
 from cupy import _core
 from cupy._core import internal
 from cupy._core._gufuncs import _GUFunc
-
-from cupy.linalg._solve import inv
+from cupy.linalg import _solve
+from cupy.linalg import _util
 
 _gu_func_matmul = _GUFunc(
     _core.matmul, '(n?,k),(k,m?)->(n?,m?)', supports_batched=True)
@@ -349,6 +349,7 @@ def tensordot(a, b, axes=2):
     return _core.tensordot_core(a, b, None, n, m, k, ret_shape)
 
 
+# TODO: rename `M` to `a`
 def matrix_power(M, n):
     """Raise a square matrix to the (integer) power `n`.
 
@@ -359,19 +360,19 @@ def matrix_power(M, n):
     Returns:
         ~cupy.ndarray: Output array.
 
-    .. note:: M must be of dtype `float32` or `float64`.
-
     ..seealso:: :func:`numpy.linalg.matrix_power`
     """
-    if M.ndim != 2 or M.shape[0] != M.shape[1]:
-        raise ValueError('input must be a square array')
+    _util._assert_cupy_array(M)
+    _util._assert_nd_squareness(M)
     if not isinstance(n, int):
         raise TypeError('exponent must be an integer')
 
     if n == 0:
-        return cupy.identity(M.shape[0], dtype=M.dtype)
+        M = cupy.empty_like(M)
+        M[...] = cupy.identity(M.shape[-2], dtype=M.dtype)
+        return M
     elif n < 0:
-        M = inv(M)
+        M = _solve.inv(M)
         n *= -1
 
     # short-cuts


Test patch:
diff --git a/tests/cupy_tests/linalg_tests/test_product.py b/tests/cupy_tests/linalg_tests/test_product.py
--- a/tests/cupy_tests/linalg_tests/test_product.py
+++ b/tests/cupy_tests/linalg_tests/test_product.py
@@ -466,3 +466,18 @@ def test_matrix_power_large(self, xp, dtype):
     def test_matrix_power_invlarge(self, xp, dtype):
         a = xp.eye(23, k=17, dtype=dtype) + xp.eye(23, k=-6, dtype=dtype)
         return xp.linalg.matrix_power(a, -987654321987654321)
+
+
+@pytest.mark.parametrize('shape', [
+    (2, 3, 3),
+    (3, 0, 0),
+])
+@pytest.mark.parametrize('n', [0, 5, -7])
+class TestMatrixPowerBatched:
+
+    @testing.for_float_dtypes(no_float16=True)
+    @testing.numpy_cupy_allclose(rtol=1e-5)
+    def test_matrix_power_batched(self, xp, dtype, shape, n):
+        a = testing.shaped_arange(shape, xp, dtype)
+        a += xp.identity(shape[-1], dtype)
+        return xp.linalg.matrix_power(a, n)