Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 14 additions & 0 deletions src/awkward/_do.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ def reduce(
):
reducer = layout.backend.prepare_reducer(reducer)

# a flat array can only be reduced with axis=None
if isinstance(layout, ak.contents.NumpyArray) and layout.data.ndim <= 1:
axis = None

if axis is None:
parts = remove_structure(
layout,
Expand All @@ -247,6 +251,16 @@ def reduce(
else:
(layout,) = parts

# Check if we're running with concrete data and if the reducer has a axis=None specialization.
# If both are true, we use the specialized reducer. This allows us to use optimized implementations
# from e.g. NumPy, but also make use of potentially better algorithms, i.e. Kahan summation for sum.
if (
layout.backend.nplike.known_data
and (specialization := reducer.axis_none_reducer()) is not None
):
# overwrite reducer if it has an axis=None version
reducer = specialization

starts = ak.index.Index64.zeros(1, layout.backend.nplike)
parents = ak.index.Index64.zeros(layout.length, layout.backend.nplike)
shifts = None
Expand Down
22 changes: 22 additions & 0 deletions src/awkward/_nplikes/array_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,28 @@ def min(
(x,) = maybe_materialize(x)
return self._module.min(x, axis=axis, keepdims=keepdims, out=maybe_out)

def sum(
self,
x: ArrayLikeT,
*,
axis: ShapeItem | tuple[ShapeItem, ...] | None = None,
keepdims: bool = False,
maybe_out: ArrayLikeT | None = None,
) -> ArrayLikeT:
(x,) = maybe_materialize(x)
return self._module.sum(x, axis=axis, keepdims=keepdims, out=maybe_out)

def prod(
self,
x: ArrayLikeT,
*,
axis: ShapeItem | tuple[ShapeItem, ...] | None = None,
keepdims: bool = False,
maybe_out: ArrayLikeT | None = None,
) -> ArrayLikeT:
(x,) = maybe_materialize(x)
return self._module.prod(x, axis=axis, keepdims=keepdims, out=maybe_out)

def max(
self,
x: ArrayLikeT,
Expand Down
30 changes: 30 additions & 0 deletions src/awkward/_nplikes/cupy.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,36 @@ def min(
else:
return out

def sum(
self,
x: ArrayLike,
*,
axis: ShapeItem | tuple[ShapeItem, ...] | None = None,
keepdims: bool = False,
maybe_out: ArrayLike | None = None,
) -> ArrayLike:
(x,) = maybe_materialize(x)
out = self._module.sum(x, axis=axis, out=maybe_out)
if axis is None and isinstance(out, self._module.ndarray):
return out.item()
else:
return out

def prod(
self,
x: ArrayLike,
*,
axis: ShapeItem | tuple[ShapeItem, ...] | None = None,
keepdims: bool = False,
maybe_out: ArrayLike | None = None,
) -> ArrayLike:
(x,) = maybe_materialize(x)
out = self._module.prod(x, axis=axis, out=maybe_out)
if axis is None and isinstance(out, self._module.ndarray):
return out.item()
else:
return out

def max(
self,
x: ArrayLike,
Expand Down
20 changes: 20 additions & 0 deletions src/awkward/_nplikes/numpy_like.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,26 @@ def max(
maybe_out: ArrayLikeT | None = None,
) -> ArrayLikeT: ...

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

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

@abstractmethod
def count_nonzero(
self, x: ArrayLikeT, *, axis: int | tuple[int, ...] | None = None
Expand Down
20 changes: 20 additions & 0 deletions src/awkward/_nplikes/typetracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,26 @@ def max(
) -> TypeTracerArray:
return self.min(x, axis=axis, keepdims=keepdims, maybe_out=maybe_out)

def sum(
self,
x: TypeTracerArray,
*,
axis: int | tuple[int, ...] | None = None,
keepdims: bool = False,
maybe_out: TypeTracerArray | None = None,
) -> TypeTracerArray:
return self.min(x, axis=axis, keepdims=keepdims, maybe_out=maybe_out)

def prod(
self,
x: TypeTracerArray,
*,
axis: int | tuple[int, ...] | None = None,
keepdims: bool = False,
maybe_out: TypeTracerArray | None = None,
) -> TypeTracerArray:
return self.min(x, axis=axis, keepdims=keepdims, maybe_out=maybe_out)

def array_str(
self,
x: TypeTracerArray,
Expand Down
51 changes: 51 additions & 0 deletions src/awkward/_reducers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ def preferred_dtype(self) -> DTypeLike: ...
def highlevel_function(cls):
return getattr(ak.operations, cls.name)

@classmethod
def axis_none_reducer(cls) -> Reducer | None:
"""A specialized version for axis=None reductions, or None if there is none."""
return None

@abstractmethod
def apply(
self,
Expand Down Expand Up @@ -336,6 +341,10 @@ class Sum(KernelReducer):
preferred_dtype: Final = np.float64
needs_position: Final = False

@classmethod
def axis_none_reducer(cls):
return AxisNoneSum()

def apply(
self,
array: ak.contents.NumpyArray,
Expand Down Expand Up @@ -435,11 +444,34 @@ def apply(
)


class AxisNoneSum(Sum):
def apply(
self,
array: ak.contents.NumpyArray,
parents: ak.index.Index,
starts: ak.index.Index,
shifts: ak.index.Index | None,
outlength: ShapeItem,
) -> ak.contents.NumpyArray:
del parents, starts, shifts, outlength # Unused
assert isinstance(array, ak.contents.NumpyArray)
if array.dtype.kind == "M":
raise ValueError(f"cannot compute the sum (ak.sum) of {array.dtype!r}")
reduce_fn = getattr(array.backend.nplike, self.name)
return ak.contents.NumpyArray(
[reduce_fn(array.data, axis=None)], backend=array.backend
)


class Prod(KernelReducer):
name: Final = "prod"
preferred_dtype: Final = np.float64
needs_position: Final = False

@classmethod
def axis_none_reducer(cls) -> Reducer | None:
return AxisNoneProd()

def apply(
self,
array: ak.contents.NumpyArray,
Expand Down Expand Up @@ -526,6 +558,25 @@ def apply(
)


class AxisNoneProd(Prod):
def apply(
self,
array: ak.contents.NumpyArray,
parents: ak.index.Index,
starts: ak.index.Index,
shifts: ak.index.Index | None,
outlength: ShapeItem,
) -> ak.contents.NumpyArray:
del parents, starts, shifts, outlength # Unused
assert isinstance(array, ak.contents.NumpyArray)
if array.dtype.kind.upper() == "M":
raise ValueError(f"cannot compute the product (ak.prod) of {array.dtype!r}")
reduce_fn = getattr(array.backend.nplike, self.name)
return ak.contents.NumpyArray(
[reduce_fn(array.data, axis=None)], backend=array.backend
)


class Any(KernelReducer):
name: Final = "any"
preferred_dtype: Final = np.bool_
Expand Down
Loading