diff --git a/.github/workflows/array-api-skips.txt b/.github/workflows/array-api-skips.txt index 6582f9d7cca0..4ad6df22b259 100644 --- a/.github/workflows/array-api-skips.txt +++ b/.github/workflows/array-api-skips.txt @@ -1,19 +1,5 @@ # array API tests to be skipped -# missing unique-like functions -array_api_tests/test_has_names.py::test_has_names[set-unique_all] -array_api_tests/test_has_names.py::test_has_names[set-unique_counts] -array_api_tests/test_has_names.py::test_has_names[set-unique_inverse] -array_api_tests/test_has_names.py::test_has_names[set-unique_values] -array_api_tests/test_set_functions.py::test_unique_all -array_api_tests/test_set_functions.py::test_unique_counts -array_api_tests/test_set_functions.py::test_unique_inverse -array_api_tests/test_set_functions.py::test_unique_values -array_api_tests/test_signatures.py::test_func_signature[unique_all] -array_api_tests/test_signatures.py::test_func_signature[unique_counts] -array_api_tests/test_signatures.py::test_func_signature[unique_inverse] -array_api_tests/test_signatures.py::test_func_signature[unique_values] - # hypothesis found failures array_api_tests/test_operators_and_elementwise_functions.py::test_clip diff --git a/doc/reference/creation.rst b/doc/reference/array-creation.rst similarity index 97% rename from doc/reference/creation.rst rename to doc/reference/array-creation.rst index 4721ec5ff018..279743c5dae3 100644 --- a/doc/reference/creation.rst +++ b/doc/reference/array-creation.rst @@ -1,4 +1,4 @@ -.. _routines.creation: +.. _routines.array-creation: Array creation routines ======================= diff --git a/doc/reference/manipulation.rst b/doc/reference/array-manipulation.rst similarity index 99% rename from doc/reference/manipulation.rst rename to doc/reference/array-manipulation.rst index 3ee5f3bfbbf3..3a1593a89a31 100644 --- a/doc/reference/manipulation.rst +++ b/doc/reference/array-manipulation.rst @@ -135,7 +135,6 @@ Adding and removing elements dpnp.append dpnp.resize dpnp.trim_zeros - dpnp.unique dpnp.pad diff --git a/doc/reference/binary.rst b/doc/reference/bitwise.rst similarity index 100% rename from doc/reference/binary.rst rename to doc/reference/bitwise.rst diff --git a/doc/reference/misc.rst b/doc/reference/other.rst similarity index 100% rename from doc/reference/misc.rst rename to doc/reference/other.rst index 2fa35e76c056..2ac3f03fd3cc 100644 --- a/doc/reference/misc.rst +++ b/doc/reference/other.rst @@ -10,8 +10,8 @@ Utility :toctree: generated/ :nosignatures: - dpnp.broadcast_shapes dpnp.byte_bounds dpnp.get_include dpnp.show_config dpnp.show_runtime + dpnp.broadcast_shapes diff --git a/doc/reference/routines.rst b/doc/reference/routines.rst index f2c261a7f250..3b91283c8e07 100644 --- a/doc/reference/routines.rst +++ b/doc/reference/routines.rst @@ -11,9 +11,9 @@ These functions cover a subset of .. toctree:: :maxdepth: 2 - creation - manipulation - binary + array-creation + array-manipulation + bitwise dtype fft functional @@ -21,7 +21,9 @@ These functions cover a subset of linalg logic math + other .. polynomials random - sorting + set + sort statistics diff --git a/doc/reference/set.rst b/doc/reference/set.rst new file mode 100644 index 000000000000..90c3b7989a56 --- /dev/null +++ b/doc/reference/set.rst @@ -0,0 +1,29 @@ +Set routines +============ + +.. https://numpy.org/doc/stable/reference/routines.set.html + +Making proper sets +------------------ +.. autosummary:: + :toctree: generated/ + :nosignatures: + + dpnp.unique + dpnp.unique_all + dpnp.unique_counts + dpnp.unique_inverse + dpnp.unique_values + +Boolean operations +------------------ +.. autosummary:: + :toctree: generated/ + :nosignatures: + + dpnp.in1d + dpnp.intersect1d + dpnp.isin + dpnp.setdiff1d + dpnp.setxor1d + dpnp.union1d diff --git a/doc/reference/sorting.rst b/doc/reference/sort.rst similarity index 100% rename from doc/reference/sorting.rst rename to doc/reference/sort.rst diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index 8234bb7916ab..5b6c9445ea3a 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -71,6 +71,24 @@ class InsertDeleteParams(NamedTuple): usm_type: str +# pylint:disable=missing-class-docstring +class UniqueAllResult(NamedTuple): + values: dpnp.ndarray + indices: dpnp.ndarray + inverse_indices: dpnp.ndarray + counts: dpnp.ndarray + + +class UniqueCountsResult(NamedTuple): + values: dpnp.ndarray + counts: dpnp.ndarray + + +class UniqueInverseResult(NamedTuple): + values: dpnp.ndarray + inverse_indices: dpnp.ndarray + + __all__ = [ "append", "array_split", @@ -122,6 +140,10 @@ class InsertDeleteParams(NamedTuple): "transpose", "trim_zeros", "unique", + "unique_all", + "unique_counts", + "unique_inverse", + "unique_values", "unstack", "vsplit", "vstack", @@ -4276,6 +4298,189 @@ def unique( return _unpack_tuple(result) +def unique_all(x, /): + """ + Find the unique elements of an array, and counts, inverse, and indices. + + For full documentation refer to :obj:`numpy.unique_all`. + + Parameters + ---------- + x : {dpnp.ndarray, usm_ndarray} + Input array. It will be flattened if it is not already 1-D. + + Returns + ------- + A namedtuple with the following attributes: + + values : dpnp.ndarray + The unique elements of an input array. + indices : dpnp.ndarray + The first occurring indices for each unique element. + inverse_indices : dpnp.ndarray + The indices from the set of unique elements that reconstruct `x`. + counts : dpnp.ndarray + The corresponding counts for each unique element. + + See Also + -------- + :obj:`dpnp.unique` : Find the unique elements of an array. + + Examples + -------- + >>> import dpnp as np + >>> x = np.array([1, 1, 2]) + >>> uniq = np.unique_all(x) + >>> uniq.values + array([1, 2]) + >>> uniq.indices + array([0, 2]) + >>> uniq.inverse_indices + array([0, 0, 1]) + >>> uniq.counts + array([2, 1]) + + """ + + result = dpnp.unique( + x, + return_index=True, + return_inverse=True, + return_counts=True, + equal_nan=False, + ) + return UniqueAllResult(*result) + + +def unique_counts(x, /): + """ + Find the unique elements and counts of an input array `x`. + + For full documentation refer to :obj:`numpy.unique_counts`. + + Parameters + ---------- + x : {dpnp.ndarray, usm_ndarray} + Input array. It will be flattened if it is not already 1-D. + + Returns + ------- + A namedtuple with the following attributes: + + values : dpnp.ndarray + The unique elements of an input array. + counts : dpnp.ndarray + The corresponding counts for each unique element. + + See Also + -------- + :obj:`dpnp.unique` : Find the unique elements of an array. + + Examples + -------- + >>> import dpnp as np + >>> x = np.array([1, 1, 2]) + >>> uniq = np.unique_counts(x) + >>> uniq.values + array([1, 2]) + >>> uniq.counts + array([2, 1]) + + """ + + result = dpnp.unique( + x, + return_index=False, + return_inverse=False, + return_counts=True, + equal_nan=False, + ) + return UniqueCountsResult(*result) + + +def unique_inverse(x, /): + """ + Find the unique elements of `x` and indices to reconstruct `x`. + + For full documentation refer to :obj:`numpy.unique_inverse`. + + Parameters + ---------- + x : {dpnp.ndarray, usm_ndarray} + Input array. It will be flattened if it is not already 1-D. + + Returns + ------- + A namedtuple with the following attributes: + + values : dpnp.ndarray + The unique elements of an input array. + inverse_indices : dpnp.ndarray + The indices from the set of unique elements that reconstruct `x`. + + See Also + -------- + :obj:`dpnp.unique` : Find the unique elements of an array. + + Examples + -------- + >>> import dpnp as np + >>> x = np.array([1, 1, 2]) + >>> uniq = np.unique_inverse(x) + >>> uniq.values + array([1, 2]) + >>> uniq.inverse_indices + array([0, 0, 1]) + + """ + + result = dpnp.unique( + x, + return_index=False, + return_inverse=True, + return_counts=False, + equal_nan=False, + ) + return UniqueInverseResult(*result) + + +def unique_values(x, /): + """ + Returns the unique elements of an input array `x`. + + For full documentation refer to :obj:`numpy.unique_values`. + + Parameters + ---------- + x : {dpnp.ndarray, usm_ndarray} + Input array. It will be flattened if it is not already 1-D. + + Returns + ------- + out : dpnp.ndarray + The unique elements of an input array. + + See Also + -------- + :obj:`dpnp.unique` : Find the unique elements of an array. + + Examples + -------- + >>> import dpnp as np + >>> np.unique_values(np.array([1, 1, 2])) + array([1, 2]) + + """ + + return dpnp.unique( + x, + return_index=False, + return_inverse=False, + return_counts=False, + equal_nan=False, + ) + + def unstack(x, /, *, axis=0): """ Split an array into a sequence of arrays along the given axis. diff --git a/dpnp/tests/test_manipulation.py b/dpnp/tests/test_manipulation.py index 4dcfe02862f0..22bd84e907e9 100644 --- a/dpnp/tests/test_manipulation.py +++ b/dpnp/tests/test_manipulation.py @@ -21,6 +21,7 @@ get_float_dtypes, get_integer_dtypes, has_support_aspect64, + numpy_version, ) from .third_party.cupy import testing @@ -1822,10 +1823,7 @@ def test_2d_axis_nans(self, dt, axis_kwd, return_kwds, row): if len(return_kwds) == 0: assert_array_equal(result, expected) else: - if ( - len(axis_kwd) == 0 - and numpy.lib.NumpyVersion(numpy.__version__) < "2.0.1" - ): + if len(axis_kwd) == 0 and numpy_version() < "2.0.1": # gh-26961: numpy.unique(..., return_inverse=True, axis=None) # returned flatten unique_inverse till 2.0.1 version expected = ( @@ -1836,6 +1834,20 @@ def test_2d_axis_nans(self, dt, axis_kwd, return_kwds, row): for iv, v in zip(result, expected): assert_array_equal(iv, v) + @testing.with_requires("numpy>=2.0") + @pytest.mark.parametrize( + "func", + ["unique_all", "unique_counts", "unique_inverse", "unique_values"], + ) + def test_array_api_functions(self, func): + a = numpy.array([numpy.nan, 1, 4, 1, 3, 4, 5, 5, 1]) + ia = dpnp.array(a) + + result = getattr(dpnp, func)(ia) + expected = getattr(numpy, func)(a) + for iv, v in zip(result, expected): + assert_array_equal(iv, v) + class TestVsplit: @pytest.mark.parametrize("xp", [numpy, dpnp]) diff --git a/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py b/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py index 9955cf3c5222..acd3e706d352 100644 --- a/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py +++ b/dpnp/tests/third_party/cupy/manipulation_tests/test_add_remove.py @@ -1,9 +1,7 @@ import unittest -import numpy import pytest -import dpnp as cupy from dpnp.tests.helper import has_support_aspect64 from dpnp.tests.third_party.cupy import testing from dpnp.tests.third_party.cupy.testing._loops import ( @@ -316,7 +314,6 @@ def test_unique_equal_nan(self, xp, dtype, equal_nan): ) return xp.unique(a, axis=1, equal_nan=equal_nan) - @pytest.mark.skip("unique_all() is not supported yet") @testing.with_requires("numpy>=2.0") @pytest.mark.parametrize( "attr", ["values", "indices", "inverse_indices", "counts"] @@ -327,7 +324,6 @@ def test_unique_all(self, xp, dtype, attr): a = testing.shaped_random((100, 100), xp, dtype) return getattr(xp.unique_all(a), attr) - @pytest.mark.skip("unique_counts() is not supported yet") @testing.with_requires("numpy>=2.0") @pytest.mark.parametrize("attr", ["values", "counts"]) @testing.for_all_dtypes(no_float16=True, no_bool=True, no_complex=True) @@ -336,7 +332,6 @@ def test_unique_counts(self, xp, dtype, attr): a = testing.shaped_random((100, 100), xp, dtype) return getattr(xp.unique_counts(a), attr) - @pytest.mark.skip("unique_inverse() is not supported yet") @testing.with_requires("numpy>=2.0") @pytest.mark.parametrize("attr", ["values", "inverse_indices"]) @testing.for_all_dtypes(no_float16=True, no_bool=True, no_complex=True) @@ -345,7 +340,6 @@ def test_unique_inverse(self, xp, dtype, attr): a = testing.shaped_random((100, 100), xp, dtype) return getattr(xp.unique_inverse(a), attr) - @pytest.mark.skip("unique_values() is not supported yet") @testing.with_requires("numpy>=2.0") @testing.for_all_dtypes(no_float16=True, no_bool=True, no_complex=True) @testing.numpy_cupy_array_equal()