From 8294cc479447880a414d43a19ad90c06e10b18a9 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Sat, 16 Dec 2023 19:32:01 +0000 Subject: [PATCH 1/6] fix: touching of unions --- src/awkward/_broadcasting.py | 171 +++++++++++------------------ src/awkward/_nplikes/typetracer.py | 65 +++++++---- 2 files changed, 108 insertions(+), 128 deletions(-) diff --git a/src/awkward/_broadcasting.py b/src/awkward/_broadcasting.py index 1bbaf9693f..d70da4c64f 100644 --- a/src/awkward/_broadcasting.py +++ b/src/awkward/_broadcasting.py @@ -773,121 +773,74 @@ def broadcast_any_union(): else: nextparameters.append(NO_PARAMETERS) - if not backend.nplike.known_data: - # assert False - union_num_contents = [] - length = None - for x in contents: - if x.is_union: - x._touch_data(recursive=False) - union_num_contents.append(len(x.contents)) - if length is None: - length = x.length - - all_combos = list( - itertools.product(*[range(x) for x in union_num_contents]) - ) - - tags = backend.index_nplike.empty(length, dtype=np.int8) - index = backend.index_nplike.empty(length, dtype=np.int64) - numoutputs = None - outcontents = [] - for combo in all_combos: - nextinputs = [] - i = 0 - for x in inputs: - if isinstance(x, UnionArray): - nextinputs.append(x._contents[combo[i]]) - i += 1 - else: - nextinputs.append(x) - assert len(nextinputs) == len(nextparameters) - outcontents.append( - apply_step( - backend, - nextinputs, - action, - depth, - copy.copy(depth_context), - lateral_context, - options, + union_tags, union_num_contents, length = [], [], unknown_length + for x in contents: + if x.is_union: + tags = x.tags.raw(backend.index_nplike) + union_tags.append(tags) + union_num_contents.append(len(x.contents)) + + if length is unknown_length: + length = tags.shape[0] + elif tags.shape[0] is unknown_length: + continue + elif length != tags.shape[0]: + raise ValueError( + "cannot broadcast UnionArray of length {} " + "with UnionArray of length {}{}".format( + length, + tags.shape[0], + in_function(options), + ) ) - ) - assert isinstance(outcontents[-1], tuple) - if numoutputs is not None: - assert numoutputs == len(outcontents[-1]) - numoutputs = len(outcontents[-1]) - assert numoutputs is not None + tags = backend.index_nplike.empty(length, dtype=np.int8) + index = backend.index_nplike.empty(length, dtype=np.int64) - else: - union_tags, union_num_contents, length = [], [], None - for x in contents: - if x.is_union: - tags = x.tags.raw(backend.index_nplike) - union_tags.append(tags) - union_num_contents.append(len(x.contents)) - if tags.shape[0] is unknown_length: - continue - - if length is None: - length = tags.shape[0] - elif length != tags.shape[0]: - raise ValueError( - "cannot broadcast UnionArray of length {} " - "with UnionArray of length {}{}".format( - length, - tags.shape[0], - in_function(options), - ) - ) - assert length is not unknown_length - - # Stack all union tags - combos = backend.index_nplike.stack(union_tags, axis=-1) - # Build array of indices (c1, c2, c3, ..., cn) of contents in - # (union 1, union 2, union 3, ..., union n) - all_combos = backend.index_nplike.asarray( - list(itertools.product(*[range(x) for x in union_num_contents])) - ) + # Stack all union tags + combos = backend.index_nplike.stack(union_tags, axis=-1) - tags = backend.index_nplike.empty(length, dtype=np.int8) - index = backend.index_nplike.empty(length, dtype=np.int64) - numoutputs, outcontents = None, [] - for tag, combo in enumerate(all_combos): - mask = backend.index_nplike.all(combos == combo, axis=-1) - tags[mask] = tag - index[mask] = backend.index_nplike.arange( - backend.index_nplike.count_nonzero(mask), dtype=np.int64 - ) - nextinputs = [] - i = 0 - for x in inputs: - if isinstance(x, UnionArray): - nextinputs.append(x[mask].project(combo[i])) - i += 1 - elif isinstance(x, Content): - nextinputs.append(x[mask]) - else: - nextinputs.append(x) - outcontents.append( - apply_step( - backend, - nextinputs, - action, - depth, - copy.copy(depth_context), - lateral_context, - options, - ) - ) - assert isinstance(outcontents[-1], tuple) - if numoutputs is None: - numoutputs = len(outcontents[-1]) + # Build array of indices (c1, c2, c3, ..., cn) of contents in + # (union 1, union 2, union 3, ..., union n) + all_combos = list(itertools.product(*[range(x) for x in union_num_contents])) + + numoutputs = None + outcontents = [] + + for tag, j_contents in enumerate(all_combos): + combo = backend.index_nplike.asarray(j_contents, dtype=np.int64) + mask = backend.index_nplike.all(combos == combo, axis=-1) + tags[mask] = tag + index[mask] = backend.index_nplike.arange( + backend.index_nplike.count_nonzero(mask), dtype=np.int64 + ) + nextinputs = [] + it_j_contents = iter(j_contents) + for x in inputs: + if isinstance(x, UnionArray): + nextinputs.append(x[mask].project(next(it_j_contents))) + elif isinstance(x, Content): + nextinputs.append(x[mask]) else: - assert numoutputs == len(outcontents[-1]) + nextinputs.append(x) + outcontents.append( + apply_step( + backend, + nextinputs, + action, + depth, + copy.copy(depth_context), + lateral_context, + options, + ) + ) + assert isinstance(outcontents[-1], tuple) + if numoutputs is None: + numoutputs = len(outcontents[-1]) + else: + assert numoutputs == len(outcontents[-1]) - assert numoutputs is not None + assert numoutputs is not None parameters = parameters_factory(nextparameters, numoutputs) diff --git a/src/awkward/_nplikes/typetracer.py b/src/awkward/_nplikes/typetracer.py index 90f13bd10d..6f32d4a89e 100644 --- a/src/awkward/_nplikes/typetracer.py +++ b/src/awkward/_nplikes/typetracer.py @@ -187,7 +187,7 @@ def _new( if not isinstance(shape, tuple): raise TypeError("typetracer shape must be a tuple") - if any(is_unknown_scalar(x) for x in shape): + if not all(isinstance(x, int) or x is unknown_length for x in shape): raise TypeError("typetracer shape must be integers or unknown-length") if not isinstance(dtype, np.dtype): raise TypeError("typetracer dtype must be an instance of np.dtype") @@ -945,7 +945,7 @@ def shape_item_as_index(self, x1: ShapeItem) -> IndexType: elif isinstance(x1, int): return x1 else: - raise TypeError(f"expected None or int type, received {x1}") + raise TypeError(f"expected unknown_length or int type, received {x1}") def index_as_shape_item(self, x1: IndexType) -> ShapeItem: if is_unknown_scalar(x1) and np.issubdtype(x1.dtype, np.integer): @@ -1294,10 +1294,24 @@ def stack( *, axis: int = 0, ) -> TypeTracerArray: + # Ensure all arrays have same ndim + ndim = arrays[0].ndim + assert all(x.ndim == ndim for x in arrays[1:]) + + if axis is None: + assert all(x.ndim == 1 for x in arrays) + elif axis < 0: + axis = ndim + axis + if not 0 <= axis < ndim: + raise ValueError(axis) + for x in arrays: assert isinstance(x, TypeTracerArray) try_touch_data(x) - raise NotImplementedError + + emptyarrays = [numpy.empty_like((0,) * ndim, dtype=a.dtype) for a in arrays] + result = numpy.stack(emptyarrays, axis=axis) + return TypeTracerArray._new(result.dtype, result.shape) def packbits( self, @@ -1436,10 +1450,20 @@ def all( ) -> TypeTracerArray: assert isinstance(x, TypeTracerArray) try_touch_data(x) - if axis is None: - return TypeTracerArray._new(np.dtype(np.bool_), shape=()) + + if axis < 0: + axis = axis + x.ndim + + assert 0 <= axis < x.ndim + + if keepdims: + next_shape = list(x.shape) + next_shape[axis] = 1 + return TypeTracerArray._new(np.dtype(np.bool_), shape=tuple(next_shape)) else: - raise NotImplementedError + next_shape = list(x.shape) + del next_shape[axis] + return TypeTracerArray._new(np.dtype(np.bool_), shape=tuple(next_shape)) def any( self, @@ -1449,12 +1473,7 @@ def any( keepdims: bool = False, maybe_out: TypeTracerArray | None = None, ) -> TypeTracerArray: - assert isinstance(x, TypeTracerArray) - try_touch_data(x) - if axis is None: - return TypeTracerArray._new(np.dtype(np.bool_), shape=()) - else: - raise NotImplementedError + return self.all(x, axis=axis, keepdims=keepdims, maybe_out=maybe_out) def count_nonzero( self, x: TypeTracerArray, *, axis: int | tuple[int, ...] | None = None @@ -1476,7 +1495,20 @@ def min( ) -> TypeTracerArray: assert isinstance(x, TypeTracerArray) try_touch_data(x) - raise NotImplementedError + + if axis < 0: + axis = axis + x.ndim + + assert 0 <= axis < x.ndim + + if keepdims: + next_shape = list(x.shape) + next_shape[axis] = 1 + return TypeTracerArray._new(x.dtype, shape=tuple(next_shape)) + else: + next_shape = list(x.shape) + del next_shape[axis] + return TypeTracerArray._new(x.dtype, shape=tuple(next_shape)) def max( self, @@ -1486,12 +1518,7 @@ def max( keepdims: bool = False, maybe_out: TypeTracerArray | None = None, ) -> TypeTracerArray: - assert isinstance(x, TypeTracerArray) - try_touch_data(x) - if axis is None: - return TypeTracerArray._new(x.dtype, shape=()) - else: - raise NotImplementedError + return self.min(x, axis=axis, keepdims=keepdims, maybe_out=maybe_out) def array_str( self, From 122a0d7049ad0b0c03f18e98b122299948c8e5c9 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Tue, 19 Dec 2023 11:56:26 +0000 Subject: [PATCH 2/6] fix: error for unsupported arguments --- src/awkward/_nplikes/typetracer.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/awkward/_nplikes/typetracer.py b/src/awkward/_nplikes/typetracer.py index 6f32d4a89e..b78bc3018a 100644 --- a/src/awkward/_nplikes/typetracer.py +++ b/src/awkward/_nplikes/typetracer.py @@ -1451,6 +1451,19 @@ def all( assert isinstance(x, TypeTracerArray) try_touch_data(x) + if isinstance(axis, tuple): + raise NotImplementedError + if maybe_out is not None: + raise NotImplementedError + + if axis is None: + return self.all( + cast(TypeTracerArray, self.reshape(x, (-1,))), + axis=axis, + keepdims=keepdims, + maybe_out=maybe_out, + ) + if axis < 0: axis = axis + x.ndim @@ -1496,6 +1509,19 @@ def min( assert isinstance(x, TypeTracerArray) try_touch_data(x) + if isinstance(axis, tuple): + raise NotImplementedError + if maybe_out is not None: + raise NotImplementedError + + if axis is None: + return self.min( + cast(TypeTracerArray, self.reshape(x, (-1,))), + axis=axis, + keepdims=keepdims, + maybe_out=maybe_out, + ) + if axis < 0: axis = axis + x.ndim From d6ca283f7b68e51182db26d96771d21283005872 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Tue, 19 Dec 2023 11:59:52 +0000 Subject: [PATCH 3/6] refactor: fix type hint --- src/awkward/contents/bytemaskedarray.py | 2 +- src/awkward/contents/content.py | 2 +- src/awkward/contents/emptyarray.py | 2 +- src/awkward/contents/indexedarray.py | 2 +- src/awkward/contents/indexedoptionarray.py | 2 +- src/awkward/contents/listarray.py | 2 +- src/awkward/contents/listoffsetarray.py | 2 +- src/awkward/contents/numpyarray.py | 2 +- src/awkward/contents/recordarray.py | 2 +- src/awkward/contents/regulararray.py | 2 +- src/awkward/contents/unionarray.py | 2 +- src/awkward/contents/unmaskedarray.py | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/awkward/contents/bytemaskedarray.py b/src/awkward/contents/bytemaskedarray.py index 84742101d3..6881292a2b 100644 --- a/src/awkward/contents/bytemaskedarray.py +++ b/src/awkward/contents/bytemaskedarray.py @@ -390,7 +390,7 @@ def _getitem_at(self, where: IndexType): else: return None - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: if not self._backend.nplike.known_data: self._touch_shape(recursive=False) return self diff --git a/src/awkward/contents/content.py b/src/awkward/contents/content.py index 7315a32ff8..657f09a3d0 100644 --- a/src/awkward/contents/content.py +++ b/src/awkward/contents/content.py @@ -700,7 +700,7 @@ def _getitem(self, where): def _getitem_at(self, where: IndexType): raise NotImplementedError - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: raise NotImplementedError def _getitem_field( diff --git a/src/awkward/contents/emptyarray.py b/src/awkward/contents/emptyarray.py index 831d8c3810..264855317a 100644 --- a/src/awkward/contents/emptyarray.py +++ b/src/awkward/contents/emptyarray.py @@ -182,7 +182,7 @@ def _getitem_nothing(self): def _getitem_at(self, where: IndexType): raise ak._errors.index_error(self, where, "array is empty") - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: return self def _getitem_field( diff --git a/src/awkward/contents/indexedarray.py b/src/awkward/contents/indexedarray.py index 7f30634e52..b0d5701837 100644 --- a/src/awkward/contents/indexedarray.py +++ b/src/awkward/contents/indexedarray.py @@ -292,7 +292,7 @@ def _getitem_at(self, where: IndexType): raise ak._errors.index_error(self, where) return self._content._getitem_at(self._index[where]) - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: if not self._backend.nplike.known_data: self._touch_shape(recursive=False) return self diff --git a/src/awkward/contents/indexedoptionarray.py b/src/awkward/contents/indexedoptionarray.py index 6373e61161..6772a76c1e 100644 --- a/src/awkward/contents/indexedoptionarray.py +++ b/src/awkward/contents/indexedoptionarray.py @@ -316,7 +316,7 @@ def _getitem_at(self, where: IndexType): else: return self._content._getitem_at(self._index[where]) - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: if not self._backend.nplike.known_data: self._touch_shape(recursive=False) return self diff --git a/src/awkward/contents/listarray.py b/src/awkward/contents/listarray.py index da0ef351bf..bd0bf63828 100644 --- a/src/awkward/contents/listarray.py +++ b/src/awkward/contents/listarray.py @@ -320,7 +320,7 @@ def _getitem_at(self, where: IndexType): start, stop = self._starts[where], self._stops[where] return self._content._getitem_range(start, stop) - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: if not self._backend.nplike.known_data: self._touch_shape(recursive=False) return self diff --git a/src/awkward/contents/listoffsetarray.py b/src/awkward/contents/listoffsetarray.py index e35b32320e..fb1991c4d8 100644 --- a/src/awkward/contents/listoffsetarray.py +++ b/src/awkward/contents/listoffsetarray.py @@ -320,7 +320,7 @@ def _getitem_at(self, where: IndexType): start, stop = self._offsets[where], self._offsets[where + 1] return self._content._getitem_range(start, stop) - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: if not self._backend.nplike.known_data: self._touch_shape(recursive=False) return self diff --git a/src/awkward/contents/numpyarray.py b/src/awkward/contents/numpyarray.py index 0adba74ba4..d136366446 100644 --- a/src/awkward/contents/numpyarray.py +++ b/src/awkward/contents/numpyarray.py @@ -321,7 +321,7 @@ def _getitem_at(self, where: IndexType): else: return out - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: try: out = self._data[start:stop] except IndexError as err: diff --git a/src/awkward/contents/recordarray.py b/src/awkward/contents/recordarray.py index 30697f6f8f..96825938f6 100644 --- a/src/awkward/contents/recordarray.py +++ b/src/awkward/contents/recordarray.py @@ -445,7 +445,7 @@ def _getitem_at(self, where: IndexType): raise ak._errors.index_error(self, where) return Record(self, where) - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: if not self._backend.nplike.known_data: self._touch_shape(recursive=False) diff --git a/src/awkward/contents/regulararray.py b/src/awkward/contents/regulararray.py index c5748d7f37..4fefc1899a 100644 --- a/src/awkward/contents/regulararray.py +++ b/src/awkward/contents/regulararray.py @@ -300,7 +300,7 @@ def _getitem_at(self, where: IndexType): start, stop = where * size_scalar, (where + 1) * size_scalar return self._content._getitem_range(start, stop) - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: index_nplike = self._backend.index_nplike if not index_nplike.known_data: self._touch_shape(recursive=False) diff --git a/src/awkward/contents/unionarray.py b/src/awkward/contents/unionarray.py index 431312c21e..fed19ebe42 100644 --- a/src/awkward/contents/unionarray.py +++ b/src/awkward/contents/unionarray.py @@ -552,7 +552,7 @@ def _getitem_at(self, where: IndexType): tag, index = self._tags[where], self._index[where] return self._contents[tag]._getitem_at(index) - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: if not self._backend.nplike.known_data: self._touch_shape(recursive=False) return self diff --git a/src/awkward/contents/unmaskedarray.py b/src/awkward/contents/unmaskedarray.py index 8afdc6af5d..018146e198 100644 --- a/src/awkward/contents/unmaskedarray.py +++ b/src/awkward/contents/unmaskedarray.py @@ -238,7 +238,7 @@ def _getitem_at(self, where: IndexType): return self._content._getitem_at(where) - def _getitem_range(self, start: SupportsIndex, stop: IndexType) -> Content: + def _getitem_range(self, start: IndexType, stop: IndexType) -> Content: if not self._backend.nplike.known_data: self._touch_shape(recursive=False) return self From 48034278f66996effd3239f4672f4da679a1776d Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Tue, 19 Dec 2023 12:01:27 +0000 Subject: [PATCH 4/6] fix: normalise slice --- src/awkward/_slicing.py | 39 +++++++-------------------------- src/awkward/contents/content.py | 2 +- src/awkward/index.py | 4 ++++ 3 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/awkward/_slicing.py b/src/awkward/_slicing.py index bb266c3b53..e09407e66d 100644 --- a/src/awkward/_slicing.py +++ b/src/awkward/_slicing.py @@ -2,8 +2,6 @@ from __future__ import annotations -import operator - import awkward as ak from awkward._backends.backend import Backend from awkward._nplikes import to_nplike @@ -15,7 +13,7 @@ from awkward._typing import TYPE_CHECKING, Sequence, TypeAlias, TypeVar if TYPE_CHECKING: - from awkward._nplikes.numpy_like import ArrayLike + from awkward._nplikes.numpy_like import ArrayLike, NumpyLike from awkward.contents.content import Content np = NumpyMetadata.instance() @@ -24,48 +22,27 @@ SliceItem: TypeAlias = "int | slice | str | None | Ellipsis | ArrayLike | Content" -def normalize_slice_item(item, *, backend: Backend): - if backend.index_nplike.is_own_array(item): - if item.ndim != 0: - raise ValueError( - f"slice items must be 0D arrays or Python integers, not {item!r}" - ) - else: - return item - else: - return operator.index(item) - - -def normalize_slice(slice_: slice, *, backend: Backend) -> slice: +def normalize_slice(slice_: slice, *, nplike: NumpyLike) -> slice: """ Args: slice_: slice object - backend: backend of layout + nplike: NumpyLike of array Return a slice of (start, stop, step) for which the slice items have been normalized into index types. """ - index_nplike = backend.index_nplike start = slice_.start stop = slice_.stop step = slice_.step - if index_nplike.known_data: + if nplike.known_data: return slice_ # Unknown lengths mean that the slice index is unknown else: - start = ( - index_nplike.shape_item_as_index(start) - if start is unknown_length - else start - ) - stop = ( - index_nplike.shape_item_as_index(stop) if stop is unknown_length else stop - ) - step = ( - index_nplike.shape_item_as_index(step) if step is unknown_length else step - ) + start = nplike.shape_item_as_index(start) if start is unknown_length else start + stop = nplike.shape_item_as_index(stop) if stop is unknown_length else stop + step = nplike.shape_item_as_index(step) if step is unknown_length else step return slice(start, stop, step) @@ -226,7 +203,7 @@ def normalise_item(item, backend: Backend) -> SliceItem: return normalize_integer_like(item) elif isinstance(item, slice): - return normalize_slice(item, backend=backend) + return normalize_slice(item, nplike=backend.index_nplike) elif isinstance(item, str): return item diff --git a/src/awkward/contents/content.py b/src/awkward/contents/content.py index 657f09a3d0..3cefb566e0 100644 --- a/src/awkward/contents/content.py +++ b/src/awkward/contents/content.py @@ -525,7 +525,7 @@ def _getitem(self, where): elif isinstance(where, slice) and where.step is None: # Ensure that start, stop are non-negative! start, stop, _, _ = self._backend.index_nplike.derive_slice_for_length( - normalize_slice(where, backend=self._backend), self.length + normalize_slice(where, nplike=self._backend.index_nplike), self.length ) return self._getitem_range(start, stop) diff --git a/src/awkward/index.py b/src/awkward/index.py index 984c1cfd24..814a2106ff 100644 --- a/src/awkward/index.py +++ b/src/awkward/index.py @@ -13,6 +13,7 @@ from awkward._nplikes.numpy_like import NumpyLike, NumpyMetadata from awkward._nplikes.shape import ShapeItem from awkward._nplikes.typetracer import TypeTracer +from awkward._slicing import normalize_slice from awkward._typing import Any, DType, Final, Self, cast np: Final = NumpyMetadata.instance() @@ -214,6 +215,9 @@ def form(self) -> str: return _dtype_to_form[self._data.dtype] def __getitem__(self, where): + if isinstance(where, slice): + where = normalize_slice(where, nplike=self.nplike) + out = self._data[where] if hasattr(out, "shape") and len(out.shape) != 0: From e591e2dda91fb3efbf790d682c6082a2f0b2b900 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Tue, 19 Dec 2023 19:44:34 +0000 Subject: [PATCH 5/6] ci: simplify test workflow (#2869) * ci: simplify test workflow * ci: try exporting ForthError * ci: use macos11 for now * ci: rename General to Test * ci: fix wheelhouse path * ci: better parameterisation * ci: try cross-platform * ci: use glob action * ci: fix ROOT workflow * ci: fix defaults * ci: fixes * ci: fixes * fix: remaining paths --- .github/workflows/coverage.yml | 2 +- .github/workflows/test.yml | 209 ++++-------------- awkward-cpp/include/awkward/util.h | 2 +- noxfile.py | 4 +- pyproject.toml | 3 +- ...nts-test.txt => requirements-test-full.txt | 0 6 files changed, 43 insertions(+), 177 deletions(-) rename requirements-test.txt => requirements-test-full.txt (100%) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7abfbccf43..f328a457b0 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -73,7 +73,7 @@ jobs: run: python -m pip list - name: Install test requirements - run: python -m pip install -v -r requirements-test.txt + run: python -m pip install -v -r requirements-test-full.txt - name: Test run: >- diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0cd35cf551..f8aaab8a0e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,27 +29,38 @@ env: SOURCE_DATE_EPOCH: "1668811211" jobs: - Windows: + run-tests: + name: Run Tests strategy: matrix: + runs-on: + - windows-latest + - ubuntu-latest + - macos-11 python-version: - '3.12' - '3.11' - '3.10' - '3.9' - '3.8' - python-architecture: - x64 - + dependencies-kind: + - full include: - python-version: '3.9' python-architecture: x86 + runs-on: windows-latest + dependencies-kind: full + - python-version: '3.8' + python-architecture: x64 + runs-on: ubuntu-latest + dependencies-kind: minimal - runs-on: windows-2019 + runs-on: ${{ matrix.runs-on }} env: - PIP_ONLY_BINARY: cmake,numpy,pandas + PIP_ONLY_BINARY: numpy,pandas,pyarrow,numexpr,numexpr PIP_PRE: "1" steps: @@ -72,7 +83,7 @@ jobs: uses: actions/cache@v3 with: path: awkward-cpp/dist - key: ${{ github.job }}-${{ matrix.python-version }}-${{ matrix.python-architecture }}-${{ hashFiles('awkward-cpp/**') }} + key: ${{ github.job }}-${{ matrix.runs-on }}-${{ matrix.python-version }}-${{ matrix.python-architecture }}-${{ hashFiles('awkward-cpp/**') }} - name: Build awkward-cpp wheel if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true' @@ -80,169 +91,24 @@ jobs: python -m pip install build python -m build -w awkward-cpp - - 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: Test specification - if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true' - run: python -m pytest -vv -rs awkward-cpp/tests-spec - - - name: Test specification with explicitly defined values - if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true' - run: python -m pytest -vv -rs awkward-cpp/tests-spec-explicit - - - name: Test CPU kernels - if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true' - run: python -m pytest -vv -rs awkward-cpp/tests-cpu-kernels - - - name: Test - run: python -m pytest -vv -rs tests - - MacOS: - strategy: - matrix: - python-version: - - '3.12' - - '3.11' - - '3.10' - - '3.9' - - '3.8' - - runs-on: macos-11 - - env: - PIP_ONLY_BINARY: cmake,numpy - PIP_PRE: "1" - - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - - name: 'Python ${{ matrix.python-version }}' - uses: actions/setup-python@v5 - with: - python-version: '${{ matrix.python-version }}' - allow-prereleases: true - - - name: Debug wheel tags - run: python -m pip debug --verbose - - - name: Generate build files - run: pipx run nox -s prepare -- --headers --signatures --tests - - - name: Cache awkward-cpp wheel - id: cache-awkward-cpp-wheel - uses: actions/cache@v3 - with: - path: ./awkward-cpp/dist - key: ${{ github.job }}-${{ matrix.python-version }}-${{ hashFiles('awkward-cpp/**') }} - - - name: Build awkward-cpp wheel - if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true' - run: | - python -m pip install build - python -m build -w ./awkward-cpp - - - 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: Test specification - if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true' - run: python -m pytest -vv -rs awkward-cpp/tests-spec - - - name: Test specification with explicitly defined values - if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true' - run: python -m pytest -vv -rs awkward-cpp/tests-spec-explicit - - - name: Test CPU kernels - if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true' - run: python -m pytest -vv -rs awkward-cpp/tests-cpu-kernels - - - name: Test - run: python -m pytest -vv -rs tests - - Linux: - strategy: - matrix: - python-version: - - '3.12' - - '3.11' - - '3.10' - - '3.9' - - '3.8' - extra-pip-constraints: - - "-r requirements-test.txt" - include: - # Lower bounds - - python-version: '3.8' - extra-pip-constraints: "-r requirements-test-minimal.txt" - - runs-on: ubuntu-22.04 - - env: - PIP_ONLY_BINARY: cmake - - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - - name: 'Python ${{ matrix.python-version }}' - uses: actions/setup-python@v5 - with: - python-version: '${{ matrix.python-version }}' - allow-prereleases: true - - - name: Oldest supported CMake - uses: jwlawson/actions-setup-cmake@v1.14 - with: - cmake-version: '3.15.x' - - - name: Generate build files - run: pipx run nox -s prepare -- --headers --signatures --tests - - - name: Cache awkward-cpp wheel - id: cache-awkward-cpp-wheel - uses: actions/cache@v3 + - name: Find built wheel + uses: tj-actions/glob@v17 + id: find-wheel with: - path: ./awkward-cpp/dist - key: ${{ github.job }}-${{ matrix.python-version }}-cmake315-${{ hashFiles('awkward-cpp/**') }} - - - name: Build awkward-cpp wheel - if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true' - run: | - python -m pip install build - python -m build -w ./awkward-cpp + files: | + awkward-cpp/dist/*.whl - 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 }} + python -m pip install -v . ${{ steps.find-wheel.outputs.paths }} pytest-github-actions-annotate-failures + -r "requirements-test-${{ matrix.dependencies-kind }}.txt" - 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' + if: (matrix.python-version == '3.12') && (matrix.runs-on == 'ubuntu-latest') run: pipx run nox -s diagnostics -- --check-spec-sorted - name: Test specification @@ -257,20 +123,21 @@ jobs: if: steps.cache-awkward-cpp-wheel.outputs.cache-hit != 'true' run: python -m pytest -vv -rs awkward-cpp/tests-cpu-kernels - - name: Test + - name: Test non-kernels run: >- python -m pytest -vv -rs tests --cov=awkward --cov-report=term --cov-report=xml - name: Upload Codecov results + if: (matrix.python-version == '3.9') && (matrix.runs-on == 'ubuntu-latest') uses: codecov/codecov-action@v3 - if: matrix.python-version == '3.9' - Linux-ROOT-dask-awkward: + + Linux-ROOT: runs-on: ubuntu-20.04 env: - PIP_ONLY_BINARY: cmake + PIP_ONLY_BINARY: numpy,pandas,pyarrow,numexpr,numexpr timeout-minutes: 30 @@ -290,6 +157,7 @@ jobs: # Cache invalidates daily by default cache-environment: true environment-name: awkward + # Need Python 3.8 for the cached wheels create-args: >- python=3.8 numpy @@ -311,13 +179,12 @@ jobs: python3 -m pip install build python3 -m build -w ./awkward-cpp - - name: Install awkward, awkward-cpp, dask-awkward, and dependencies + - 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 - dask-awkward - -r requirements-test.txt + -r requirements-test-full.txt - name: Print versions run: python -m pip list @@ -331,7 +198,7 @@ jobs: runs-on: ubuntu-22.04 env: - PIP_ONLY_BINARY: cmake + PIP_ONLY_BINARY: numpy,pandas,pyarrow,numexpr,numexpr steps: - uses: actions/checkout@v4 @@ -364,14 +231,12 @@ jobs: with: image: "docker.io/agoose77/cppyy-wheels:cp311" path: "/wheels/." - destination: "/tmp/cppyy-wheels" + destination: "/tmp/wheelhouse" - 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 - /tmp/cppyy-wheels/* + python -m pip install -v --only-binary "numpy,pandas,pyarrow,numexpr" + ./ ./awkward-cpp/dist/*.whl /tmp/wheelhouse/* pytest-github-actions-annotate-failures - name: Print versions run: python -m pip list diff --git a/awkward-cpp/include/awkward/util.h b/awkward-cpp/include/awkward/util.h index b6ff5c7561..ed1e38ff90 100644 --- a/awkward-cpp/include/awkward/util.h +++ b/awkward-cpp/include/awkward/util.h @@ -64,7 +64,7 @@ namespace awkward { quote(const std::string& x); /// @brief Exhaustive list of runtime errors possible in the ForthMachine. - enum class ForthError { + enum class EXPORT_SYMBOL ForthError { // execution can continue none, diff --git a/noxfile.py b/noxfile.py index edce96d05f..61eb0566e7 100644 --- a/noxfile.py +++ b/noxfile.py @@ -37,7 +37,7 @@ def tests(session): """ Run the unit and regular tests. """ - session.install("-r", "requirements-test.txt", "./awkward-cpp", ".") + session.install("-r", "requirements-test-full.txt", "./awkward-cpp", ".") session.run("pytest", *session.posargs if session.posargs else ["tests"]) @@ -65,7 +65,7 @@ def coverage(session): """ Run the unit and regular tests. """ - session.install("-r", "requirements-test.txt", "./awkward-cpp", ".") + session.install("-r", "requirements-test-full.txt", "./awkward-cpp", ".") session.run( "pytest", "tests", "--cov=awkward", "--cov-report=xml", *session.posargs ) diff --git a/pyproject.toml b/pyproject.toml index c4d42987e7..4b7b0640a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,8 @@ include = [ "/CITATION.cff", "/CONTRIBUTING.md", "/README.md", - "/requirements-test.txt" + "/requirements-test-full.txt", + "/requirements-test-minimal.txt" ] artifacts = [ "/tests-cuda-kernels" diff --git a/requirements-test.txt b/requirements-test-full.txt similarity index 100% rename from requirements-test.txt rename to requirements-test-full.txt From 95d8679f586999253a61ccf5c5ffe4d607989059 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Tue, 19 Dec 2023 20:19:49 +0000 Subject: [PATCH 6/6] ci: disable PRE --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8aaab8a0e..45d0ad93b9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,7 +61,6 @@ jobs: env: PIP_ONLY_BINARY: numpy,pandas,pyarrow,numexpr,numexpr - PIP_PRE: "1" steps: - uses: actions/checkout@v4