From b6b6f050f0f2f513d047ecdd0e4ad2a6cbdfaa10 Mon Sep 17 00:00:00 2001 From: ioanaif Date: Thu, 25 Aug 2022 16:53:47 +0200 Subject: [PATCH] fix: prevent reducers like ak.sum on records (v2) (#1607) * policy: Prevent reducers like ak.sum on records (v2) * Pass behavior on the rest of the reducers * Made a util function for checking if a reducer function is overloaded for records * Switch using .split with .name * Add 'highlevel_function() utility --- src/awkward/_v2/_reducers.py | 4 + src/awkward/_v2/_util.py | 7 + src/awkward/_v2/contents/bitmaskedarray.py | 2 + src/awkward/_v2/contents/bytemaskedarray.py | 2 + src/awkward/_v2/contents/content.py | 53 +++++--- src/awkward/_v2/contents/emptyarray.py | 2 + src/awkward/_v2/contents/indexedarray.py | 2 + .../_v2/contents/indexedoptionarray.py | 2 + src/awkward/_v2/contents/listarray.py | 2 + src/awkward/_v2/contents/listoffsetarray.py | 4 + src/awkward/_v2/contents/numpyarray.py | 2 + src/awkward/_v2/contents/recordarray.py | 35 ++--- src/awkward/_v2/contents/regulararray.py | 2 + src/awkward/_v2/contents/unionarray.py | 2 + src/awkward/_v2/contents/unmaskedarray.py | 2 + src/awkward/_v2/operations/ak_all.py | 4 +- src/awkward/_v2/operations/ak_any.py | 4 +- src/awkward/_v2/operations/ak_argmax.py | 4 +- src/awkward/_v2/operations/ak_argmin.py | 4 +- src/awkward/_v2/operations/ak_count.py | 4 +- .../_v2/operations/ak_count_nonzero.py | 4 +- src/awkward/_v2/operations/ak_max.py | 6 +- src/awkward/_v2/operations/ak_min.py | 6 +- src/awkward/_v2/operations/ak_prod.py | 4 +- src/awkward/_v2/operations/ak_sum.py | 4 +- .../v2/test_0115-generic-reducer-operation.py | 34 ++--- tests/v2/test_1607_no_reducers_on_records.py | 126 ++++++++++++++++++ 27 files changed, 258 insertions(+), 69 deletions(-) create mode 100644 tests/v2/test_1607_no_reducers_on_records.py diff --git a/src/awkward/_v2/_reducers.py b/src/awkward/_v2/_reducers.py index ff258dc2b6..09e5aa45e6 100644 --- a/src/awkward/_v2/_reducers.py +++ b/src/awkward/_v2/_reducers.py @@ -8,6 +8,10 @@ class Reducer: needs_position = False + @classmethod + def highlevel_function(cls): + return getattr(ak._v2.operations, cls.name) + @classmethod def return_dtype(cls, given_dtype): if given_dtype in (np.bool_, np.int8, np.int16, np.int32): diff --git a/src/awkward/_v2/_util.py b/src/awkward/_v2/_util.py index 592c7fbc4c..b69f96204a 100644 --- a/src/awkward/_v2/_util.py +++ b/src/awkward/_v2/_util.py @@ -597,6 +597,13 @@ def recordclass(layout, behavior): return ak._v2.highlevel.Record +def reducer_recordclass(reducer, layout, behavior): + behavior = Behavior(ak._v2.behavior, behavior) + rec = layout.parameter("__record__") + if isstr(rec): + return behavior[reducer.highlevel_function(), rec] + + def typestrs(behavior): behavior = Behavior(ak._v2.behavior, behavior) out = {} diff --git a/src/awkward/_v2/contents/bitmaskedarray.py b/src/awkward/_v2/contents/bitmaskedarray.py index 568c923799..14241bb5b6 100644 --- a/src/awkward/_v2/contents/bitmaskedarray.py +++ b/src/awkward/_v2/contents/bitmaskedarray.py @@ -559,6 +559,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): return self.toByteMaskedArray()._reduce_next( reducer, @@ -569,6 +570,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) def _validity_error(self, path): diff --git a/src/awkward/_v2/contents/bytemaskedarray.py b/src/awkward/_v2/contents/bytemaskedarray.py index ca1b48052f..d885e9952a 100644 --- a/src/awkward/_v2/contents/bytemaskedarray.py +++ b/src/awkward/_v2/contents/bytemaskedarray.py @@ -795,6 +795,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): mask_length = self._mask.length @@ -899,6 +900,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) if not branch and negaxis == depth: diff --git a/src/awkward/_v2/contents/content.py b/src/awkward/_v2/contents/content.py index 186bae29ef..b60c93b26d 100644 --- a/src/awkward/_v2/contents/content.py +++ b/src/awkward/_v2/contents/content.py @@ -814,7 +814,7 @@ def dummy(self): def local_index(self, axis): return self._local_index(axis, 0) - def _reduce(self, reducer, axis=-1, mask=True, keepdims=False): + def _reduce(self, reducer, axis=-1, mask=True, keepdims=False, behavior=None): if axis is None: raise ak._v2._util.error(NotImplementedError) @@ -861,39 +861,50 @@ def _reduce(self, reducer, axis=-1, mask=True, keepdims=False): 1, mask, keepdims, + behavior, ) return next[0] - def argmin(self, axis=-1, mask=True, keepdims=False): - return self._reduce(awkward._v2._reducers.ArgMin, axis, mask, keepdims) + def argmin(self, axis=-1, mask=True, keepdims=False, behavior=None): + return self._reduce( + awkward._v2._reducers.ArgMin, axis, mask, keepdims, behavior + ) - def argmax(self, axis=-1, mask=True, keepdims=False): - return self._reduce(awkward._v2._reducers.ArgMax, axis, mask, keepdims) + def argmax(self, axis=-1, mask=True, keepdims=False, behavior=None): + return self._reduce( + awkward._v2._reducers.ArgMax, axis, mask, keepdims, behavior + ) - def count(self, axis=-1, mask=False, keepdims=False): - return self._reduce(awkward._v2._reducers.Count, axis, mask, keepdims) + def count(self, axis=-1, mask=False, keepdims=False, behavior=None): + return self._reduce(awkward._v2._reducers.Count, axis, mask, keepdims, behavior) - def count_nonzero(self, axis=-1, mask=False, keepdims=False): - return self._reduce(awkward._v2._reducers.CountNonzero, axis, mask, keepdims) + def count_nonzero(self, axis=-1, mask=False, keepdims=False, behavior=None): + return self._reduce( + awkward._v2._reducers.CountNonzero, axis, mask, keepdims, behavior + ) - def sum(self, axis=-1, mask=False, keepdims=False): - return self._reduce(awkward._v2._reducers.Sum, axis, mask, keepdims) + def sum(self, axis=-1, mask=False, keepdims=False, behavior=None): + return self._reduce(awkward._v2._reducers.Sum, axis, mask, keepdims, behavior) - def prod(self, axis=-1, mask=False, keepdims=False): - return self._reduce(awkward._v2._reducers.Prod, axis, mask, keepdims) + def prod(self, axis=-1, mask=False, keepdims=False, behavior=None): + return self._reduce(awkward._v2._reducers.Prod, axis, mask, keepdims, behavior) - def any(self, axis=-1, mask=False, keepdims=False): - return self._reduce(awkward._v2._reducers.Any, axis, mask, keepdims) + def any(self, axis=-1, mask=False, keepdims=False, behavior=None): + return self._reduce(awkward._v2._reducers.Any, axis, mask, keepdims, behavior) - def all(self, axis=-1, mask=False, keepdims=False): - return self._reduce(awkward._v2._reducers.All, axis, mask, keepdims) + def all(self, axis=-1, mask=False, keepdims=False, behavior=None): + return self._reduce(awkward._v2._reducers.All, axis, mask, keepdims, behavior) - def min(self, axis=-1, mask=True, keepdims=False, initial=None): - return self._reduce(awkward._v2._reducers.Min(initial), axis, mask, keepdims) + def min(self, axis=-1, mask=True, keepdims=False, initial=None, behavior=None): + return self._reduce( + awkward._v2._reducers.Min(initial), axis, mask, keepdims, behavior + ) - def max(self, axis=-1, mask=True, keepdims=False, initial=None): - return self._reduce(awkward._v2._reducers.Max(initial), axis, mask, keepdims) + def max(self, axis=-1, mask=True, keepdims=False, initial=None, behavior=None): + return self._reduce( + awkward._v2._reducers.Max(initial), axis, mask, keepdims, behavior + ) def argsort(self, axis=-1, ascending=True, stable=False, kind=None, order=None): negaxis = -axis diff --git a/src/awkward/_v2/contents/emptyarray.py b/src/awkward/_v2/contents/emptyarray.py index 40f90f2262..7cd02c6683 100644 --- a/src/awkward/_v2/contents/emptyarray.py +++ b/src/awkward/_v2/contents/emptyarray.py @@ -261,6 +261,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): as_numpy = self.toNumpyArray(reducer.preferred_dtype) return as_numpy._reduce_next( @@ -272,6 +273,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) def _validity_error(self, path): diff --git a/src/awkward/_v2/contents/indexedarray.py b/src/awkward/_v2/contents/indexedarray.py index e79ecfa0f3..232e7afd47 100644 --- a/src/awkward/_v2/contents/indexedarray.py +++ b/src/awkward/_v2/contents/indexedarray.py @@ -970,6 +970,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): branch, depth = self.branch_depth @@ -1013,6 +1014,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) # If we are reducing the contents of this layout, diff --git a/src/awkward/_v2/contents/indexedoptionarray.py b/src/awkward/_v2/contents/indexedoptionarray.py index f1acc8a50c..e2022c01db 100644 --- a/src/awkward/_v2/contents/indexedoptionarray.py +++ b/src/awkward/_v2/contents/indexedoptionarray.py @@ -1368,6 +1368,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): branch, depth = self.branch_depth @@ -1390,6 +1391,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) # If we are reducing the contents of this layout, diff --git a/src/awkward/_v2/contents/listarray.py b/src/awkward/_v2/contents/listarray.py index 269b906946..58727e8beb 100644 --- a/src/awkward/_v2/contents/listarray.py +++ b/src/awkward/_v2/contents/listarray.py @@ -1234,6 +1234,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): return self.toListOffsetArray64(True)._reduce_next( reducer, @@ -1244,6 +1245,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) def _validity_error(self, path): diff --git a/src/awkward/_v2/contents/listoffsetarray.py b/src/awkward/_v2/contents/listoffsetarray.py index 2faa7a347e..47beaca3ce 100644 --- a/src/awkward/_v2/contents/listoffsetarray.py +++ b/src/awkward/_v2/contents/listoffsetarray.py @@ -1467,6 +1467,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): if self._offsets.dtype != np.dtype(np.int64) or ( self._offsets.nplike.known_data and self._offsets[0] != 0 @@ -1481,6 +1482,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) branch, depth = self.branch_depth @@ -1586,6 +1588,7 @@ def _reduce_next( maxnextparents[0] + 1, mask, False, + behavior, ) out = ak._v2.contents.ListArray( @@ -1641,6 +1644,7 @@ def _reduce_next( globalstarts_length, mask, keepdims, + behavior, ) outoffsets = ak._v2.index.Index64.empty(outlength + 1, self._nplike) diff --git a/src/awkward/_v2/contents/numpyarray.py b/src/awkward/_v2/contents/numpyarray.py index 72f13f70c4..25a9168738 100644 --- a/src/awkward/_v2/contents/numpyarray.py +++ b/src/awkward/_v2/contents/numpyarray.py @@ -1077,6 +1077,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): if len(self._data.shape) != 1 or not self.is_contiguous: return self.toRegularArray()._reduce_next( @@ -1088,6 +1089,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) if isinstance(self.nplike, ak.nplike.Jax): diff --git a/src/awkward/_v2/contents/recordarray.py b/src/awkward/_v2/contents/recordarray.py index b775c29f37..24ac5b40e2 100644 --- a/src/awkward/_v2/contents/recordarray.py +++ b/src/awkward/_v2/contents/recordarray.py @@ -824,30 +824,23 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): - contents = [] - for content in self._contents: - contents.append( - content[: self._length]._reduce_next( - reducer, - negaxis, - starts, - shifts, - parents, - outlength, - mask, - keepdims, + reducer_recordclass = ak._v2._util.reducer_recordclass(reducer, self, behavior) + if reducer_recordclass is None: + raise ak._v2._util.error( + TypeError( + "no ak.{} overloads for custom types: {}".format( + reducer.name, ", ".join(self._fields) + ) + ) + ) + else: + raise ak._v2._util.error( + NotImplementedError( + "overloading reducers for RecordArrays has not been implemented yet" ) ) - - return ak._v2.contents.RecordArray( - contents, - self._fields, - outlength, - None, - None, - self._nplike, - ) def _validity_error(self, path): for i, cont in enumerate(self.contents): diff --git a/src/awkward/_v2/contents/regulararray.py b/src/awkward/_v2/contents/regulararray.py index 716a08c94f..832970715f 100644 --- a/src/awkward/_v2/contents/regulararray.py +++ b/src/awkward/_v2/contents/regulararray.py @@ -985,6 +985,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): out = self.toListOffsetArray64(True)._reduce_next( reducer, @@ -995,6 +996,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) if not self._content.dimension_optiontype: diff --git a/src/awkward/_v2/contents/unionarray.py b/src/awkward/_v2/contents/unionarray.py index a592cbcffb..36f0e49f32 100644 --- a/src/awkward/_v2/contents/unionarray.py +++ b/src/awkward/_v2/contents/unionarray.py @@ -1132,6 +1132,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): simplified = self.simplify_uniontype(mergebool=True) if isinstance(simplified, UnionArray): @@ -1150,6 +1151,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) def _validity_error(self, path): diff --git a/src/awkward/_v2/contents/unmaskedarray.py b/src/awkward/_v2/contents/unmaskedarray.py index d9d9843451..85bc5fd51e 100644 --- a/src/awkward/_v2/contents/unmaskedarray.py +++ b/src/awkward/_v2/contents/unmaskedarray.py @@ -488,6 +488,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ): next = self._content if isinstance(next, ak._v2.contents.RegularArray): @@ -502,6 +503,7 @@ def _reduce_next( outlength, mask, keepdims, + behavior, ) def _validity_error(self, path): diff --git a/src/awkward/_v2/operations/ak_all.py b/src/awkward/_v2/operations/ak_all.py index 5f7a9fd82d..35ce53677a 100644 --- a/src/awkward/_v2/operations/ak_all.py +++ b/src/awkward/_v2/operations/ak_all.py @@ -84,7 +84,9 @@ def reduce(xs): else: behavior = ak._v2._util.behavior_of(array) - out = layout.all(axis=axis, mask=mask_identity, keepdims=keepdims) + out = layout.all( + axis=axis, mask=mask_identity, keepdims=keepdims, behavior=behavior + ) if isinstance(out, (ak._v2.contents.Content, ak._v2.record.Record)): return ak._v2._util.wrap(out, behavior) else: diff --git a/src/awkward/_v2/operations/ak_any.py b/src/awkward/_v2/operations/ak_any.py index 2a30170337..c5de7b7d81 100644 --- a/src/awkward/_v2/operations/ak_any.py +++ b/src/awkward/_v2/operations/ak_any.py @@ -84,7 +84,9 @@ def reduce(xs): else: behavior = ak._v2._util.behavior_of(array) - out = layout.any(axis=axis, mask=mask_identity, keepdims=keepdims) + out = layout.any( + axis=axis, mask=mask_identity, keepdims=keepdims, behavior=behavior + ) if isinstance(out, (ak._v2.contents.Content, ak._v2.record.Record)): return ak._v2._util.wrap(out, behavior) else: diff --git a/src/awkward/_v2/operations/ak_argmax.py b/src/awkward/_v2/operations/ak_argmax.py index e049a8f655..8114e681c7 100644 --- a/src/awkward/_v2/operations/ak_argmax.py +++ b/src/awkward/_v2/operations/ak_argmax.py @@ -132,7 +132,9 @@ def _impl(array, axis, keepdims, mask_identity, flatten_records): else: behavior = ak._v2._util.behavior_of(array) - out = layout.argmax(axis=axis, mask=mask_identity, keepdims=keepdims) + out = layout.argmax( + axis=axis, mask=mask_identity, keepdims=keepdims, behavior=behavior + ) if isinstance(out, (ak._v2.contents.Content, ak._v2.record.Record)): return ak._v2._util.wrap(out, behavior) else: diff --git a/src/awkward/_v2/operations/ak_argmin.py b/src/awkward/_v2/operations/ak_argmin.py index cdc096d403..95fc391fbb 100644 --- a/src/awkward/_v2/operations/ak_argmin.py +++ b/src/awkward/_v2/operations/ak_argmin.py @@ -132,7 +132,9 @@ def _impl(array, axis, keepdims, mask_identity, flatten_records): else: behavior = ak._v2._util.behavior_of(array) - out = layout.argmin(axis=axis, mask=mask_identity, keepdims=keepdims) + out = layout.argmin( + axis=axis, mask=mask_identity, keepdims=keepdims, behavior=behavior + ) if isinstance(out, (ak._v2.contents.Content, ak._v2.record.Record)): return ak._v2._util.wrap(out, behavior) else: diff --git a/src/awkward/_v2/operations/ak_count.py b/src/awkward/_v2/operations/ak_count.py index 5c2674afd2..52a7c03649 100644 --- a/src/awkward/_v2/operations/ak_count.py +++ b/src/awkward/_v2/operations/ak_count.py @@ -126,7 +126,9 @@ def reduce(xs): else: behavior = ak._v2._util.behavior_of(array) - out = layout.count(axis=axis, mask=mask_identity, keepdims=keepdims) + out = layout.count( + axis=axis, mask=mask_identity, keepdims=keepdims, behavior=behavior + ) if isinstance(out, (ak._v2.contents.Content, ak._v2.record.Record)): return ak._v2._util.wrap(out, behavior) else: diff --git a/src/awkward/_v2/operations/ak_count_nonzero.py b/src/awkward/_v2/operations/ak_count_nonzero.py index bd3ef6db32..38de5ddf0a 100644 --- a/src/awkward/_v2/operations/ak_count_nonzero.py +++ b/src/awkward/_v2/operations/ak_count_nonzero.py @@ -88,7 +88,9 @@ def reduce(xs): else: behavior = ak._v2._util.behavior_of(array) - out = layout.count_nonzero(axis=axis, mask=mask_identity, keepdims=keepdims) + out = layout.count_nonzero( + axis=axis, mask=mask_identity, keepdims=keepdims, behavior=behavior + ) if isinstance(out, (ak._v2.contents.Content, ak._v2.record.Record)): return ak._v2._util.wrap(out, behavior) else: diff --git a/src/awkward/_v2/operations/ak_max.py b/src/awkward/_v2/operations/ak_max.py index 10ec170c6c..de5d3ace7e 100644 --- a/src/awkward/_v2/operations/ak_max.py +++ b/src/awkward/_v2/operations/ak_max.py @@ -157,7 +157,11 @@ def reduce(xs): else: behavior = ak._v2._util.behavior_of(array) out = layout.max( - axis=axis, mask=mask_identity, keepdims=keepdims, initial=initial + axis=axis, + mask=mask_identity, + keepdims=keepdims, + initial=initial, + behavior=behavior, ) if isinstance(out, (ak._v2.contents.Content, ak._v2.record.Record)): return ak._v2._util.wrap(out, behavior) diff --git a/src/awkward/_v2/operations/ak_min.py b/src/awkward/_v2/operations/ak_min.py index edcc79eb5a..6f2d3eec57 100644 --- a/src/awkward/_v2/operations/ak_min.py +++ b/src/awkward/_v2/operations/ak_min.py @@ -157,7 +157,11 @@ def reduce(xs): else: behavior = ak._v2._util.behavior_of(array) out = layout.min( - axis=axis, mask=mask_identity, keepdims=keepdims, initial=initial + axis=axis, + mask=mask_identity, + keepdims=keepdims, + initial=initial, + behavior=behavior, ) if isinstance(out, (ak._v2.contents.Content, ak._v2.record.Record)): return ak._v2._util.wrap(out, behavior) diff --git a/src/awkward/_v2/operations/ak_prod.py b/src/awkward/_v2/operations/ak_prod.py index 04e9e6d926..852643b187 100644 --- a/src/awkward/_v2/operations/ak_prod.py +++ b/src/awkward/_v2/operations/ak_prod.py @@ -131,7 +131,9 @@ def reduce(xs): else: behavior = ak._v2._util.behavior_of(array) - out = layout.prod(axis=axis, mask=mask_identity, keepdims=keepdims) + out = layout.prod( + axis=axis, mask=mask_identity, keepdims=keepdims, behavior=behavior + ) if isinstance(out, (ak._v2.contents.Content, ak._v2.record.Record)): return ak._v2._util.wrap(out, behavior) else: diff --git a/src/awkward/_v2/operations/ak_sum.py b/src/awkward/_v2/operations/ak_sum.py index fd31291ecb..d4e3c3b3e3 100644 --- a/src/awkward/_v2/operations/ak_sum.py +++ b/src/awkward/_v2/operations/ak_sum.py @@ -275,7 +275,9 @@ def reduce(xs): else: behavior = ak._v2._util.behavior_of(array) - out = layout.sum(axis=axis, mask=mask_identity, keepdims=keepdims) + out = layout.sum( + axis=axis, mask=mask_identity, keepdims=keepdims, behavior=behavior + ) if isinstance(out, (ak._v2.contents.Content, ak._v2.record.Record)): return ak._v2._util.wrap(out, behavior) else: diff --git a/tests/v2/test_0115-generic-reducer-operation.py b/tests/v2/test_0115-generic-reducer-operation.py index 0d85495508..49f515930a 100644 --- a/tests/v2/test_0115-generic-reducer-operation.py +++ b/tests/v2/test_0115-generic-reducer-operation.py @@ -709,12 +709,12 @@ def test_complicated(): ] assert complicated.typetracer["y"].form == complicated["y"].form - assert to_list(complicated.prod(-1)) == [ - {"x": [30], "y": [[30, 1, 77, 13]]}, - {"x": [], "y": []}, - {"x": [1, 77], "y": [[], [323, 23]]}, - ] - assert complicated.typetracer.prod(-1).form == complicated.prod(-1).form + with pytest.raises(TypeError): + to_list(complicated.prod(-1)) + + with pytest.raises(TypeError): + complicated.typetracer.prod(-1).form + assert to_list(complicated["x"].prod(-1)) == [[30], [], [1, 77]] assert complicated.typetracer["x"].prod(-1).form == complicated["x"].prod(-1).form assert to_list(complicated["y"].prod(-1)) == [ @@ -724,12 +724,11 @@ def test_complicated(): ] assert complicated.typetracer["y"].prod(-1).form == complicated["y"].prod(-1).form - assert to_list(complicated.prod(-2)) == [ - {"x": [2, 3, 5], "y": [[182, 33, 5]]}, - {"x": [], "y": []}, - {"x": [7, 11], "y": [[], [391, 19]]}, - ] - assert complicated.typetracer.prod(-2).form == complicated.prod(-2).form + with pytest.raises(TypeError): + to_list(complicated.prod(-2)) + + with pytest.raises(TypeError): + complicated.typetracer.prod(-2).form assert to_list(complicated["x"].prod(-2)) == [[2, 3, 5], [], [7, 11]] assert complicated.typetracer["x"].prod(-2).form == complicated["x"].prod(-2).form assert to_list(complicated["y"].prod(-2)) == [ @@ -743,11 +742,12 @@ def test_complicated(): {"x": [2, 3, 5], "y": [[2, 3, 5], [], [7, 11], [13]]} ] assert complicated.typetracer[0].form == complicated[0].form - assert to_list(complicated[0].prod(-1)) == {"x": [30], "y": [[30, 1, 77, 13]]} - assert ( - complicated.typetracer[0].prod(-1).array.form - == complicated[0].prod(-1).array.form - ) + + with pytest.raises(TypeError): + to_list(complicated[0].prod(-1)) + + with pytest.raises(TypeError): + to_list(complicated.typetracer[0].prod(-1)) def test_EmptyArray(): diff --git a/tests/v2/test_1607_no_reducers_on_records.py b/tests/v2/test_1607_no_reducers_on_records.py new file mode 100644 index 0000000000..40d76a68c7 --- /dev/null +++ b/tests/v2/test_1607_no_reducers_on_records.py @@ -0,0 +1,126 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE + +import pytest # noqa: F401 +import numpy as np # noqa: F401 +import awkward as ak # noqa: F401 + + +def test_reducers(): + array = ak._v2.Array([[{"rho": -1.1, "phi": -0.1}, {"rho": 1.1, "phi": 0.1}]]) + + with pytest.raises(TypeError): + assert ak._v2.to_list(ak._v2.operations.all(array, axis=1)) == [ + {"phi": True, "rho": True} + ] + + with pytest.raises(TypeError): + assert ak._v2.to_list(ak._v2.operations.any(array, axis=1)) == [ + {"phi": True, "rho": True} + ] + + with pytest.raises(TypeError): + assert ak._v2.to_list(ak._v2.operations.argmax(array, axis=1)) == [ + {"phi": True, "rho": True} + ] + + with pytest.raises(TypeError): + assert ak._v2.to_list(ak._v2.operations.argmin(array, axis=1)) == [ + {"phi": 0, "rho": 0} + ] + + with pytest.raises(TypeError): + assert ak._v2.to_list(ak._v2.operations.count_nonzero(array, axis=1)) == [ + {"phi": 2, "rho": 2} + ] + + with pytest.raises(TypeError): + assert ak._v2.to_list(ak._v2.operations.count(array, axis=1)) == [ + {"phi": 2, "rho": 2} + ] + + with pytest.raises(TypeError): + assert ak._v2.to_list(ak._v2.operations.max(array, axis=1)) == [ + {"phi": 0.1, "rho": 1.1} + ] + + with pytest.raises(TypeError): + assert ak._v2.to_list(ak._v2.operations.min(array, axis=1)) == [ + {"phi": -0.1, "rho": -1.1} + ] + + with pytest.raises(TypeError): + assert ak._v2.to_list(ak._v2.operations.prod(array, axis=1)) == [ + {"phi": -0.010000000000000002, "rho": -1.2100000000000002} + ] + + with pytest.raises(TypeError): + assert ak._v2.to_list(ak._v2.operations.sum(array, axis=1)) == [ + {"phi": 0.0, "rho": 0.0} + ] + + +def test_overloaded_reducers(): + def overload_add(array, axis=-1): + return ak._v2.contents.RecordArray( + [ + ak._v2.contents.NumpyArray( + np.asarray([ak._v2.sum(array["rho"], axis=axis)]) + ), + ak._v2.contents.NumpyArray( + np.asarray([ak._v2.sum(array["phi"], axis=axis)]) + ), + ], + ["rho", "phi"], + ) + + behavior = {} + behavior[ak._v2.sum, "VectorArray2D"] = overload_add + + array = ak._v2.Array( + [[{"rho": -1.1, "phi": -0.1}, {"rho": 1.1, "phi": 0.1}]], + with_name="VectorArray2D", + behavior=behavior, + ) + + with pytest.raises(NotImplementedError): + assert ak._v2.to_list(ak._v2.sum(array, axis=1)) == [{"rho": 0, "phi": 0}] + + with pytest.raises(TypeError): + ak._v2.to_list(array + 1) + + def overload_add2(array): + return ak._v2.contents.RecordArray( + [ak._v2.contents.NumpyArray(np.asarray([2.4, 3, 4.5, 6]))], ["rho"] + ) + + behavior = {} + behavior[ak._v2.sum, "VectorArray2D"] = overload_add2 + + array = ak._v2.Array( + [[{"rho": -1.1, "phi": -0.1}, {"rho": 1.1, "phi": 0.1}]], + with_name="VectorArray2D", + behavior=behavior, + ) + + with pytest.raises(NotImplementedError): + assert ak._v2.to_list(ak._v2.sum(array, axis=1)) == [{"rho": 2.4}] + + array = ak._v2.highlevel.Array( + ak._v2.contents.ByteMaskedArray( + ak._v2.index.Index8(np.array([True, True])), + ak._v2.contents.RecordArray( + [ + ak._v2.contents.NumpyArray(np.arange(2)), + ak._v2.contents.IndexedOptionArray( + ak._v2.index.Index64(np.array([0, -1])), + ak._v2.contents.NumpyArray(np.array([1], dtype=np.int64)), + ), + ], + ["a", "b"], + ), + valid_when=True, + ) + ) + + with pytest.raises(TypeError): + ak._v2.to_list(ak._v2.sum(array, axis=0))