From e5966bc3f70de9183ace7877b1dee719ed5da430 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 11 Aug 2026 18:13:42 +0200 Subject: [PATCH 1/2] Fix ValueError in dpnp.bincount for empty input arrays dpnp.bincount raised a ValueError on an empty input array because the Python layer reduces over the array with dpnp.max/dpnp.min to determine binning edges, and those reductions have no identity for zero-size input. Short-circuit empty input and return an intp array of zeros of length minlength, matching NumPy behavior (which returns intp even when weights are provided). --- CHANGELOG.md | 1 + dpnp/dpnp_iface_histograms.py | 4 ++++ .../cupy/statistics_tests/test_histogram.py | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5c37e4d748d..8d01978e60df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ This release is compatible with NumPy 2.5. * Fixed `dpnp.interp` with an empty input array `x` to return an empty array with the correct dtype [#2985](https://github.com/IntelPython/dpnp/pull/2985) * 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) ### Security diff --git a/dpnp/dpnp_iface_histograms.py b/dpnp/dpnp_iface_histograms.py index 67287ddf199a..8cd8bd3c08e8 100644 --- a/dpnp/dpnp_iface_histograms.py +++ b/dpnp/dpnp_iface_histograms.py @@ -374,6 +374,10 @@ def bincount(x, weights=None, minlength=0): queue = x.sycl_queue device = queue.sycl_device + if x.size == 0: + # NumPy returns intp dtype for empty input even when weights is given + return dpnp.zeros_like(x, shape=int(minlength), dtype=dpnp.intp) + if weights is None: ntype = dpnp.dtype(dpnp.intp) else: diff --git a/dpnp/tests/third_party/cupy/statistics_tests/test_histogram.py b/dpnp/tests/third_party/cupy/statistics_tests/test_histogram.py index fbc22032aba2..edb713830e42 100644 --- a/dpnp/tests/third_party/cupy/statistics_tests/test_histogram.py +++ b/dpnp/tests/third_party/cupy/statistics_tests/test_histogram.py @@ -339,6 +339,25 @@ def test_bincount_too_small_minlength(self, dtype): with pytest.raises((ValueError, TypeError)): xp.bincount(x, minlength=-1) + @for_all_dtypes_bincount() + @testing.numpy_cupy_allclose(accept_error=TypeError) + def test_bincount_empty(self, xp, dtype): + x = xp.array([], dtype=dtype) + return xp.bincount(x) + + @for_all_dtypes_bincount() + @testing.numpy_cupy_allclose(accept_error=TypeError) + def test_bincount_empty_with_minlength(self, xp, dtype): + x = xp.array([], dtype=dtype) + return xp.bincount(x, minlength=2) + + @for_all_dtypes_combination_bincount(names=["x_type", "w_type"]) + @testing.numpy_cupy_allclose(accept_error=TypeError) + def test_bincount_empty_with_weight(self, xp, x_type, w_type): + x = xp.array([], dtype=x_type) + w = xp.array([], dtype=w_type) + return xp.bincount(x, weights=w, minlength=2) + # This class compares CUB results against NumPy's @unittest.skipUnless(False, "The CUB routine is not enabled") From a01584206329bd4f9750e9f6ef6b8c006545a214 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 11 Aug 2026 19:41:43 +0200 Subject: [PATCH 2/2] Skip device-unsupported dtypes in for_dtypes_combination for_dtypes already skips dtypes the default device cannot represent natively (float64/complex128 without fp64 support, float16 without fp16 support), but for_dtypes_combination did not, so combination tests could attempt to allocate an unsupported-dtype array and fail with a device ValueError instead of exercising the intended code path. This surfaced in the new bincount empty-with-weights test. Factor the per-dtype check into a shared helper and apply it in both decorators so combination tests skip such dtypes consistently. --- dpnp/tests/third_party/cupy/testing/_loops.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/dpnp/tests/third_party/cupy/testing/_loops.py b/dpnp/tests/third_party/cupy/testing/_loops.py index c85481e20928..bcbc1de3c45a 100644 --- a/dpnp/tests/third_party/cupy/testing/_loops.py +++ b/dpnp/tests/third_party/cupy/testing/_loops.py @@ -991,6 +991,16 @@ def test_func(*args, **kw): return decorator +def _dtype_supported_by_default_device(dtype): + """Skip dtypes the default device cannot represent natively.""" + dtype = numpy.dtype(dtype).type + if dtype in (numpy.float64, numpy.complex128): + return has_support_aspect64() + if dtype == numpy.float16: + return select_default_device().has_aspect_fp16 + return True + + def for_dtypes(dtypes, name="dtype", xfail_dtypes=None): """Decorator for parameterized dtype test. @@ -1008,16 +1018,7 @@ def decorator(impl): @_wraps_partial(impl, name) def test_func(*args, **kw): for dtype in dtypes: - if ( - numpy.dtype(dtype).type in (numpy.float64, numpy.complex128) - and not has_support_aspect64() - ): - continue - - if ( - numpy.dtype(dtype).type == numpy.float16 - and not select_default_device().has_aspect_fp16 - ): + if not _dtype_supported_by_default_device(dtype): continue try: @@ -1331,6 +1332,12 @@ def decorator(impl): @_wraps_partial(impl, *names) def test_func(*args, **kw): for dtypes in combination: + if not all( + _dtype_supported_by_default_device(dtype) + for dtype in dtypes.values() + ): + continue + kw_copy = kw.copy() kw_copy.update(dtypes)