Skip to content

Commit 37e9176

Browse files
authored
Add implementation of unique functions from Python array API (#2320)
The PR implements `dpnp.unique_all`, `dpnp.unique_counts`, `dpnp.unique_inverse` and `dpnp.unique_values` which are required to be compliant with Python array API. All of them are leveraging on `dpnp.unique` call but with different set of input flags passed through.
1 parent b09533e commit 37e9176

File tree

11 files changed

+258
-31
lines changed

11 files changed

+258
-31
lines changed

.github/workflows/array-api-skips.txt

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
11
# array API tests to be skipped
22

3-
# missing unique-like functions
4-
array_api_tests/test_has_names.py::test_has_names[set-unique_all]
5-
array_api_tests/test_has_names.py::test_has_names[set-unique_counts]
6-
array_api_tests/test_has_names.py::test_has_names[set-unique_inverse]
7-
array_api_tests/test_has_names.py::test_has_names[set-unique_values]
8-
array_api_tests/test_set_functions.py::test_unique_all
9-
array_api_tests/test_set_functions.py::test_unique_counts
10-
array_api_tests/test_set_functions.py::test_unique_inverse
11-
array_api_tests/test_set_functions.py::test_unique_values
12-
array_api_tests/test_signatures.py::test_func_signature[unique_all]
13-
array_api_tests/test_signatures.py::test_func_signature[unique_counts]
14-
array_api_tests/test_signatures.py::test_func_signature[unique_inverse]
15-
array_api_tests/test_signatures.py::test_func_signature[unique_values]
16-
173
# hypothesis found failures
184
array_api_tests/test_operators_and_elementwise_functions.py::test_clip
195

doc/reference/creation.rst renamed to doc/reference/array-creation.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.. _routines.creation:
1+
.. _routines.array-creation:
22

33
Array creation routines
44
=======================

doc/reference/manipulation.rst renamed to doc/reference/array-manipulation.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ Adding and removing elements
135135
dpnp.append
136136
dpnp.resize
137137
dpnp.trim_zeros
138-
dpnp.unique
139138
dpnp.pad
140139

141140

File renamed without changes.

doc/reference/misc.rst renamed to doc/reference/other.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ Utility
1010
:toctree: generated/
1111
:nosignatures:
1212

13-
dpnp.broadcast_shapes
1413
dpnp.byte_bounds
1514
dpnp.get_include
1615
dpnp.show_config
1716
dpnp.show_runtime
17+
dpnp.broadcast_shapes

doc/reference/routines.rst

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ These functions cover a subset of
1111
.. toctree::
1212
:maxdepth: 2
1313

14-
creation
15-
manipulation
16-
binary
14+
array-creation
15+
array-manipulation
16+
bitwise
1717
dtype
1818
fft
1919
functional
2020
indexing
2121
linalg
2222
logic
2323
math
24+
other
2425
.. polynomials
2526
random
26-
sorting
27+
set
28+
sort
2729
statistics

doc/reference/set.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Set routines
2+
============
3+
4+
.. https://numpy.org/doc/stable/reference/routines.set.html
5+
6+
Making proper sets
7+
------------------
8+
.. autosummary::
9+
:toctree: generated/
10+
:nosignatures:
11+
12+
dpnp.unique
13+
dpnp.unique_all
14+
dpnp.unique_counts
15+
dpnp.unique_inverse
16+
dpnp.unique_values
17+
18+
Boolean operations
19+
------------------
20+
.. autosummary::
21+
:toctree: generated/
22+
:nosignatures:
23+
24+
dpnp.in1d
25+
dpnp.intersect1d
26+
dpnp.isin
27+
dpnp.setdiff1d
28+
dpnp.setxor1d
29+
dpnp.union1d
File renamed without changes.

dpnp/dpnp_iface_manipulation.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,24 @@ class InsertDeleteParams(NamedTuple):
7171
usm_type: str
7272

7373

74+
# pylint:disable=missing-class-docstring
75+
class UniqueAllResult(NamedTuple):
76+
values: dpnp.ndarray
77+
indices: dpnp.ndarray
78+
inverse_indices: dpnp.ndarray
79+
counts: dpnp.ndarray
80+
81+
82+
class UniqueCountsResult(NamedTuple):
83+
values: dpnp.ndarray
84+
counts: dpnp.ndarray
85+
86+
87+
class UniqueInverseResult(NamedTuple):
88+
values: dpnp.ndarray
89+
inverse_indices: dpnp.ndarray
90+
91+
7492
__all__ = [
7593
"append",
7694
"array_split",
@@ -122,6 +140,10 @@ class InsertDeleteParams(NamedTuple):
122140
"transpose",
123141
"trim_zeros",
124142
"unique",
143+
"unique_all",
144+
"unique_counts",
145+
"unique_inverse",
146+
"unique_values",
125147
"unstack",
126148
"vsplit",
127149
"vstack",
@@ -4276,6 +4298,189 @@ def unique(
42764298
return _unpack_tuple(result)
42774299

42784300

4301+
def unique_all(x, /):
4302+
"""
4303+
Find the unique elements of an array, and counts, inverse, and indices.
4304+
4305+
For full documentation refer to :obj:`numpy.unique_all`.
4306+
4307+
Parameters
4308+
----------
4309+
x : {dpnp.ndarray, usm_ndarray}
4310+
Input array. It will be flattened if it is not already 1-D.
4311+
4312+
Returns
4313+
-------
4314+
A namedtuple with the following attributes:
4315+
4316+
values : dpnp.ndarray
4317+
The unique elements of an input array.
4318+
indices : dpnp.ndarray
4319+
The first occurring indices for each unique element.
4320+
inverse_indices : dpnp.ndarray
4321+
The indices from the set of unique elements that reconstruct `x`.
4322+
counts : dpnp.ndarray
4323+
The corresponding counts for each unique element.
4324+
4325+
See Also
4326+
--------
4327+
:obj:`dpnp.unique` : Find the unique elements of an array.
4328+
4329+
Examples
4330+
--------
4331+
>>> import dpnp as np
4332+
>>> x = np.array([1, 1, 2])
4333+
>>> uniq = np.unique_all(x)
4334+
>>> uniq.values
4335+
array([1, 2])
4336+
>>> uniq.indices
4337+
array([0, 2])
4338+
>>> uniq.inverse_indices
4339+
array([0, 0, 1])
4340+
>>> uniq.counts
4341+
array([2, 1])
4342+
4343+
"""
4344+
4345+
result = dpnp.unique(
4346+
x,
4347+
return_index=True,
4348+
return_inverse=True,
4349+
return_counts=True,
4350+
equal_nan=False,
4351+
)
4352+
return UniqueAllResult(*result)
4353+
4354+
4355+
def unique_counts(x, /):
4356+
"""
4357+
Find the unique elements and counts of an input array `x`.
4358+
4359+
For full documentation refer to :obj:`numpy.unique_counts`.
4360+
4361+
Parameters
4362+
----------
4363+
x : {dpnp.ndarray, usm_ndarray}
4364+
Input array. It will be flattened if it is not already 1-D.
4365+
4366+
Returns
4367+
-------
4368+
A namedtuple with the following attributes:
4369+
4370+
values : dpnp.ndarray
4371+
The unique elements of an input array.
4372+
counts : dpnp.ndarray
4373+
The corresponding counts for each unique element.
4374+
4375+
See Also
4376+
--------
4377+
:obj:`dpnp.unique` : Find the unique elements of an array.
4378+
4379+
Examples
4380+
--------
4381+
>>> import dpnp as np
4382+
>>> x = np.array([1, 1, 2])
4383+
>>> uniq = np.unique_counts(x)
4384+
>>> uniq.values
4385+
array([1, 2])
4386+
>>> uniq.counts
4387+
array([2, 1])
4388+
4389+
"""
4390+
4391+
result = dpnp.unique(
4392+
x,
4393+
return_index=False,
4394+
return_inverse=False,
4395+
return_counts=True,
4396+
equal_nan=False,
4397+
)
4398+
return UniqueCountsResult(*result)
4399+
4400+
4401+
def unique_inverse(x, /):
4402+
"""
4403+
Find the unique elements of `x` and indices to reconstruct `x`.
4404+
4405+
For full documentation refer to :obj:`numpy.unique_inverse`.
4406+
4407+
Parameters
4408+
----------
4409+
x : {dpnp.ndarray, usm_ndarray}
4410+
Input array. It will be flattened if it is not already 1-D.
4411+
4412+
Returns
4413+
-------
4414+
A namedtuple with the following attributes:
4415+
4416+
values : dpnp.ndarray
4417+
The unique elements of an input array.
4418+
inverse_indices : dpnp.ndarray
4419+
The indices from the set of unique elements that reconstruct `x`.
4420+
4421+
See Also
4422+
--------
4423+
:obj:`dpnp.unique` : Find the unique elements of an array.
4424+
4425+
Examples
4426+
--------
4427+
>>> import dpnp as np
4428+
>>> x = np.array([1, 1, 2])
4429+
>>> uniq = np.unique_inverse(x)
4430+
>>> uniq.values
4431+
array([1, 2])
4432+
>>> uniq.inverse_indices
4433+
array([0, 0, 1])
4434+
4435+
"""
4436+
4437+
result = dpnp.unique(
4438+
x,
4439+
return_index=False,
4440+
return_inverse=True,
4441+
return_counts=False,
4442+
equal_nan=False,
4443+
)
4444+
return UniqueInverseResult(*result)
4445+
4446+
4447+
def unique_values(x, /):
4448+
"""
4449+
Returns the unique elements of an input array `x`.
4450+
4451+
For full documentation refer to :obj:`numpy.unique_values`.
4452+
4453+
Parameters
4454+
----------
4455+
x : {dpnp.ndarray, usm_ndarray}
4456+
Input array. It will be flattened if it is not already 1-D.
4457+
4458+
Returns
4459+
-------
4460+
out : dpnp.ndarray
4461+
The unique elements of an input array.
4462+
4463+
See Also
4464+
--------
4465+
:obj:`dpnp.unique` : Find the unique elements of an array.
4466+
4467+
Examples
4468+
--------
4469+
>>> import dpnp as np
4470+
>>> np.unique_values(np.array([1, 1, 2]))
4471+
array([1, 2])
4472+
4473+
"""
4474+
4475+
return dpnp.unique(
4476+
x,
4477+
return_index=False,
4478+
return_inverse=False,
4479+
return_counts=False,
4480+
equal_nan=False,
4481+
)
4482+
4483+
42794484
def unstack(x, /, *, axis=0):
42804485
"""
42814486
Split an array into a sequence of arrays along the given axis.

dpnp/tests/test_manipulation.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
get_float_dtypes,
2222
get_integer_dtypes,
2323
has_support_aspect64,
24+
numpy_version,
2425
)
2526
from .third_party.cupy import testing
2627

@@ -1822,10 +1823,7 @@ def test_2d_axis_nans(self, dt, axis_kwd, return_kwds, row):
18221823
if len(return_kwds) == 0:
18231824
assert_array_equal(result, expected)
18241825
else:
1825-
if (
1826-
len(axis_kwd) == 0
1827-
and numpy.lib.NumpyVersion(numpy.__version__) < "2.0.1"
1828-
):
1826+
if len(axis_kwd) == 0 and numpy_version() < "2.0.1":
18291827
# gh-26961: numpy.unique(..., return_inverse=True, axis=None)
18301828
# returned flatten unique_inverse till 2.0.1 version
18311829
expected = (
@@ -1836,6 +1834,20 @@ def test_2d_axis_nans(self, dt, axis_kwd, return_kwds, row):
18361834
for iv, v in zip(result, expected):
18371835
assert_array_equal(iv, v)
18381836

1837+
@testing.with_requires("numpy>=2.0")
1838+
@pytest.mark.parametrize(
1839+
"func",
1840+
["unique_all", "unique_counts", "unique_inverse", "unique_values"],
1841+
)
1842+
def test_array_api_functions(self, func):
1843+
a = numpy.array([numpy.nan, 1, 4, 1, 3, 4, 5, 5, 1])
1844+
ia = dpnp.array(a)
1845+
1846+
result = getattr(dpnp, func)(ia)
1847+
expected = getattr(numpy, func)(a)
1848+
for iv, v in zip(result, expected):
1849+
assert_array_equal(iv, v)
1850+
18391851

18401852
class TestVsplit:
18411853
@pytest.mark.parametrize("xp", [numpy, dpnp])

0 commit comments

Comments
 (0)