Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion .github/workflows/array-api-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
uses: actions/checkout@v3
with:
repository: data-apis/array-api-tests
ref: '2025.05.23'
ref: '2026.02.26'
path: array-api-tests
submodules: "true"
- name: Set up Python ${{ matrix.python-version }}
Expand Down Expand Up @@ -136,9 +136,12 @@ jobs:
array_api_tests/test_array_object.py::test_getitem_arrays_and_ints_2
# test_searchsorted depends on sort which is not implemented
array_api_tests/test_searching_functions.py::test_searchsorted
array_api_tests/test_searching_functions.py::test_searchsorted_with_scalars
# cumulative_* functions with include_initial=True are not implemented
array_api_tests/test_statistical_functions.py::test_cumulative_prod
array_api_tests/test_statistical_functions.py::test_cumulative_sum
# repeated axis not supported
array_api_tests/test_manipulation_functions.py::TestExpandDims::test_expand_dims_tuples

# not implemented
array_api_tests/test_array_object.py::test_setitem
Expand All @@ -160,6 +163,24 @@ jobs:
# https://github.com/numpy/numpy/issues/20870
#array_api_tests/test_data_type_functions.py::test_can_cast

# complex special cases, see also https://github.com/data-apis/array-api-compat/blob/main/numpy-xfails.txt
array_api_tests/test_special_cases.py::test_unary[acosh(real(x_i) is +0 and imag(x_i) is NaN) -> NaN \xb1 \u03c0j/2]
array_api_tests/test_special_cases.py::test_unary[atanh(isfinite(real(x_i)) and real(x_i) != 0 and imag(x_i) is NaN) -> NaN + NaN j]
array_api_tests/test_special_cases.py::test_unary[atanh(real(x_i) is +0 and imag(x_i) is NaN) -> +0 + NaN j]
array_api_tests/test_special_cases.py::test_unary[atanh(real(x_i) is 1 and imag(x_i) is +0) -> +infinity + 0j]
array_api_tests/test_special_cases.py::test_unary[atanh(real(x_i) is +infinity and imag(x_i) is NaN) -> +0 + NaN j]
array_api_tests/test_special_cases.py::test_unary[atanh(real(x_i) is NaN and imag(x_i) is +infinity) -> \xb10 + \u03c0j/2]
array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is +infinity and imag(x_i) is +0) -> +infinity + 0j]
array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is -infinity and imag(x_i) is +infinity) -> -1 + 0j]
array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is +infinity and imag(x_i) is +infinity) -> infinity + NaN j]
array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is -infinity and imag(x_i) is NaN) -> -1 + 0j]
array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is +infinity and imag(x_i) is NaN) -> infinity + NaN j]
array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is NaN and imag(x_i) is +0) -> NaN + 0j]
array_api_tests/test_special_cases.py::test_unary[expm1((real(x_i) is +0 or real(x_i) == -0) and imag(x_i) is +0) -> 0 + 0j]
array_api_tests/test_special_cases.py::test_unary[tanh(real(x_i) is +0 and imag(x_i) is +infinity) -> +0 + NaN j]
array_api_tests/test_special_cases.py::test_unary[tanh(real(x_i) is +0 and imag(x_i) is NaN) -> +0 + NaN j]
array_api_tests/test_special_cases.py::test_unary[tanh(real(x_i) is +infinity and isfinite(imag(x_i)) and imag(x_i) > 0) -> 1 + 0j]

EOF

CUBED_BACKEND_ARRAY_API_MODULE=array_api_compat.numpy pytest -v -rxXfEA --hypothesis-max-examples=2 --disable-data-dependent-shapes --disable-extension linalg --hypothesis-disable-deadline
10 changes: 7 additions & 3 deletions api_status.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Array API Coverage Implementation Status

