Repository: cubed-dev/cubed
Pull request #644 (2024-12-13 14:12:47)

Add `tile`
Part of #438


Patch:
diff --git a/api_status.md b/api_status.md
index ff1f2c59..89a3eb17 100644
--- a/api_status.md
+++ b/api_status.md
@@ -66,7 +66,7 @@ This table shows which parts of the the [Array API](https://data-apis.org/array-
 |                          | `roll`              | :white_check_mark: |            |                              |
 |                          | `squeeze`           | :white_check_mark: |            |                              |
 |                          | `stack`             | :white_check_mark: |            |                              |
-|                          | `tile`              | :x:                | 2023.12    |                              |
+|                          | `tile`              | :white_check_mark: | 2023.12    |                              |
 |                          | `unstack`           | :white_check_mark: | 2023.12    |                              |
 | Searching Functions      | `argmax`            | :white_check_mark: |            |                              |
 |                          | `argmin`            | :white_check_mark: |            |                              |
diff --git a/cubed/__init__.py b/cubed/__init__.py
index 048fea5b..dbb28f7a 100644
--- a/cubed/__init__.py
+++ b/cubed/__init__.py
@@ -302,6 +302,7 @@ from .array_api.manipulation_functions import (
     roll,
     squeeze,
     stack,
+    tile,
     unstack,
 )
 
@@ -318,6 +319,7 @@ __all__ += [
     "roll",
     "squeeze",
     "stack",
+    "tile",
     "unstack",
 ]
 
diff --git a/cubed/array_api/__init__.py b/cubed/array_api/__init__.py
index 587242f9..e0510185 100644
--- a/cubed/array_api/__init__.py
+++ b/cubed/array_api/__init__.py
@@ -243,6 +243,7 @@ from .manipulation_functions import (
     roll,
     squeeze,
     stack,
+    tile,
     unstack,
 )
 
@@ -259,6 +260,7 @@ __all__ += [
     "roll",
     "squeeze",
     "stack",
+    "tile",
     "unstack",
 ]
 
diff --git a/cubed/array_api/manipulation_functions.py b/cubed/array_api/manipulation_functions.py
index 93ea2341..9a31f107 100644
--- a/cubed/array_api/manipulation_functions.py
+++ b/cubed/array_api/manipulation_functions.py
@@ -579,6 +579,21 @@ def _read_stack_chunk(array, axis=None):
     return nxp.expand_dims(array, axis=axis)
 
 
+def tile(x, repetitions, /):
+    N = len(x.shape)
+    M = len(repetitions)
+    if N > M:
+        repetitions = (1,) * (N - M) + repetitions
+    elif N < M:
+        for _ in range(M - N):
+            x = expand_dims(x, axis=0)
+    out = x
+    for i, nrep in enumerate(repetitions):
+        if nrep > 1:
+            out = concat([out] * nrep, axis=i)
+    return out
+
+
 def unstack(x, /, *, axis=0):
     axis = validate_axis(axis, x.ndim)
 


Test patch:
diff --git a/cubed/tests/runtime/utils.py b/cubed/tests/runtime/utils.py
index 238218aa..f8da354b 100644
--- a/cubed/tests/runtime/utils.py
+++ b/cubed/tests/runtime/utils.py
@@ -56,7 +56,7 @@ def deterministic_failure(path, timing_map, i, *, default_sleep=0.01, name=None)
     else:
         time.sleep(-timing_code)
         raise RuntimeError(
-            f"Deliberately fail on invocation number {invocation_count+1} for input {i}"
+            f"Deliberately fail on invocation number {invocation_count + 1} for input {i}"
         )
 
 
diff --git a/cubed/tests/test_array_api.py b/cubed/tests/test_array_api.py
index 327e6726..2ea3d9fe 100644
--- a/cubed/tests/test_array_api.py
+++ b/cubed/tests/test_array_api.py
@@ -696,6 +696,16 @@ def test_stack(spec, executor):
     )
 
 
+@pytest.mark.parametrize("repetitions", [(2,), (2, 5), (2, 5, 3)])
+def test_tile(spec, repetitions):
+    a = xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]], chunks=(2, 2), spec=spec)
+    b = xp.tile(a, repetitions)
+    assert_array_equal(
+        b.compute(),
+        np.tile(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), repetitions),
+    )
+
+
 @pytest.mark.parametrize("chunks", [(1, 2, 3), (2, 2, 3), (3, 2, 3)])
 def test_unstack(spec, executor, chunks):
     a = xp.full((4, 6), 1, chunks=(2, 3), spec=spec)