Repository: cupy/cupy

Issue:
`cupy.concatenate()` misses arguments `dtype` and `casting`
Refs:
- NumPy: https://numpy.org/doc/stable/reference/generated/numpy.concatenate.html
- CuPy: https://docs.cupy.dev/en/stable/reference/generated/cupy.concatenate.html

The `dtype` argument is needed by the Array API standard (#5698, #4789).


Discussion:

Resolving patch:
diff --git a/cupy/_manipulation/join.py b/cupy/_manipulation/join.py
--- a/cupy/_manipulation/join.py
+++ b/cupy/_manipulation/join.py
@@ -32,7 +32,7 @@ def column_stack(tup):
     return concatenate(lst, axis=1)
 
 
-def concatenate(tup, axis=0, out=None):
+def concatenate(tup, axis=0, out=None, *, dtype=None, casting='same_kind'):
     """Joins arrays along an axis.
 
     Args:
@@ -42,6 +42,11 @@ def concatenate(tup, axis=0, out=None):
             If axis is None, arrays are flattened before use.
             Default is 0.
         out (cupy.ndarray): Output array.
+        dtype (str or dtype): If provided, the destination array will have this
+            dtype. Cannot be provided together with ``out``.
+        casting ({‘no’, ‘equiv’, ‘safe’, ‘same_kind’, ‘unsafe’}, optional):
+            Controls what kind of data casting may occur. Defaults to
+            ``'same_kind'``.
 
     Returns:
         cupy.ndarray: Joined array.
@@ -52,7 +57,7 @@ def concatenate(tup, axis=0, out=None):
     if axis is None:
         tup = [m.ravel() for m in tup]
         axis = 0
-    return _core.concatenate_method(tup, axis, out)
+    return _core.concatenate_method(tup, axis, out, dtype, casting)
 
 
 def dstack(tup):


Test patch:
diff --git a/tests/cupy_tests/manipulation_tests/test_join.py b/tests/cupy_tests/manipulation_tests/test_join.py
--- a/tests/cupy_tests/manipulation_tests/test_join.py
+++ b/tests/cupy_tests/manipulation_tests/test_join.py
@@ -118,7 +118,7 @@ def test_concatenate_large_f_contiguous(self, xp, dtype):
         return xp.concatenate((a, b, c, d, e) * 2, axis=-1)
 
     @testing.numpy_cupy_array_equal()
-    def test_concatenate_many_multi_dptye(self, xp):
+    def test_concatenate_many_multi_dtype(self, xp):
         a = testing.shaped_arange((2, 1), xp, 'i')
         b = testing.shaped_arange((2, 1), xp, 'f')
         return xp.concatenate((a, b) * 1024, axis=1)
@@ -208,6 +208,39 @@ def test_concatenate_out_different_dtype(self, xp, dtype1, dtype2):
         out = xp.zeros((6, 4), dtype=dtype2)
         return xp.concatenate((a, b), out=out)
 
+    @testing.with_requires('numpy>=1.20.0')
+    @testing.for_all_dtypes_combination(names=['dtype1', 'dtype2'])
+    @testing.numpy_cupy_array_equal(accept_error=TypeError)
+    def test_concatenate_dtype(self, xp, dtype1, dtype2):
+        a = testing.shaped_arange((3, 4), xp, dtype1)
+        b = testing.shaped_arange((3, 4), xp, dtype1)
+        return xp.concatenate((a, b), dtype=dtype2)
+
+    @testing.with_requires('numpy>=1.20.0')
+    def test_concatenate_dtype_invalid_out(self):
+        for xp in (numpy, cupy):
+            a = testing.shaped_arange((3, 4), xp, xp.float64)
+            b = testing.shaped_arange((3, 4), xp, xp.float64)
+            out = xp.zeros((6, 4), dtype=xp.int64)
+            with pytest.raises(TypeError):
+                xp.concatenate((a, b), out=out, dtype=xp.int64)
+
+    @testing.with_requires('numpy>=1.20.0')
+    @pytest.mark.parametrize('casting', [
+        'no',
+        'equiv',
+        'safe',
+        'same_kind',
+        'unsafe',
+    ])
+    @testing.for_all_dtypes_combination(names=['dtype1', 'dtype2'])
+    @testing.numpy_cupy_array_equal(accept_error=TypeError)
+    def test_concatenate_casting(self, xp, dtype1, dtype2, casting):
+        a = testing.shaped_arange((3, 4), xp, dtype1)
+        b = testing.shaped_arange((3, 4), xp, dtype1)
+        # may raise TypeError
+        return xp.concatenate((a, b), dtype=dtype2)
+
     @testing.numpy_cupy_array_equal()
     def test_dstack(self, xp):
         a = testing.shaped_arange((1, 3, 2), xp)