diff --git a/array_api_compat/cupy/_aliases.py b/array_api_compat/cupy/_aliases.py index 2e512fc8..f91805f2 100644 --- a/array_api_compat/cupy/_aliases.py +++ b/array_api_compat/cupy/_aliases.py @@ -1,6 +1,7 @@ from __future__ import annotations from builtins import bool as py_bool +from typing import Literal import cupy as cp @@ -139,6 +140,15 @@ def take_along_axis(x: Array, indices: Array, /, *, axis: int = -1) -> Array: return cp.take_along_axis(x, indices, axis=axis) +# https://github.com/cupy/cupy/pull/9582 +def broadcast_arrays(*arrays: Array) -> tuple[Array, ...]: + return tuple(cp.broadcast_arrays(*arrays)) + + +def meshgrid(*arrays: Array, indexing: Literal['xy', 'ij'] = 'xy') -> tuple[Array, ...]: + return tuple(cp.meshgrid(*arrays, indexing=indexing)) + + # These functions are completely new here. If the library already has them # (i.e., numpy 2.0), use the library version instead of our wrapper. if hasattr(cp, 'vecdot'): @@ -161,7 +171,8 @@ def take_along_axis(x: Array, indices: Array, /, *, axis: int = -1) -> Array: 'atan2', 'atanh', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_right_shift', 'bool', 'concat', 'count_nonzero', 'pow', 'sign', - 'ceil', 'floor', 'trunc', 'take_along_axis'] + 'ceil', 'floor', 'trunc', 'take_along_axis', + 'broadcast_arrays', 'meshgrid'] def __dir__() -> list[str]: diff --git a/array_api_compat/cupy/_info.py b/array_api_compat/cupy/_info.py index 78e48a33..aef10e85 100644 --- a/array_api_compat/cupy/_info.py +++ b/array_api_compat/cupy/_info.py @@ -333,4 +333,4 @@ def devices(self): __array_namespace_info__.dtypes """ - return [cuda.Device(i) for i in range(cuda.runtime.getDeviceCount())] + return tuple(cuda.Device(i) for i in range(cuda.runtime.getDeviceCount())) diff --git a/array_api_compat/dask/array/_info.py b/array_api_compat/dask/array/_info.py index 2f39fc4b..3a7285d5 100644 --- a/array_api_compat/dask/array/_info.py +++ b/array_api_compat/dask/array/_info.py @@ -379,7 +379,7 @@ def dtypes( return res raise ValueError(f"unsupported kind: {kind!r}") - def devices(self) -> list[Device]: + def devices(self) -> tuple[Device]: """ The devices supported by Dask. @@ -404,4 +404,4 @@ def devices(self) -> list[Device]: ['cpu', DASK_DEVICE] """ - return ["cpu", _DASK_DEVICE] + return ("cpu", _DASK_DEVICE) diff --git a/array_api_compat/numpy/_info.py b/array_api_compat/numpy/_info.py index c625c13e..9ba004da 100644 --- a/array_api_compat/numpy/_info.py +++ b/array_api_compat/numpy/_info.py @@ -332,7 +332,7 @@ def dtypes( return res raise ValueError(f"unsupported kind: {kind!r}") - def devices(self) -> list[Device]: + def devices(self) -> tuple[Device]: """ The devices supported by NumPy. @@ -357,7 +357,7 @@ def devices(self) -> list[Device]: ['cpu'] """ - return ["cpu"] + return ("cpu",) __all__ = ["__array_namespace_info__"] diff --git a/array_api_compat/torch/_aliases.py b/array_api_compat/torch/_aliases.py index 4a7e9c00..27f0a263 100644 --- a/array_api_compat/torch/_aliases.py +++ b/array_api_compat/torch/_aliases.py @@ -711,9 +711,9 @@ def astype( return x.to(dtype=dtype, copy=copy) -def broadcast_arrays(*arrays: Array) -> list[Array]: +def broadcast_arrays(*arrays: Array) -> tuple[Array, ...]: shape = torch.broadcast_shapes(*[a.shape for a in arrays]) - return [torch.broadcast_to(a, shape) for a in arrays] + return tuple(torch.broadcast_to(a, shape) for a in arrays) # Note that these named tuples aren't actually part of the standard namespace, # but I don't see any issue with exporting the names here regardless. @@ -897,10 +897,11 @@ def sign(x: Array, /) -> Array: return out -def meshgrid(*arrays: Array, indexing: Literal['xy', 'ij'] = 'xy') -> list[Array]: - # enforce the default of 'xy' - # TODO: is the return type a list or a tuple - return list(torch.meshgrid(*arrays, indexing=indexing)) +def meshgrid(*arrays: Array, indexing: Literal['xy', 'ij'] = 'xy') -> tuple[Array, ...]: + # torch <= 2.9 emits a UserWarning: "torch.meshgrid: in an upcoming release, it + # will be required to pass the indexing argument." + # Thus always pass it explicitly. + return torch.meshgrid(*arrays, indexing=indexing) __all__ = ['asarray', 'result_type', 'can_cast', diff --git a/array_api_compat/torch/_info.py b/array_api_compat/torch/_info.py index 818e5d37..050c7846 100644 --- a/array_api_compat/torch/_info.py +++ b/array_api_compat/torch/_info.py @@ -366,4 +366,4 @@ def devices(self): break i += 1 - return devices + return tuple(devices) diff --git a/dask-xfails.txt b/dask-xfails.txt index 5ce24254..4d5a89af 100644 --- a/dask-xfails.txt +++ b/dask-xfails.txt @@ -129,6 +129,13 @@ array_api_tests/test_linalg.py::test_matrix_norm array_api_tests/test_linalg.py::test_qr array_api_tests/test_manipulation_functions.py::test_roll +# 2025.12 support +array_api_tests/test_has_names.py::test_has_names[manipulation-broadcast_shapes] +array_api_tests/test_signatures.py::test_func_signature[broadcast_shapes] +array_api_tests/test_data_type_functions.py::TestBroadcastShapes::test_broadcast_shapes +array_api_tests/test_data_type_functions.py::TestBroadcastShapes::test_empty +array_api_tests/test_data_type_functions.py::TestBroadcastShapes::test_error + # Stubs have a comment: (**note**: libraries may return ``NaN`` to match Python behavior.) array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i > 0) -> +infinity] array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i < 0) -> -infinity] diff --git a/numpy-1-22-xfails.txt b/numpy-1-22-xfails.txt index e2df8b47..c2e13d35 100644 --- a/numpy-1-22-xfails.txt +++ b/numpy-1-22-xfails.txt @@ -152,6 +152,13 @@ array_api_tests/test_signatures.py::test_func_signature[bitwise_right_shift] array_api_tests/test_signatures.py::test_func_signature[bitwise_xor] array_api_tests/test_data_type_functions.py::TestResultType::test_with_scalars +# 2025.12 support + +# older numpies return lists not tuples +array_api_tests/test_creation_functions.py::test_meshgrid +array_api_tests/test_data_type_functions.py::test_broadcast_arrays + + # Stubs have a comment: (**note**: libraries may return ``NaN`` to match Python behavior.); Apparently,NumPy does just that array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i > 0) -> +infinity] array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i < 0) -> -infinity] diff --git a/numpy-1-26-xfails.txt b/numpy-1-26-xfails.txt index 407cc531..f09be4a4 100644 --- a/numpy-1-26-xfails.txt +++ b/numpy-1-26-xfails.txt @@ -42,9 +42,15 @@ array_api_tests/test_signatures.py::test_array_method_signature[__dlpack__] # 2024.12 support array_api_tests/test_data_type_functions.py::TestResultType::test_with_scalars - array_api_tests/test_operators_and_elementwise_functions.py::test_where_with_scalars +# 2025.12 support + +# older numpies return lists not tuples +array_api_tests/test_creation_functions.py::test_meshgrid +array_api_tests/test_data_type_functions.py::test_broadcast_arrays + + # Stubs have a comment: (**note**: libraries may return ``NaN`` to match Python behavior.); Apparently, NumPy does just that array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i > 0) -> +infinity] array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i < 0) -> -infinity] diff --git a/torch-xfails.txt b/torch-xfails.txt index 3c2a0028..84271a56 100644 --- a/torch-xfails.txt +++ b/torch-xfails.txt @@ -144,6 +144,11 @@ array_api_tests/test_signatures.py::test_func_signature[from_dlpack] # Argument 'max_version' missing from signature array_api_tests/test_signatures.py::test_array_method_signature[__dlpack__] +# 2025.12 support + +# broadcast_shapes emits a RuntimeError where the spec says ValueError +array_api_tests/test_data_type_functions.py::TestBroadcastShapes::test_error + # 2024.12 support: binary functions reject python scalar arguments array_api_tests/test_operators_and_elementwise_functions.py::test_binary_with_scalars_real[atan2]