Skip to content

Commit cb56101

Browse files
authored
Leverage on dpctl.tensor implementation in dpnp.count_nonzero (#1962)
* Leverage on dpctl.tensor implementation of count_nonzero * Extend dpnp.get_result_array() to accept dpt.usm_ndarray * Updated dpnp.mean() per review comment * Add more dpnp tests to cover different use cases
1 parent d2c623b commit cb56101

File tree

7 files changed

+161
-64
lines changed

7 files changed

+161
-64
lines changed

dpnp/dpnp_iface.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ def get_result_array(a, out=None, casting="safe"):
654654
655655
Parameters
656656
----------
657-
a : {dpnp_array}
657+
a : {dpnp.ndarray, usm_ndarray}
658658
Input array.
659659
out : {dpnp.ndarray, usm_ndarray}
660660
If provided, value of `a` array will be copied into it
@@ -671,6 +671,8 @@ def get_result_array(a, out=None, casting="safe"):
671671
"""
672672

673673
if out is None:
674+
if isinstance(a, dpt.usm_ndarray):
675+
return dpnp_array._create_from_usm_ndarray(a)
674676
return a
675677

676678
if isinstance(out, dpt.usm_ndarray):

dpnp/dpnp_iface_counting.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,38 @@
4444
__all__ = ["count_nonzero"]
4545

4646

47-
def count_nonzero(a, axis=None, *, keepdims=False):
47+
def count_nonzero(a, axis=None, *, keepdims=False, out=None):
4848
"""
4949
Counts the number of non-zero values in the array `a`.
5050
5151
For full documentation refer to :obj:`numpy.count_nonzero`.
5252
53+
Parameters
54+
----------
55+
a : {dpnp.ndarray, usm_ndarray}
56+
The array for which to count non-zeros.
57+
axis : {None, int, tuple}, optional
58+
Axis or tuple of axes along which to count non-zeros.
59+
Default value means that non-zeros will be counted along a flattened
60+
version of `a`.
61+
Default: ``None``.
62+
keepdims : bool, optional
63+
If this is set to ``True``, the axes that are counted are left in the
64+
result as dimensions with size one. With this option, the result will
65+
broadcast correctly against the input array.
66+
Default: ``False``.
67+
out : {None, dpnp.ndarray, usm_ndarray}, optional
68+
The array into which the result is written. The data type of `out` must
69+
match the expected shape and the expected data type of the result.
70+
If ``None`` then a new array is returned.
71+
Default: ``None``.
72+
5373
Returns
5474
-------
5575
out : dpnp.ndarray
5676
Number of non-zero values in the array along a given axis.
57-
Otherwise, a zero-dimensional array with the total number of
58-
non-zero values in the array is returned.
59-
60-
Limitations
61-
-----------
62-
Parameters `a` is supported as either :class:`dpnp.ndarray`
63-
or :class:`dpctl.tensor.usm_ndarray`.
64-
Otherwise ``TypeError`` exception will be raised.
65-
Input array data types are limited by supported DPNP :ref:`Data types`.
77+
Otherwise, a zero-dimensional array with the total number of non-zero
78+
values in the array is returned.
6679
6780
See Also
6881
--------
@@ -87,8 +100,10 @@ def count_nonzero(a, axis=None, *, keepdims=False):
87100
88101
"""
89102

90-
# TODO: might be improved by implementing an extension
91-
# with `count_nonzero` kernel
92103
usm_a = dpnp.get_usm_ndarray(a)
93-
usm_a = dpt.astype(usm_a, dpnp.bool, copy=False)
94-
return dpnp.sum(usm_a, axis=axis, dtype=dpnp.intp, keepdims=keepdims)
104+
usm_out = None if out is None else dpnp.get_usm_ndarray(out)
105+
106+
usm_res = dpt.count_nonzero(
107+
usm_a, axis=axis, keepdims=keepdims, out=usm_out
108+
)
109+
return dpnp.get_result_array(usm_res, out)

dpnp/dpnp_iface_logic.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151

5252
import dpnp
5353
from dpnp.dpnp_algo.dpnp_elementwise_common import DPNPBinaryFunc, DPNPUnaryFunc
54-
from dpnp.dpnp_array import dpnp_array
5554

5655
__all__ = [
5756
"all",
@@ -167,13 +166,11 @@ def all(a, /, axis=None, out=None, keepdims=False, *, where=True):
167166

168167
dpnp.check_limitations(where=where)
169168

170-
dpt_array = dpnp.get_usm_ndarray(a)
171-
result = dpnp_array._create_from_usm_ndarray(
172-
dpt.all(dpt_array, axis=axis, keepdims=keepdims)
173-
)
169+
usm_a = dpnp.get_usm_ndarray(a)
170+
usm_res = dpt.all(usm_a, axis=axis, keepdims=keepdims)
171+
174172
# TODO: temporary solution until dpt.all supports out parameter
175-
result = dpnp.get_result_array(result, out)
176-
return result
173+
return dpnp.get_result_array(usm_res, out)
177174

178175

179176
def allclose(a, b, rtol=1.0e-5, atol=1.0e-8, equal_nan=False):
@@ -333,13 +330,11 @@ def any(a, /, axis=None, out=None, keepdims=False, *, where=True):
333330

334331
dpnp.check_limitations(where=where)
335332

336-
dpt_array = dpnp.get_usm_ndarray(a)
337-
result = dpnp_array._create_from_usm_ndarray(
338-
dpt.any(dpt_array, axis=axis, keepdims=keepdims)
339-
)
333+
usm_a = dpnp.get_usm_ndarray(a)
334+
usm_res = dpt.any(usm_a, axis=axis, keepdims=keepdims)
335+
340336
# TODO: temporary solution until dpt.any supports out parameter
341-
result = dpnp.get_result_array(result, out)
342-
return result
337+
return dpnp.get_result_array(usm_res, out)
343338

344339

345340
_EQUAL_DOCSTRING = """

dpnp/dpnp_iface_searching.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,5 @@ def where(condition, x=None, y=None, /, *, order="K", out=None):
391391
usm_condition = dpnp.get_usm_ndarray(condition)
392392

393393
usm_out = None if out is None else dpnp.get_usm_ndarray(out)
394-
result = dpnp_array._create_from_usm_ndarray(
395-
dpt.where(usm_condition, usm_x, usm_y, order=order, out=usm_out)
396-
)
397-
return dpnp.get_result_array(result, out)
394+
usm_res = dpt.where(usm_condition, usm_x, usm_y, order=order, out=usm_out)
395+
return dpnp.get_result_array(usm_res, out)

dpnp/dpnp_iface_statistics.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -614,13 +614,12 @@ def mean(a, /, axis=None, dtype=None, out=None, keepdims=False, *, where=True):
614614

615615
dpnp.check_limitations(where=where)
616616

617-
dpt_array = dpnp.get_usm_ndarray(a)
618-
result = dpnp_array._create_from_usm_ndarray(
619-
dpt.mean(dpt_array, axis=axis, keepdims=keepdims)
620-
)
621-
result = result.astype(dtype) if dtype is not None else result
617+
usm_a = dpnp.get_usm_ndarray(a)
618+
usm_res = dpt.mean(usm_a, axis=axis, keepdims=keepdims)
619+
if dtype is not None:
620+
usm_res = dpt.astype(usm_res, dtype)
622621

623-
return dpnp.get_result_array(result, out, casting="same_kind")
622+
return dpnp.get_result_array(usm_res, out, casting="same_kind")
624623

625624

626625
def median(x1, axis=None, out=None, overwrite_input=False, keepdims=False):
@@ -904,11 +903,9 @@ def std(
904903
)
905904
dpnp.sqrt(result, out=result)
906905
else:
907-
dpt_array = dpnp.get_usm_ndarray(a)
908-
result = dpnp_array._create_from_usm_ndarray(
909-
dpt.std(dpt_array, axis=axis, correction=ddof, keepdims=keepdims)
910-
)
911-
result = dpnp.get_result_array(result, out)
906+
usm_a = dpnp.get_usm_ndarray(a)
907+
usm_res = dpt.std(usm_a, axis=axis, correction=ddof, keepdims=keepdims)
908+
result = dpnp.get_result_array(usm_res, out)
912909

913910
if dtype is not None and out is None:
914911
result = result.astype(dtype, casting="same_kind")
@@ -1028,11 +1025,9 @@ def var(
10281025

10291026
dpnp.divide(result, cnt, out=result)
10301027
else:
1031-
dpt_array = dpnp.get_usm_ndarray(a)
1032-
result = dpnp_array._create_from_usm_ndarray(
1033-
dpt.var(dpt_array, axis=axis, correction=ddof, keepdims=keepdims)
1034-
)
1035-
result = dpnp.get_result_array(result, out)
1028+
usm_a = dpnp.get_usm_ndarray(a)
1029+
usm_res = dpt.var(usm_a, axis=axis, correction=ddof, keepdims=keepdims)
1030+
result = dpnp.get_result_array(usm_res, out)
10361031

10371032
if out is None and dtype is not None:
10381033
result = result.astype(dtype, casting="same_kind")

dpnp/dpnp_utils/dpnp_utils_reduction.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626

2727
import dpnp
28-
from dpnp.dpnp_array import dpnp_array
2928

3029
__all__ = ["dpnp_wrap_reduction_call"]
3130

@@ -53,5 +52,4 @@ def dpnp_wrap_reduction_call(
5352

5453
kwargs["out"] = usm_out
5554
res_usm = _reduction_fn(*args, **kwargs)
56-
res = dpnp_array._create_from_usm_ndarray(res_usm)
57-
return dpnp.get_result_array(res, input_out, casting="unsafe")
55+
return dpnp.get_result_array(res_usm, input_out, casting="unsafe")

tests/test_counting.py

Lines changed: 107 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,124 @@
11
import numpy
22
import pytest
3+
from dpctl.tensor._numpy_helper import AxisError
34
from numpy.testing import (
45
assert_allclose,
6+
assert_equal,
7+
assert_raises,
58
)
69

710
import dpnp
811

912
from .helper import (
1013
get_all_dtypes,
14+
get_float_dtypes,
1115
)
1216

1317

14-
@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True))
15-
@pytest.mark.parametrize("size", [2, 4, 8, 16, 3, 9, 27, 81])
16-
def test_count_nonzero(dtype, size):
17-
if dtype != dpnp.bool:
18-
a = numpy.arange(size, dtype=dtype)
19-
else:
20-
a = numpy.resize(numpy.arange(2, dtype=dtype), size)
18+
class TestCountNonZero:
19+
@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True))
20+
@pytest.mark.parametrize("size", [2, 4, 8, 16, 3, 9, 27, 81])
21+
def test_basic(self, dtype, size):
22+
if dtype != dpnp.bool:
23+
a = numpy.arange(size, dtype=dtype)
24+
else:
25+
a = numpy.resize(numpy.arange(2, dtype=dtype), size)
2126

