Skip to content

Commit

Permalink
ci: fix oldest-supported-dependencies test workflow (#2783)
Browse files Browse the repository at this point in the history
* ci: perform installs in single pass

* fix: support older NumPY

* test: check NEP-50 vs non NEP-50 behavior

* fix: properly broadcast np.generic objects

* test: add test
  • Loading branch information
agoose77 authored Oct 30, 2023
1 parent 3f623ee commit 5665d85
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 127 deletions.
87 changes: 33 additions & 54 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,16 @@ jobs:
python -m pip install build
python -m build -w awkward-cpp
- name: Install awkward-cpp
run: python -m pip install -v @(get-childitem -path awkward-cpp/dist/*.whl)

- name: Build & install awkward
run: python -m pip install -v .
- name: Install awkward, awkward-cpp, and dependencies
run: >-
python -m pip install --only-binary "numpy,pandas,pyarrow,numexpr,numexpr"
-v . @(get-childitem -path awkward-cpp/dist/*.whl)
pytest-github-actions-annotate-failures
-r requirements-test.txt
- name: Print versions
run: python -m pip list

- name: Check if kernel specification is sorted
run: pipx run nox -s diagnostics -- --check-spec-sorted

- name: Install test requirements
run: python -m pip install -v -r requirements-test.txt pytest-github-actions-annotate-failures

- name: Test specification
if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true'
run: python -m pytest -vv -rs awkward-cpp/tests-spec
Expand Down Expand Up @@ -156,21 +151,17 @@ jobs:
python -m pip install build
python -m build -w ./awkward-cpp
- name: Install awkward-cpp
run: python -m pip install -v ./awkward-cpp/dist/*.whl

- name: Build & install awkward
run: python -m pip install -v .
- name: Install awkward, awkward-cpp, dask-awkward, and dependencies
run: >-
python -m pip install --only-binary "numpy,pandas,pyarrow,numexpr"
-v . ./awkward-cpp/dist/*.whl
pytest-github-actions-annotate-failures
dask-awkward
-r requirements-test.txt
- name: Print versions
run: python -m pip list

- name: Check if kernel specification is sorted
run: pipx run nox -s diagnostics -- --check-spec-sorted

- name: Install test requirements
run: python -m pip install -v -r requirements-test.txt pytest-github-actions-annotate-failures

- name: Test specification
if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true'
run: python -m pytest -vv -rs awkward-cpp/tests-spec
Expand All @@ -190,24 +181,16 @@ jobs:
strategy:
matrix:
python-version:
- '3.12'
- '3.11'
- '3.10'
- '3.9'
- '3.8'
numpy-package:
- "numpy"
pyarrow-package:
- "pyarrow"
extra-pip-constraints:
- "-r requirements-test.txt"
include:
# Lower bounds
- python-version: '3.8'
numpy-package: "numpy==1.18.0"
pyarrow-package: "pyarrow"
- python-version: '3.8'
numpy-package: "numpy"
pyarrow-package: "pyarrow==7.0.0"
- python-version: '3.12'
numpy-package: "numpy>=1.26.0b1"
pyarrow-package: "pyarrow;python_version<'3.12'"
extra-pip-constraints: "-r requirements-test-minimal.txt"

runs-on: ubuntu-22.04

Expand Down Expand Up @@ -246,21 +229,21 @@ jobs:
python -m pip install build
python -m build -w ./awkward-cpp
- name: Install awkward-cpp
run: python -m pip install -v ./awkward-cpp/dist/*.whl "${{ matrix.numpy-package }}" "${{ matrix.pyarrow-package }}"

- name: Build & install awkward
run: python -m pip install -v .
- name: Install awkward, awkward-cpp, and dependencies
run: >-
python -m pip install --only-binary "numpy,pandas,pyarrow,numexpr"
-v . ./awkward-cpp/dist/*.whl
pytest-github-actions-annotate-failures
${{ matrix.extra-pip-constraints }}
- name: Print versions
run: python -m pip list

- name: Check if kernel specification is sorted
# We don't need to run this all the time
if: matrix.python-version == '3.12'
run: pipx run nox -s diagnostics -- --check-spec-sorted

- name: Install test requirements
run: python -m pip install -v -r requirements-test.txt pytest-github-actions-annotate-failures

- name: Test specification
if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true'
run: python -m pytest -vv -rs awkward-cpp/tests-spec
Expand Down Expand Up @@ -332,20 +315,16 @@ jobs:
python3 -m pip install build
python3 -m build -w ./awkward-cpp
- name: Install awkward-cpp
run: python3 -m pip install -v ./awkward-cpp/dist/*.whl

- name: Build & install awkward
run: python3 -m pip install -v .

- name: Also install dask-awkward
run: python3 -m pip install dask-awkward
- name: Install awkward, awkward-cpp, dask-awkward, and dependencies
run: >-
python -m pip install --only-binary "numpy,pandas,pyarrow,numexpr"
-v . ./awkward-cpp/dist/*.whl
pytest-github-actions-annotate-failures
dask-awkward
-r requirements-test.txt
- name: Print versions
run: python -m pip list

- name: Install test requirements
run: python -m pip install -v -r requirements-test.txt pytest-github-actions-annotate-failures

- name: Test
run: python -m pytest -vv -rs tests
6 changes: 6 additions & 0 deletions requirements-test-minimal.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fsspec;sys_platform != "win32"
numpy==1.18.0
pyarrow==7.0.0
pytest>=6
pytest-cov
pytest-xdist
4 changes: 2 additions & 2 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
fsspec;sys_platform != "win32"
jax[cpu]>=0.2.15;sys_platform != "win32" and python_version < "3.12"
numba>=0.50.0,!=0.58.0rc1;python_version < "3.12"
numexpr; python_version < "3.12"
numexpr>=2.7; python_version < "3.12"
pandas>=0.24.0;sys_platform != "win32" and python_version < "3.12"
pyarrow>=7.0.0;sys_platform != "win32" and python_version < "3.12"
pytest>=6
pytest-cov
pytest-xdist
uproot
uproot>=5
110 changes: 81 additions & 29 deletions src/awkward/_nplikes/array_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
from __future__ import annotations

import math
from functools import lru_cache

import numpy
import packaging.version

from awkward._nplikes.numpylike import (
ArrayLike,
Expand All @@ -18,6 +20,20 @@
from awkward._typing import Any, Final, Literal

np = NumpyMetadata.instance()
NUMPY_HAS_NEP_50 = packaging.version.Version(
numpy.__version__
) >= packaging.version.Version("1.24")


@lru_cache
def _nplike_concatenate_has_casting(module: Any) -> bool:
x = module.zeros(2)
try:
module.concatenate((x, x), casting="same_kind")
except TypeError:
return False
else:
return True


class ArrayModuleNumpyLike(NumpyLike):
Expand Down Expand Up @@ -128,12 +144,15 @@ def meshgrid(

def array_equal(
self, x1: ArrayLike, x2: ArrayLike, *, equal_nan: bool = False
) -> ArrayLike:
) -> bool:
assert not isinstance(x1, PlaceholderArray)
assert not isinstance(x2, PlaceholderArray)
return self._module.asarray(
self._module.array_equal(x1, x2, equal_nan=equal_nan)
)
if equal_nan:
both_nan = self._module.logical_and(x1 == np.nan, x2 == np.nan)
both_equal = x1 == x2
return self._module.all(self._module.logical_or(both_equal, both_nan))
else:
return self._module.array_equal(x1, x2)

def searchsorted(
self,
Expand All @@ -150,28 +169,57 @@ def searchsorted(

############################ manipulation

def apply_ufunc(
self,
ufunc: UfuncLike,
method: str,
args: list[Any],
kwargs: dict[str, Any] | None = None,
) -> ArrayLike | tuple[ArrayLike]:
# Determine input argument dtypes
input_arg_dtypes = [getattr(obj, "dtype", type(obj)) for obj in args]
# Resolve these for the given ufunc
arg_dtypes = tuple(input_arg_dtypes + [None] * ufunc.nout)
resolved_dtypes = ufunc.resolve_dtypes(arg_dtypes)
# Interpret the arguments under these dtypes
resolved_args = [
self.asarray(arg, dtype=dtype) for arg, dtype in zip(args, resolved_dtypes)
]
# Broadcast these resolved arguments
broadcasted_args = self.broadcast_arrays(*resolved_args)
# Allow other nplikes to replace implementation
impl = self.prepare_ufunc(ufunc)
# Compute the result
return impl(*broadcasted_args, **kwargs)
# Does NumPy support value-less ufunc resolution?
if NUMPY_HAS_NEP_50:

def apply_ufunc(
self,
ufunc: UfuncLike,
method: str,
args: list[Any],
kwargs: dict[str, Any] | None = None,
) -> ArrayLike | tuple[ArrayLike]:
# Determine input argument dtypes
input_arg_dtypes = [getattr(obj, "dtype", type(obj)) for obj in args]
# Resolve these for the given ufunc
arg_dtypes = tuple(input_arg_dtypes + [None] * ufunc.nout)
resolved_dtypes = ufunc.resolve_dtypes(arg_dtypes)
# Interpret the arguments under these dtypes, converting scalars to length-1 arrays
resolved_args = [
self.asarray(arg, dtype=dtype)
for arg, dtype in zip(args, resolved_dtypes)
]
# Broadcast to ensure all-scalar or all-nd-array
broadcasted_args = self.broadcast_arrays(*resolved_args)
# Allow other nplikes to replace implementation
impl = self.prepare_ufunc(ufunc)
# Compute the result
return impl(*broadcasted_args, **(kwargs or {}))

else:
# Otherwise, perform default NumPy coercion (value-dependent)
def apply_ufunc(
self,
ufunc: UfuncLike,
method: str,
args: list[Any],
kwargs: dict[str, Any] | None = None,
) -> ArrayLike | tuple[ArrayLike]:
# Convert np.generic to scalar arrays
resolved_args = [
self.asarray(arg, dtype=arg.dtype) if hasattr(arg, "dtype") else arg
for arg in args
]
broadcasted_args = self.broadcast_arrays(*resolved_args)
# Choose the broadcasted argument if it wasn't a Python scalar
non_generic_value_promoted_args = [
y if hasattr(x, "ndim") else x
for x, y in zip(resolved_args, broadcasted_args)
]
# Allow other nplikes to replace implementation
impl = self.prepare_ufunc(ufunc)
# Compute the result
return impl(*non_generic_value_promoted_args, **(kwargs or {}))

def broadcast_arrays(self, *arrays: ArrayLike) -> list[ArrayLike]:
assert not any(isinstance(x, PlaceholderArray) for x in arrays)
Expand Down Expand Up @@ -327,7 +375,10 @@ def concat(
axis: int | None = 0,
) -> ArrayLike:
assert not any(isinstance(x, PlaceholderArray) for x in arrays)
return self._module.concatenate(arrays, axis=axis, casting="same_kind")
if _nplike_concatenate_has_casting(self._module):
return self._module.concatenate(arrays, axis=axis, casting="same_kind")
else:
return self._module.concatenate(arrays, axis=axis)

def repeat(
self,
Expand Down Expand Up @@ -507,10 +558,11 @@ def max(
return self._module.max(x, axis=axis, keepdims=keepdims, out=maybe_out)

def count_nonzero(
self, x: ArrayLike, *, axis: int | None = None, keepdims: bool = False
self, x: ArrayLike, *, axis: int | tuple[int, ...] | None = None
) -> ArrayLike:
assert not isinstance(x, PlaceholderArray)
return self._module.count_nonzero(x, axis=axis, keepdims=keepdims)
assert isinstance(axis, int) or axis is None
return self._module.count_nonzero(x, axis=axis)

def cumsum(
self,
Expand Down
13 changes: 6 additions & 7 deletions src/awkward/_nplikes/cupy.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ def frombuffer(
np_array = numpy.frombuffer(buffer, dtype=dtype, count=count)
return self._module.asarray(np_array)

def array_equal(self, x1: ArrayLike, x2: ArrayLike, *, equal_nan: bool = False):
def array_equal(
self, x1: ArrayLike, x2: ArrayLike, *, equal_nan: bool = False
) -> bool:
assert not isinstance(x1, PlaceholderArray)
assert not isinstance(x2, PlaceholderArray)
if x1.shape != x2.shape:
return False
else:
return self._module.all(x1 - x2 == 0)
return self._module.array_equal(x1, x2, equal_nan=equal_nan).get()

def repeat(
self, x: ArrayLike, repeats: ArrayLike | int, *, axis: int | None = None
Expand Down Expand Up @@ -106,13 +108,10 @@ def any(
return out

def count_nonzero(
self,
x: ArrayLike,
*,
axis: int | tuple[int, ...] | None = None,
keepdims: bool = False,
self, x: ArrayLike, *, axis: int | tuple[int, ...] | None = None
) -> ArrayLike:
assert not isinstance(x, PlaceholderArray)
assert isinstance(axis, int) or axis is None
out = self._module.count_nonzero(x, axis=axis)
if axis is None and isinstance(out, self._module.ndarray):
return out.item()
Expand Down
2 changes: 1 addition & 1 deletion src/awkward/_nplikes/numpylike.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ def max(

@abstractmethod
def count_nonzero(
self, x: ArrayLike, *, axis: int | None = None, keepdims: bool = False
self, x: ArrayLike, *, axis: int | tuple[int, ...] | None = None
) -> ArrayLike:
...

Expand Down
Loading

0 comments on commit 5665d85

Please sign in to comment.