Skip to content

Commit 131e4e0

Browse files
authored
Disallow minlength=None in dpnp.bincount (#2310)
`minlength=None` was deprecated for `numpy.bincount` since numpy `1.14` and numpy is going to raise `TypeError` exception in `2.3.0` release. The PR propose to update implementation of `dpnp.bincount` and to disallow `minlength=None` in the same way as it will be implemented in NumPy 2.3.0. Additionally there is explicit type check of input arrays added through `dpnp.check_supported_arrays_type` calls at validation step.
1 parent 0d5ffad commit 131e4e0

File tree

3 files changed

+74
-23
lines changed

3 files changed

+74
-23
lines changed

dpnp/dpnp_iface_histograms.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,23 @@ def _get_bin_edges(a, bins, range, usm_type):
225225

226226

227227
def _bincount_validate(x, weights, minlength):
228+
dpnp.check_supported_arrays_type(x)
228229
if x.ndim > 1:
229230
raise ValueError("object too deep for desired array")
231+
230232
if x.ndim < 1:
231233
raise ValueError("object of too small depth for desired array")
234+
232235
if not dpnp.issubdtype(x.dtype, dpnp.integer) and not dpnp.issubdtype(
233236
x.dtype, dpnp.bool
234237
):
235238
raise TypeError("x must be an integer array")
239+
236240
if weights is not None:
241+
dpnp.check_supported_arrays_type(weights)
237242
if x.shape != weights.shape:
238243
raise ValueError("The weights and x don't have the same length.")
244+
239245
if not (
240246
dpnp.issubdtype(weights.dtype, dpnp.integer)
241247
or dpnp.issubdtype(weights.dtype, dpnp.floating)
@@ -245,10 +251,12 @@ def _bincount_validate(x, weights, minlength):
245251
f"Weights must be integer or float. Got {weights.dtype}"
246252
)
247253

248-
if minlength is not None:
249-
minlength = int(minlength)
250-
if minlength < 0:
251-
raise ValueError("minlength must be non-negative")
254+
if minlength is None:
255+
raise TypeError("use 0 instead of None for minlength")
256+
257+
minlength = int(minlength)
258+
if minlength < 0:
259+
raise ValueError("minlength must be non-negative")
252260

253261

254262
def _bincount_run_native(
@@ -262,9 +270,7 @@ def _bincount_run_native(
262270
if min_v < 0:
263271
raise ValueError("x argument must have no negative arguments")
264272

265-
size = int(dpnp.max(max_v)) + 1
266-
if minlength is not None:
267-
size = max(size, minlength)
273+
size = max(int(max_v) + 1, minlength)
268274

269275
# bincount implementation uses atomics, but atomics doesn't work with
270276
# host usm memory
@@ -299,9 +305,9 @@ def _bincount_run_native(
299305
return n_casted
300306

301307

302-
def bincount(x, weights=None, minlength=None):
308+
def bincount(x, weights=None, minlength=0):
303309
"""
304-
bincount(x, /, weights=None, minlength=None)
310+
bincount(x, /, weights=None, minlength=0)
305311
306312
Count number of occurrences of each value in array of non-negative ints.
307313
@@ -313,10 +319,12 @@ def bincount(x, weights=None, minlength=None):
313319
Input 1-dimensional array with non-negative integer values.
314320
weights : {None, dpnp.ndarray, usm_ndarray}, optional
315321
Weights, array of the same shape as `x`.
322+
316323
Default: ``None``
317-
minlength : {None, int}, optional
324+
minlength : int, optional
318325
A minimum number of bins for the output array.
319-
Default: ``None``
326+
327+
Default: ``0``
320328
321329
Returns
322330
-------
@@ -416,6 +424,7 @@ def digitize(x, bins, right=False):
416424
increasing or decreasing.
417425
right : bool, optional
418426
Indicates whether the intervals include the right or the left bin edge.
427+
419428
Default: ``False``.
420429
421430
Returns
@@ -675,6 +684,7 @@ def histogram_bin_edges(a, bins=10, range=None, weights=None):
675684
given range.
676685
If `bins` is a sequence, it defines the bin edges, including the
677686
rightmost edge, allowing for non-uniform bin widths.
687+
678688
Default: ``10``.
679689
range : {None, 2-tuple of float}, optional
680690
The lower and upper range of the bins. If not provided, range is simply
@@ -683,12 +693,14 @@ def histogram_bin_edges(a, bins=10, range=None, weights=None):
683693
affects the automatic bin computation as well. While bin width is
684694
computed to be optimal based on the actual data within `range`, the bin
685695
count will fill the entire range including portions containing no data.
696+
686697
Default: ``None``.
687698
weights : {None, dpnp.ndarray, usm_ndarray}, optional
688699
An array of weights, of the same shape as `a`. Each value in `a` only
689700
contributes its associated weight towards the bin count (instead of 1).
690701
This is currently not used by any of the bin estimators, but may be in
691702
the future.
703+
692704
Default: ``None``.
693705
694706
Returns

dpnp/tests/helper.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from numpy.testing import assert_allclose, assert_array_equal
77

88
import dpnp
9-
from dpnp.tests import config
9+
10+
from . import config
1011

1112

1213
def assert_dtype_allclose(
@@ -86,14 +87,14 @@ def assert_dtype_allclose(
8687
assert dpnp_arr.dtype == numpy_arr.dtype
8788

8889

89-
def get_integer_dtypes(no_unsigned=False):
90+
def get_integer_dtypes(all_int_types=False, no_unsigned=False):
9091
"""
9192
Build a list of integer types supported by DPNP.
9293
"""
9394

9495
dtypes = [dpnp.int32, dpnp.int64]
9596

96-
if config.all_int_types:
97+
if config.all_int_types or all_int_types:
9798
dtypes += [dpnp.int8, dpnp.int16]
9899
if not no_unsigned:
99100
dtypes += [dpnp.uint8, dpnp.uint16, dpnp.uint32, dpnp.uint64]

dpnp/tests/test_histogram.py

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
assert_array_equal,
88
assert_raises,
99
assert_raises_regex,
10-
suppress_warnings,
1110
)
1211

1312
import dpnp
@@ -19,6 +18,7 @@
1918
get_float_dtypes,
2019
get_integer_dtypes,
2120
has_support_aspect64,
21+
numpy_version,
2222
)
2323

2424

@@ -282,9 +282,10 @@ def test_weights(self, density):
282282
assert_dtype_allclose(result_hist, expected_hist)
283283
assert_dtype_allclose(result_edges, expected_edges)
284284

285-
def test_integer_weights(self):
285+
@pytest.mark.parametrize("dt", get_integer_dtypes(all_int_types=True))
286+
def test_integer_weights(self, dt):
286287
v = numpy.array([1, 2, 2, 4])
287-
w = numpy.array([4, 3, 2, 1])
288+
w = numpy.array([4, 3, 2, 1], dtype=dt)
288289

289290
iv = dpnp.array(v)
290291
iw = dpnp.array(w)
@@ -602,19 +603,42 @@ def test_different_bins_amount(self, bins_count):
602603
@pytest.mark.parametrize(
603604
"array",
604605
[[1, 2, 3], [1, 2, 2, 1, 2, 4], [2, 2, 2, 2]],
605-
ids=["[1, 2, 3]", "[1, 2, 2, 1, 2, 4]", "[2, 2, 2, 2]"],
606+
ids=["size=3", "size=6", "size=4"],
606607
)
607-
@pytest.mark.parametrize(
608-
"minlength", [0, 1, 3, 5], ids=["0", "1", "3", "5"]
609-
)
610-
def test_bincount_minlength(self, array, minlength):
608+
@pytest.mark.parametrize("minlength", [0, 1, 3, 5])
609+
def test_minlength(self, array, minlength):
611610
np_a = numpy.array(array)
612611
dpnp_a = dpnp.array(array)
613612

614613
expected = numpy.bincount(np_a, minlength=minlength)
615614
result = dpnp.bincount(dpnp_a, minlength=minlength)
616615
assert_allclose(expected, result)
617616

617+
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
618+
@pytest.mark.parametrize(
619+
"xp",
620+
[
621+
dpnp,
622+
pytest.param(
623+
numpy,
624+
marks=pytest.mark.xfail(
625+
numpy_version() < "2.3.0",
626+
reason="numpy deprecates but accepts that",
627+
strict=True,
628+
),
629+
),
630+
],
631+
)
632+
def test_minlength_none(self, xp):
633+
a = xp.array([1, 2, 3])
634+
assert_raises_regex(
635+
TypeError,
636+
"use 0 instead of None for minlength",
637+
xp.bincount,
638+
a,
639+
minlength=None,
640+
)
641+
618642
@pytest.mark.parametrize(
619643
"array", [[1, 2, 2, 1, 2, 4]], ids=["[1, 2, 2, 1, 2, 4]"]
620644
)
@@ -623,7 +647,7 @@ def test_bincount_minlength(self, array, minlength):
623647
[None, [0.3, 0.5, 0.2, 0.7, 1.0, -0.6], [2, 2, 2, 2, 2, 2]],
624648
ids=["None", "[0.3, 0.5, 0.2, 0.7, 1., -0.6]", "[2, 2, 2, 2, 2, 2]"],
625649
)
626-
def test_bincount_weights(self, array, weights):
650+
def test_weights(self, array, weights):
627651
np_a = numpy.array(array)
628652
np_weights = numpy.array(weights) if weights is not None else weights
629653
dpnp_a = dpnp.array(array)
@@ -633,6 +657,20 @@ def test_bincount_weights(self, array, weights):
633657
result = dpnp.bincount(dpnp_a, weights=dpnp_weights)
634658
assert_allclose(expected, result)
635659

660+
@pytest.mark.parametrize(
661+
"data",
662+
[numpy.arange(5), 3, [2, 1]],
663+
ids=["numpy.ndarray", "scalar", "list"],
664+
)
665+
def test_unsupported_data_weights(self, data):
666+
# check input array
667+
msg = "An array must be any of supported type"
668+
assert_raises_regex(TypeError, msg, dpnp.bincount, data)
669+
670+
# check array of weights
671+
a = dpnp.ones(5, dtype=dpnp.int32)
672+
assert_raises_regex(TypeError, msg, dpnp.bincount, a, weights=data)
673+
636674

637675
class TestHistogramDd:
638676
@pytest.mark.usefixtures("suppress_complex_warning")

0 commit comments

Comments
 (0)