22-
for i in range(int(size / 2)):
23-
a[(i * (int(size / 3) - 1)) % size] = 0
27+
for i in range(int(size / 2)):
28+
a[(i * (int(size / 3) - 1)) % size] = 0
2429

25-
ia = dpnp.array(a)
30+
ia = dpnp.array(a)
2631

27-
np_res = numpy.count_nonzero(a)
28-
dpnp_res = dpnp.count_nonzero(ia)
32+
result = dpnp.count_nonzero(ia)
33+
expected = numpy.count_nonzero(a)
34+
assert_allclose(result, expected)
2935

30-
assert_allclose(dpnp_res, np_res)
36+
@pytest.mark.parametrize("data", [[], [0], [1]])
37+
def test_trivial(self, data):
38+
a = numpy.array(data)
39+
ia = dpnp.array(a)
40+
41+
result = dpnp.count_nonzero(ia)
42+
expected = numpy.count_nonzero(a)
43+
assert_allclose(result, expected)
44+
45+
@pytest.mark.parametrize("data", [[], [0], [1]])
46+
def test_trivial_boolean_dtype(self, data):
47+
a = numpy.array(data, dtype="?")
48+
ia = dpnp.array(a)
49+
50+
result = dpnp.count_nonzero(ia)
51+
expected = numpy.count_nonzero(a)
52+
assert_allclose(result, expected)
53+
54+
@pytest.mark.parametrize("axis", [0, 1])
55+
def test_axis_basic(self, axis):
56+
a = numpy.array([[0, 1, 7, 0, 0], [3, 0, 0, 2, 19]])
57+
ia = dpnp.array(a)
58+
59+
result = dpnp.count_nonzero(ia, axis=axis)
60+
expected = numpy.count_nonzero(a, axis=axis)
61+
assert_equal(result, expected)
62+
63+
@pytest.mark.parametrize("xp", [numpy, dpnp])
64+
def test_axis_raises(self, xp):
65+
a = xp.array([[0, 1, 7, 0, 0], [3, 0, 0, 2, 19]])
66+
67+
assert_raises(ValueError, xp.count_nonzero, a, axis=(1, 1))
68+
assert_raises(TypeError, xp.count_nonzero, a, axis="foo")
69+
assert_raises(AxisError, xp.count_nonzero, a, axis=3)
70+
71+
# different exception type in numpy and dpnp
72+
with pytest.raises((ValueError, TypeError)):
73+
xp.count_nonzero(a, axis=xp.array([[1], [2]]))
74+
75+
@pytest.mark.parametrize("dt", get_all_dtypes(no_none=True))
76+
@pytest.mark.parametrize("axis", [0, 1, (0, 1), None])
77+
def test_axis_all_dtypes(self, dt, axis):
78+
a = numpy.zeros((3, 3), dtype=dt)
79+
a[0, 0] = a[1, 0] = 1
80+
ia = dpnp.array(a)
81+
82+
result = dpnp.count_nonzero(ia, axis=axis)
83+
expected = numpy.count_nonzero(a, axis=axis)
84+
assert_equal(result, expected)
85+
86+
def test_axis_empty(self):
87+
axis = ()
88+
a = numpy.array([[0, 0, 1], [1, 0, 1]])
89+
ia = dpnp.array(a)
90+
91+
result = dpnp.count_nonzero(ia, axis=axis)
92+
expected = numpy.count_nonzero(a, axis=axis)
93+
assert_equal(result, expected)
94+
95+
@pytest.mark.parametrize("axis", [None, 0, 1])
96+
def test_keepdims(self, axis):
97+
a = numpy.array([[0, 0, 1, 0], [0, 3, 5, 0], [7, 9, 2, 0]])
98+
ia = dpnp.array(a)
99+
100+
result = dpnp.count_nonzero(ia, axis=axis, keepdims=True)
101+
expected = numpy.count_nonzero(a, axis=axis, keepdims=True)
102+
assert_equal(result, expected)
103+
104+
@pytest.mark.parametrize("dt", get_all_dtypes(no_none=True))
105+
def test_out(self, dt):
106+
a = numpy.array([[0, 1, 0], [2, 0, 3]], dtype=dt)
107+
ia = dpnp.array(a)
108+
iout = dpnp.empty_like(ia, shape=ia.shape[1], dtype=dpnp.intp)
109+
110+
result = dpnp.count_nonzero(ia, axis=0, out=iout)
111+
expected = numpy.count_nonzero(a, axis=0) # no out keyword
112+
assert_equal(result, expected)
113+
assert result is iout
114+
115+
@pytest.mark.parametrize("dt", get_float_dtypes())
116+
def test_out_floating_dtype(self, dt):
117+
a = dpnp.array([[0, 1, 0], [2, 0, 3]])
118+
out = dpnp.empty_like(a, shape=a.shape[1], dtype=dt)
119+
assert_raises(ValueError, dpnp.count_nonzero, a, axis=0, out=out)
120+
121+
def test_array_method(self):
122+
a = numpy.array([[1, 0, 0], [4, 0, 6]])
123+
ia = dpnp.array(a)
124+
assert_equal(ia.nonzero(), a.nonzero())

0 commit comments

Comments
 (0)