diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d01978e60d..8defd2a707a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ This release is compatible with NumPy 2.5. * Fixed `dpnp.interp` returning `nan` when querying at an exact knot point whose adjacent `fp` value is `inf` [#2986](https://github.com/IntelPython/dpnp/pull/2986) * Fixed missing strides validation in `dpnp.tensor.usm_ndarray` constructor when allocating new memory [#2927](https://github.com/IntelPython/dpnp/pull/2927) * Fixed `dpnp.bincount` raising a `ValueError` on an empty input array instead of returning an empty `intp` array [#3018](https://github.com/IntelPython/dpnp/pull/3018) +* Fixed `dpnp.all` and `dpnp.any` aborting when reducing over an empty axis (e.g. an array with a zero-length dimension) [#3021](https://github.com/IntelPython/dpnp/pull/3021) ### Security diff --git a/dpnp/tensor/_utility_functions.py b/dpnp/tensor/_utility_functions.py index 651ce083026..8d0be2d8502 100644 --- a/dpnp/tensor/_utility_functions.py +++ b/dpnp/tensor/_utility_functions.py @@ -48,7 +48,7 @@ ) -def _boolean_reduction(x, axis, keepdims, func): +def _boolean_reduction(x, axis, keepdims, func, identity): if not isinstance(x, dpt.usm_ndarray): raise TypeError(f"Expected dpnp.tensor.usm_ndarray, got {type(x)}") @@ -77,37 +77,48 @@ def _boolean_reduction(x, axis, keepdims, func): exec_q = x.sycl_queue res_usm_type = x.usm_type - _manager = du.SequentialOrderManager[exec_q] - dep_evs = _manager.submitted_events - # always allocate the temporary as - # int32 and usm-device to ensure that atomic updates - # are supported - res_tmp = dpt.empty( - res_shape, - dtype=dpt.int32, - usm_type="device", - sycl_queue=exec_q, - ) - hev0, ev0 = func( - src=x_tmp, - trailing_dims_to_reduce=red_nd, - dst=res_tmp, - sycl_queue=exec_q, - depends=dep_evs, - ) - _manager.add_event_pair(hev0, ev0) - - # copy to boolean result array - res = dpt.empty( - res_shape, - dtype=dpt.bool, - usm_type=res_usm_type, - sycl_queue=exec_q, - ) - hev1, ev1 = ti._copy_usm_ndarray_into_usm_ndarray( - src=res_tmp, dst=res, sycl_queue=exec_q, depends=[ev0] - ) - _manager.add_event_pair(hev1, ev1) + if x_tmp.size == 0: + # nothing to reduce over: the result is either empty (a non-reduced + # dimension is zero) or filled with the reduction identity (a reduced + # dimension is zero, e.g. all([]) is True and any([]) is False) + res = dpt.full( + res_shape, + identity, + dtype=dpt.bool, + usm_type=res_usm_type, + sycl_queue=exec_q, + ) + else: + _manager = du.SequentialOrderManager[exec_q] + dep_evs = _manager.submitted_events + # always allocate the temporary as int32 and usm-device to ensure + # that atomic updates are supported + res_tmp = dpt.empty( + res_shape, + dtype=dpt.int32, + usm_type="device", + sycl_queue=exec_q, + ) + hev0, ev0 = func( + src=x_tmp, + trailing_dims_to_reduce=red_nd, + dst=res_tmp, + sycl_queue=exec_q, + depends=dep_evs, + ) + _manager.add_event_pair(hev0, ev0) + + # copy to boolean result array + res = dpt.empty( + res_shape, + dtype=dpt.bool, + usm_type=res_usm_type, + sycl_queue=exec_q, + ) + hev1, ev1 = ti._copy_usm_ndarray_into_usm_ndarray( + src=res_tmp, dst=res, sycl_queue=exec_q, depends=[ev0] + ) + _manager.add_event_pair(hev1, ev1) if keepdims: res_shape = res_shape + (1,) * red_nd @@ -142,7 +153,7 @@ def all(x, /, *, axis=None, keepdims=False): An array with a data type of `bool` containing the results of the logical AND reduction. """ - return _boolean_reduction(x, axis, keepdims, tri._all) + return _boolean_reduction(x, axis, keepdims, tri._all, True) def any(x, /, *, axis=None, keepdims=False): @@ -171,7 +182,7 @@ def any(x, /, *, axis=None, keepdims=False): An array with a data type of `bool` containing the results of the logical OR reduction. """ - return _boolean_reduction(x, axis, keepdims, tri._any) + return _boolean_reduction(x, axis, keepdims, tri._any, False) def _validate_diff_shape(sh1, sh2, axis):