diff --git a/CHANGELOG.md b/CHANGELOG.md index e5c37e4d748..8d01978e60d 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 67287ddf199..8cd8bd3c08e 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 fbc22032aba..edb713830e4 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") diff --git a/dpnp/tests/third_party/cupy/testing/_loops.py b/dpnp/tests/third_party/cupy/testing/_loops.py index c85481e2092..bcbc1de3c45 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)