Cubed supports version [2024.12](https://data-apis.org/array-api/2024.12/index.html) of the Python array API standard, with a few exceptions noted below. The [Fourier transform functions](https://data-apis.org/array-api/2024.12/extensions/fourier_transform_functions.html) are *not* supported.
Cubed supports version [2025.12](https://data-apis.org/array-api/2025.12/index.html) of the Python array API standard, with a few exceptions noted below. The [Fourier transform functions](https://data-apis.org/array-api/2025.12/extensions/fourier_transform_functions.html) are *not* supported.

This table shows which parts of the the [Array API](https://data-apis.org/array-api/latest/API_specification/index.html) have been implemented in Cubed, and which ones are missing. The version column shows the version when the feature was added to the standard, for version 2022.12 or later.

Expand Down Expand Up @@ -55,6 +55,7 @@ This table shows which parts of the the [Array API](https://data-apis.org/array-
| | `tensordot` | :white_check_mark: | | |
| | `vecdot` | :white_check_mark: | | |
| Manipulation Functions | `broadcast_arrays` | :white_check_mark: | | |
| | `broadcast_shapes` | :white_check_mark: | 2025.12 | |
| | `broadcast_to` | :white_check_mark: | | |
| | `concat` | :white_check_mark: | | |
| | `expand_dims` | :white_check_mark: | | |
Expand All @@ -73,7 +74,8 @@ This table shows which parts of the the [Array API](https://data-apis.org/array-
| | `nonzero` | :x: | | Shape is data dependent |
| | `searchsorted` | :white_check_mark: | 2023.12 | |
| | `where` | :white_check_mark: | | |
| Set Functions | `unique_all` | :x: | | Shape is data dependent |
| Set Functions | `isin` | :white_check_mark: | 2025.12 | |
| | `unique_all` | :x: | | Shape is data dependent |
| | `unique_counts` | :x: | | Shape is data dependent |
| | `unique_inverse` | :x: | | Shape is data dependent |
| | `unique_values` | :x: | | Shape is data dependent |
Expand All @@ -94,15 +96,17 @@ This table shows which parts of the the [Array API](https://data-apis.org/array-

### Linear Algebra Extension

A few of the [linear algebra extension](https://data-apis.org/array-api/2022.12/extensions/linear_algebra_functions.html) functions are supported, as indicated in this table.
A few of the [linear algebra extension](https://data-apis.org/array-api/2025.12/extensions/linear_algebra_functions.html) functions are supported, as indicated in this table.

| Category | Object/Function | Implemented | Version | Notes |
| ------------------------ | ------------------- | ------------------ | ---------- | ---------------------------- |
| Linear Algebra Functions | `cholesky` | :x: | | |
| | `cross` | :x: | | |
| | `det` | :x: | | |
| | `diagonal` | :x: | | |
| | `eig` | :x: | | |
| | `eigh` | :x: | | |
| | `eigvals` | :x: | | |
| | `eigvalsh` | :x: | | |
| | `inv` | :x: | | |
| | `matmul` | :white_check_mark: | | |
Expand Down
4 changes: 3 additions & 1 deletion cubed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@

# Array API

__array_api_version__ = "2024.12"
__array_api_version__ = "2025.12"

from .array_api.inspection import __array_namespace_info__

Expand Down Expand Up @@ -301,6 +301,7 @@

from .array_api.manipulation_functions import (
broadcast_arrays,
broadcast_shapes,
broadcast_to,
concat,
expand_dims,
Expand All @@ -318,6 +319,7 @@

__all__ += [
"broadcast_arrays",
"broadcast_shapes",
"broadcast_to",
"concat",
"expand_dims",
Expand Down
4 changes: 3 additions & 1 deletion cubed/array_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
__all__ = []

__array_api_version__ = "2024.12"
__array_api_version__ = "2025.12"

from .inspection import __array_namespace_info__

Expand Down Expand Up @@ -236,6 +236,7 @@

from .manipulation_functions import (
broadcast_arrays,
broadcast_shapes,
broadcast_to,
concat,
expand_dims,
Expand All @@ -253,6 +254,7 @@

__all__ += [
"broadcast_arrays",
"broadcast_shapes",
"broadcast_to",
"concat",
"expand_dims",
Expand Down
1 change: 1 addition & 0 deletions cubed/array_api/array_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ def __array_namespace__(self, /, *, api_version=None):
"2022.12",
"2023.12",
"2024.12",
"2025.12",
):
raise ValueError(f"Unrecognized array API version: {api_version!r}")
import cubed
Expand Down
2 changes: 1 addition & 1 deletion cubed/array_api/creation_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def meshgrid(*arrays, indexing="xy") -> List["Array"]:
if indexing == "xy" and len(arrs) > 1:
grid[0], grid[1] = grid[1], grid[0]

return grid
return tuple(grid)


def ones(shape, *, dtype=None, device=None, chunks="auto", spec=None) -> "Array":
Expand Down
7 changes: 6 additions & 1 deletion cubed/array_api/indexing_functions.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
def take(x, indices, /, *, axis):
def take(x, indices, /, *, axis=None):
from cubed.array_api.manipulation_functions import flatten

if axis is None:
x = flatten(x)
axis = 0
return x[(slice(None),) * axis + (indices,)]
9 changes: 6 additions & 3 deletions cubed/array_api/manipulation_functions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from bisect import bisect
from operator import add, mul

import numpy as np
import tlz
from toolz import reduce

Expand Down Expand Up @@ -41,14 +40,18 @@ def broadcast_arrays(*arrays):
uc_args = tlz.concat(zip(arrays, inds))
_, args = unify_chunks(*uc_args, warn=False)

shape = np.broadcast_shapes(*(e.shape for e in args))
shape = broadcast_shapes(*(e.shape for e in args))
chunks = broadcast_chunks(*(e.chunks for e in args))

result = tuple(broadcast_to(e, shape=shape, chunks=chunks) for e in args)

return result


def broadcast_shapes(*shapes):
return nxp.broadcast_shapes(*shapes)


def broadcast_to(x, /, shape, *, chunks=None):
if x.shape == shape and (chunks is None or chunks == x.chunks):
return x
Expand Down Expand Up @@ -272,7 +275,7 @@ def _chunk_slices(
arr_sel_offset += cp.out_selection[axis].stop


def expand_dims(x, /, *, axis):
def expand_dims(x, /, axis):
if not isinstance(axis, tuple):
axis = (axis,)
ndim_new = len(axis) + x.ndim
Expand Down
5 changes: 4 additions & 1 deletion cubed/array_api/searching_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ def searchsorted(x1, x2, /, *, side="left", sorter=None):
if x1.ndim != 1:
raise ValueError("Input array x1 must be one dimensional")

if isinstance(x2, int | float | complex):
x2 = asarray(x2, spec=x1.spec)

if sorter is not None:
raise NotImplementedError(
"searchsorted with a sorter argument is not supported"
Expand Down Expand Up @@ -97,7 +100,7 @@ def _searchsorted(x, y, side):
# of telling which block is being operated on (unlike map_blocks),
# so set all 0 values to a special value and set back at the end of searchsorted
res = nxp.where(res == 0, -1, res)
return res[nxp.newaxis, :]
return res[nxp.newaxis, ...]


def where(condition, x1, x2, /):
Expand Down
4 changes: 3 additions & 1 deletion cubed/core/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,8 +591,10 @@ def _general_blockwise(

def elemwise(func, *args: "Array", dtype=None) -> "Array":
"""Apply a function elementwise to array arguments, respecting broadcasting."""
from cubed.array_api.manipulation_functions import broadcast_shapes

shapes = [arg.shape for arg in args]
out_ndim = len(np.broadcast_shapes(*shapes))
out_ndim = len(broadcast_shapes(*shapes))
expr_inds = tuple(range(out_ndim))[::-1]
if dtype is None:
raise ValueError("dtype must be specified for elemwise")
Expand Down
22 changes: 19 additions & 3 deletions cubed/tests/test_array_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,10 +581,11 @@ def test_concat_incompatible_shapes(spec):
xp.concat([a, b], axis=1) # OK


def test_expand_dims(spec, executor):
@pytest.mark.parametrize("axis", [0, 1, (0, 1), (2, 0)])
def test_expand_dims(spec, axis):
a = xp.asarray([1, 2, 3], chunks=(2,), spec=spec)
b = xp.expand_dims(a, axis=0)
assert_array_equal(b.compute(executor=executor), np.expand_dims([1, 2, 3], 0))
b = xp.expand_dims(a, axis=axis)
assert_array_equal(b.compute(), np.expand_dims([1, 2, 3], axis))


@pytest.mark.parametrize(
Expand Down Expand Up @@ -826,6 +827,21 @@ def test_searchsorted(x1, x1_chunks, x2, x2_chunks, side):
assert_array_equal(out.compute(), np.searchsorted(x1, x2, side=side))


@pytest.mark.parametrize("side", ["left", "right"])
def test_searchsorted_scalar(side):
x1 = np.array([-10, 0, 10, 20, 30])
x2 = 11

x1d = xp.asarray(x1, chunks=3)
x2d = x2

out = xp.searchsorted(x1d, x2d, side=side)

assert out.shape == ()
assert out.chunks == ()
assert_array_equal(out.compute(), np.searchsorted(x1, x2, side=side))


def test_searchsorted_sorter_not_implemented():
with pytest.raises(NotImplementedError):
xp.searchsorted(xp.asarray([1, 0]), xp.asarray([1]), sorter=xp.asarray([1, 0]))
Expand Down
4 changes: 2 additions & 2 deletions cubed/vendor/dask/array/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import numbers

import numpy as np

from numpy.exceptions import AxisError

def validate_axis(axis, ndim):
"""Validate an input to axis= keywords"""
Expand All @@ -10,7 +10,7 @@ def validate_axis(axis, ndim):
if not isinstance(axis, numbers.Integral):
raise TypeError("Axis value must be an integer, got %s" % axis)
if axis < -ndim or axis >= ndim:
raise np.AxisError(
raise AxisError(
"Axis %d is out of bounds for array of dimension %d" % (axis, ndim)
)
if axis < 0:
Expand Down
2 changes: 1 addition & 1 deletion docs/array-api.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Python Array API

Cubed implements version 2024.12 of the [Python Array API standard](https://data-apis.org/array-api/2024.12/index.html) in `cubed.array_api`, with a few exceptions listed on the [coverage status](https://github.com/cubed-dev/cubed/blob/main/api_status.md) page. The [Fourier transform functions](https://data-apis.org/array-api/2024.12/extensions/fourier_transform_functions.html) are *not* supported.
Cubed implements version 2025.12 of the [Python Array API standard](https://data-apis.org/array-api/2025.12/index.html) in `cubed.array_api`, with a few exceptions listed on the [coverage status](https://github.com/cubed-dev/cubed/blob/main/api_status.md) page. The [Fourier transform functions](https://data-apis.org/array-api/2025.12/extensions/fourier_transform_functions.html) are *not* supported.

## Differences between Cubed and the standard

Expand Down
Loading