Skip to content
Open
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
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,18 @@ jobs:
matrix:
platform: [linux-x64, linux-arm, macos, windows]
environment:
- tests-py311
- tests-py312
- tests-py314
- tests-numpy1
- tests-nogil
- tests-backends
- tests-backends-py311
- tests-backends-py312
- tests-run-deps
include:
# Full x64 image is faster than ubuntu-slim for the slowest jobs
- {environment: tests-py311, x64_runner: ubuntu-latest}
- {environment: tests-py312, x64_runner: ubuntu-latest}
- {environment: tests-backends, x64_runner: ubuntu-latest}
- {environment: tests-backends-py311, x64_runner: ubuntu-latest}
- {environment: tests-backends-py312, x64_runner: ubuntu-latest}
# Non-default pixi task (default: tests-ci)
- {
environment: tests-nogil,
Expand Down
8,109 changes: 4,898 additions & 3,211 deletions pixi.lock

Large diffs are not rendered by default.

32 changes: 17 additions & 15 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ requires-pixi = ">=0.82.1" # security update
exclude-newer = "7d"

[exclude-newer]
mparray = "5d"
jax = "1d"
jaxlib = "1d"

### array-api-extra package definition ###

Expand All @@ -34,8 +35,8 @@ default = {
solve-group = "py314",
}
lint = {
features = ["py311", "lint"],
solve-group = "py311",
features = ["py312", "lint"],
solve-group = "py312",
}
docs = {
features = ["py314", "docs"],
Expand All @@ -50,9 +51,9 @@ tests-py314 = { # alias of tests
features = ["py314", "tests"],
solve-group = "py314",
}
tests-py311 = {
features = ["py311", "tests"],
solve-group = "py311",
tests-py312 = {
features = ["py312", "tests"],
solve-group = "py312",
}
tests-run-deps = {
features = ["py314", "tests-run-deps"],
Expand All @@ -68,7 +69,7 @@ tests-backends = {
features = ["py314", "tests", "backends"],
solve-group = "backends",
}
tests-backends-py311.features = ["py311", "tests", "backends"]
tests-backends-py312.features = ["py312", "tests", "backends"]

# CUDA not available on free github actions and on some developers' PCs
dev-cuda = {
Expand All @@ -79,10 +80,10 @@ tests-cuda = {
features = ["py314", "tests", "backends", "cuda-backends"],
solve-group = "cuda",
}
tests-cuda-py311.features = ["py311", "tests", "backends", "cuda-backends"]
tests-cuda-py312.features = ["py312", "tests", "backends", "cuda-backends"]

# Ungrouped environments
tests-numpy1 = ["py311", "tests", "numpy1"]
tests-numpy1 = ["py312", "tests", "numpy1"]
tests-nogil = ["py314t", "nogil", "tests"]

### default feature definition ###
Expand Down Expand Up @@ -353,8 +354,8 @@ ipython = {
default-environment = "dev",
}

[feature.py311.dependencies]
python = "~=3.11.0"
[feature.py312.dependencies]
python = "~=3.12.0"

[feature.py314.dependencies]
python = "~=3.14.0"
Expand All @@ -365,18 +366,19 @@ python-freethreading = "~=3.14.0"
[feature.numpy1.dependencies]
# Oldest NumPy version supported by scikit-learn.
# Note that this is older than what SPEC0 recommends.
numpy = "=1.24.1"
numpy = "=1.26.0"
scipy = "1.16.*" # compatible ith the pinned numpy version

# Backends that can run on CPU-only hosts
# Note: JAX and PyTorch will install CPU variants.
[feature.backends.dependencies]
pytorch = ">=2.12.0"
pytorch = ">=2.12.1"
dask-core = ">=2026.8.0" # No distributed, tornado, etc.
sparse = ">=0.19.2"
mparray = ">=0.2.2"

[feature.backends.target.unix.dependencies]
jax = ">=0.10.2" # waiting for conda-forge/jaxlib-feedstock#326
jax = ">=0.11.2"

# Backends that require a GPU host and a CUDA driver.
[feature.cuda-backends]
Expand All @@ -388,7 +390,7 @@ cupy = ">=14.2.0"
pytorch = ">=2.12.0"

[feature.cuda-backends.target.linux.dependencies]
jaxlib = ">=0.10.2"
jaxlib = ">=0.11.2"

[feature.nogil.dependencies]
pytest-run-parallel = ">=0.10.0"
Expand Down
10 changes: 3 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "array-api-extra"
version = "0.11.5.dev0"
description = "Extra array functions built on top of the array API standard."
readme = "README.md"
requires-python = ">=3.11"
requires-python = ">=3.12"
license = "MIT"
license-files = ["LICENSE"]
authors = [
Expand All @@ -16,7 +16,6 @@ classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
Expand All @@ -37,7 +36,7 @@ build-backend = "mesonpy"
[tool.basedpyright]
include = ["src", "tests"]
exclude = ["tests/vendoring/array_api_compat"]
pythonVersion = "3.11"
pythonVersion = "3.12"
pythonPlatform = "All"
typeCheckingMode = "all"

Expand Down Expand Up @@ -72,7 +71,7 @@ run.source = ["array_api_extra"]

[tool.mypy]
files = ["src", "tests"]
python_version = "3.11"
python_version = "3.12"
warn_unused_configs = true
strict = true
enable_error_code = ["ignore-without-code", "truthy-bool"]
Expand Down Expand Up @@ -156,9 +155,6 @@ minversion = "6.0"
testpaths = ["tests"]
xfail_strict = true

[tool.ruff]
target-version = "py311"

[tool.ruff.lint]
extend-select = [
"B", # flake8-bugbear
Expand Down
10 changes: 4 additions & 6 deletions src/array_api_extra/_lazy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@
import math
import typing
from collections.abc import Callable, Sequence
from typing import Any, ParamSpec
from typing import Any

from . import _agnostic
from ._lib import _compat, _helpers
from ._lib._typing import Array, ArrayLike, ArrayNamespace, DType

P = ParamSpec("P")

__all__ = ["lazy_apply"]


@typing.overload # pyrefly: ignore[invalid-param-spec]
def lazy_apply( # type: ignore[valid-type]
def lazy_apply[**P]( # type: ignore[valid-type]
func: Callable[P, Array | ArrayLike],
*args: Array | complex | None,
shape: tuple[int | None, ...] | None = None,
Expand All @@ -28,7 +26,7 @@ def lazy_apply( # type: ignore[valid-type]


@typing.overload # pyrefly: ignore[invalid-param-spec]
def lazy_apply( # type: ignore[valid-type]
def lazy_apply[**P]( # type: ignore[valid-type]
func: Callable[P, Sequence[Array | ArrayLike]],
*args: Array | complex | None,
shape: Sequence[tuple[int | None, ...]],
Expand All @@ -39,7 +37,7 @@ def lazy_apply( # type: ignore[valid-type]
) -> tuple[Array, ...]: ... # numpydoc ignore=GL08


def lazy_apply( # type: ignore[valid-type] # pyrefly: ignore[invalid-param-spec] # numpydoc ignore=GL07,SA04
def lazy_apply[**P]( # type: ignore[valid-type] # pyrefly: ignore[invalid-param-spec] # numpydoc ignore=GL07,SA04
func: Callable[P, Array | ArrayLike | Sequence[Array | ArrayLike]],
*args: Array | complex | None,
shape: tuple[int | None, ...] | Sequence[tuple[int | None, ...]] | None = None,
Expand Down
8 changes: 3 additions & 5 deletions src/array_api_extra/_lib/_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
import numpy as np
import pytest

__all__ = ["NUMPY_VERSION", "Backend"]

NUMPY_VERSION = tuple(int(v) for v in np.__version__.split(".")[:3])
__all__ = ["Backend"]


class Backend(Enum): # numpydoc ignore=PR02
Expand Down Expand Up @@ -63,8 +61,8 @@ def pytest_param(self) -> Any:
if self.like(Backend.ARRAY_API_STRICT):
marks.append(
pytest.mark.skipif(
NUMPY_VERSION < (1, 26),
reason="array_api_strict is untested on NumPy <1.26",
np.lib.NumpyVersion(np.__version__) < "2.0.0",
reason="array_api_strict is untested on NumPy <2.0",
)
)
if self.like(Backend.DASK, Backend.JAX):
Expand Down
10 changes: 4 additions & 6 deletions src/array_api_extra/_lib/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
from typing import (
Any,
ClassVar,
Generic,
Literal,
ParamSpec,
TypeAlias,
TypeVar,
)

Expand Down Expand Up @@ -332,10 +330,10 @@ def capabilities(xp: ArrayNamespace, x: Array | None = None) -> dict[str, int |
type, types.BuiltinFunctionType, types.FunctionType, types.ModuleType
)) # fmt: skip

FlattenRest: TypeAlias = tuple[object, ...]
type FlattenRest = tuple[object, ...]


def pickle_flatten(
def pickle_flatten[T](
obj: object, cls: type[T] | tuple[type[T], ...]
) -> tuple[list[T], FlattenRest]:
"""
Expand Down Expand Up @@ -480,7 +478,7 @@ def persistent_load(self, pid: Literal[0, 1]) -> object: # numpydoc ignore=GL08
return Unpickler(f).load()


class _AutoJITWrapper(Generic[T]): # numpydoc ignore=PR01
class _AutoJITWrapper[T]: # numpydoc ignore=PR01
"""
Helper of :func:`jax_autojit`.

Expand Down Expand Up @@ -524,7 +522,7 @@ def _register(cls) -> None: # numpydoc ignore=SS06
cls._registered = True


def jax_autojit(
def jax_autojit[**P, T](
func: Callable[P, T],
) -> Callable[P, T]: # numpydoc ignore=PR01,RT01,SS03
"""
Expand Down
14 changes: 6 additions & 8 deletions src/array_api_extra/_lib/_typing.pyi
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
"""Static typing helpers."""

from types import EllipsisType, ModuleType
from typing import Any, Protocol, TypeAlias
from typing import Any, Protocol, override

import numpy as np
from dask.typing import Graph, Key, SchedulerGetCallable
from numpy.typing import ArrayLike

# TODO import from typing (requires Python >=3.12 and >=3.13)
from typing_extensions import TypeIs, override
from typing_extensions import TypeIs

# TODO: use array-api-typing once it is available

ArrayNamespace: TypeAlias = ModuleType
type ArrayNamespace = ModuleType

class Array(Protocol): # pylint: disable=missing-class-docstring
# Unary operations
Expand Down Expand Up @@ -98,13 +98,11 @@ class DType(Protocol): # pylint: disable=missing-class-docstring
class Device(Protocol): # pylint: disable=missing-class-docstring
...

SetIndex: TypeAlias = (
type SetIndex = (
int | slice | EllipsisType | Array | tuple[int | slice | EllipsisType | Array, ...]
)
GetIndex: TypeAlias = (
SetIndex | tuple[int | slice | EllipsisType | Array | None, ...] | None
)
NumPyObject: TypeAlias = np.ndarray[Any, Any] | np.generic
type GetIndex = SetIndex | tuple[int | slice | EllipsisType | Array | None, ...] | None
type NumPyObject = np.ndarray[Any, Any] | np.generic

__all__ = [
"Array",
Expand Down
7 changes: 2 additions & 5 deletions src/array_api_extra/testing/_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import warnings
from collections.abc import Callable, Generator, Iterator, Sequence
from types import FunctionType, ModuleType
from typing import Any, ParamSpec, TypeVar
from typing import Any

from .._lib import _compat, _helpers
from .._lib._typing import (
Expand All @@ -38,9 +38,6 @@
]


P = ParamSpec("P")
T = TypeVar("T")

_ufuncs_tags: dict[object, dict[str, Any]] = {}


Expand Down Expand Up @@ -495,7 +492,7 @@ def __call__(
return dask.get(dsk, keys, **kwargs) # type: ignore[attr-defined] # pyright: ignore[reportPrivateImportUsage]


def _dask_wrap(
def _dask_wrap[**P, T](
func: Callable[P, T], n: int
) -> Callable[P, T]: # numpydoc ignore=PR01,RT01
"""
Expand Down
4 changes: 2 additions & 2 deletions tests/main/test_elementwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
rad2deg,
sinc,
)
from array_api_extra._lib._backends import NUMPY_VERSION, Backend
from array_api_extra._lib._backends import Backend
from array_api_extra._lib._compat import device as get_device
from array_api_extra._lib._typing import Array, ArrayNamespace, Device
from array_api_extra.testing import assert_close, assert_equal, lazy_xp_function
Expand Down Expand Up @@ -206,7 +206,7 @@ def test_hypothesis(
):
if (
library.like(Backend.NUMPY)
and NUMPY_VERSION < (2, 0)
and np.lib.NumpyVersion(np.__version__) < "2.0.0"
and dtype.type is np.float32
):
pytest.xfail(reason="NumPy 1.x dtype promotion for scalars")
Expand Down
6 changes: 2 additions & 4 deletions tests/main/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections.abc import Iterator
from typing import Generic, TypeVar, cast
from typing import cast

import numpy as np
import pytest
Expand All @@ -23,8 +23,6 @@

from .conftest import np_compat

T = TypeVar("T")

# FIXME calls xp.unique_values without size
lazy_xp_function(in1d, jax_jit=False)

Expand Down Expand Up @@ -227,7 +225,7 @@ def test_device(self, xp: ArrayNamespace, library: Backend, device: Device):
assert actual == expect


class Wrapper(Generic[T]): # noqa: PLW1641
class Wrapper[T]: # noqa: PLW1641
"""Trivial opaque wrapper. Must be pickleable."""

x: T
Expand Down
2 changes: 1 addition & 1 deletion tests/main/test_searching.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def test_nd(
x[mask] = -np.inf
mask = rng.random(shape) > 0.9
x[mask] = np.inf
x = np.sort(x, axis=-1) # type:ignore[assignment]
x = np.sort(x, axis=-1)
x, y = np.asarray(x, dtype=np.float64), np.asarray(y, dtype=np.float64)
xp_default_int = default_dtype(xp, kind="integral")
if x.size == 0 and x.ndim > 0 and x.shape[-1] != 0:
Expand Down
Loading
Loading