Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement dpnp.array_equal and dpnp.array_equiv #1965

Merged
merged 7 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/reference/logic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Comparison
dpnp.allclose
dpnp.isclose
dpnp.array_equal
dpnp.array_equiv
dpnp.greater
dpnp.greater_equal
dpnp.less
Expand Down
19 changes: 0 additions & 19 deletions dpnp/dpnp_iface.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@

__all__ = [
"are_same_logical_tensors",
"array_equal",
"asnumpy",
"astype",
"as_usm_ndarray",
Expand Down Expand Up @@ -173,24 +172,6 @@ def are_same_logical_tensors(ar1, ar2):
)


def array_equal(a1, a2, equal_nan=False):
"""
True if two arrays have the same shape and elements, False otherwise.

For full documentation refer to :obj:`numpy.array_equal`.

See Also
--------
:obj:`dpnp.allclose` : Returns True if two arrays are element-wise equal
within a tolerance.
:obj:`dpnp.array_equiv` : Returns True if input arrays are shape consistent
and all elements equal.

"""

return numpy.array_equal(a1, a2, equal_nan=equal_nan)


def asnumpy(a, order="C"):
"""
Returns the NumPy array with input data.
Expand Down
193 changes: 191 additions & 2 deletions dpnp/dpnp_iface_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,14 @@
import dpnp
from dpnp.dpnp_algo.dpnp_elementwise_common import DPNPBinaryFunc, DPNPUnaryFunc

from .dpnp_utils import get_usm_allocations

__all__ = [
"all",
"allclose",
"any",
"array_equal",
"array_equiv",
npolina4 marked this conversation as resolved.
Show resolved Hide resolved
"equal",
"greater",
"greater_equal",
Expand Down Expand Up @@ -112,7 +116,7 @@ def all(a, /, axis=None, out=None, keepdims=False, *, where=True):
Returns
-------
out : dpnp.ndarray
An array with a data type of `bool`
An array with a data type of `bool`.
containing the results of the logical AND reduction is returned
unless `out` is specified. Otherwise, a reference to `out` is returned.
The result has the same shape as `a` if `axis` is not ``None``
Expand Down Expand Up @@ -276,7 +280,7 @@ def any(a, /, axis=None, out=None, keepdims=False, *, where=True):
Returns
-------
out : dpnp.ndarray
An array with a data type of `bool`
An array with a data type of `bool`.
containing the results of the logical OR reduction is returned
unless `out` is specified. Otherwise, a reference to `out` is returned.
The result has the same shape as `a` if `axis` is not ``None``
Expand Down Expand Up @@ -337,6 +341,191 @@ def any(a, /, axis=None, out=None, keepdims=False, *, where=True):
return dpnp.get_result_array(usm_res, out)


def array_equal(a1, a2, equal_nan=False):
"""
``True`` if two arrays have the same shape and elements, ``False``
otherwise.

npolina4 marked this conversation as resolved.
Show resolved Hide resolved
For full documentation refer to :obj:`numpy.array_equal`.

Parameters
----------
a1 : {dpnp.ndarray, usm_ndarray, scalar}
First input array.
Both inputs `x1` and `x2` can not be scalars at the same time.
a2 : {dpnp.ndarray, usm_ndarray, scalar}
Second input array.
Both inputs `x1` and `x2` can not be scalars at the same time.
equal_nan : bool, optional
Whether to compare ``NaNs`` as equal. If the dtype of `a1` and `a2` is
complex, values will be considered equal if either the real or the
imaginary component of a given value is ``NaN``.
Default: ``False``.

Returns
-------
b : dpnp.ndarray
An array with a data type of `bool`.
Returns ``True`` if the arrays are equal.

See Also
--------
:obj:`dpnp.allclose`: Returns ``True`` if two arrays are element-wise equal
within a tolerance.
:obj:`dpnp.array_equiv`: Returns ``True`` if input arrays are shape
consistent and all elements equal.

Examples
--------
>>> import dpnp as np
>>> a = np.array([1, 2])
>>> b = np.array([1, 2])
>>> np.array_equal(a, b)
array(True)

>>> b = np.array([1, 2, 3])
>>> np.array_equal(a, b)
array(False)

>>> b = np.array([1, 4])
>>> np.array_equal(a, b)
array(False)

>>> a = np.array([1, np.nan])
>>> np.array_equal(a, a)
array(False)

>>> np.array_equal(a, a, equal_nan=True)
array(True)

When ``equal_nan`` is ``True``, complex values with nan components are
considered equal if either the real *or* the imaginary components are
``NaNs``.

>>> a = np.array([1 + 1j])
>>> b = a.copy()
>>> a.real = np.nan
>>> b.imag = np.nan
>>> np.array_equal(a, b, equal_nan=True)
array(True)

"""

dpnp.check_supported_arrays_type(a1, a2, scalar_type=True)
if dpnp.isscalar(a1):
usm_type_alloc = a2.usm_type
sycl_queue_alloc = a2.sycl_queue
a1 = dpnp.array(
a1,
dtype=dpnp.result_type(a1, a2),
usm_type=usm_type_alloc,
sycl_queue=sycl_queue_alloc,
)
elif dpnp.isscalar(a2):
usm_type_alloc = a1.usm_type
sycl_queue_alloc = a1.sycl_queue
a2 = dpnp.array(
a2,
dtype=dpnp.result_type(a1, a2),
usm_type=usm_type_alloc,
sycl_queue=sycl_queue_alloc,
)
else:
usm_type_alloc, sycl_queue_alloc = get_usm_allocations([a1, a2])

if a1.shape != a2.shape:
return dpnp.array(
False, usm_type=usm_type_alloc, sycl_queue=sycl_queue_alloc
)

if not equal_nan:
return (a1 == a2).all()

if a1 is a2:
# NaN will compare equal so an array will compare equal to itself
return dpnp.array(
npolina4 marked this conversation as resolved.
Show resolved Hide resolved
npolina4 marked this conversation as resolved.
Show resolved Hide resolved
True, usm_type=usm_type_alloc, sycl_queue=sycl_queue_alloc
)

if not (
dpnp.issubdtype(a1, dpnp.inexact) or dpnp.issubdtype(a2, dpnp.inexact)
):
return (a1 == a2).all()
npolina4 marked this conversation as resolved.
Show resolved Hide resolved

# Handling NaN values if equal_nan is True
a1nan, a2nan = isnan(a1), isnan(a2)
# NaNs occur at different locations
if not (a1nan == a2nan).all():
return dpnp.array(
npolina4 marked this conversation as resolved.
Show resolved Hide resolved
False, usm_type=usm_type_alloc, sycl_queue=sycl_queue_alloc
)
# Shapes of a1, a2 and masks are guaranteed to be consistent by this point
return (a1[~a1nan] == a2[~a1nan]).all()


def array_equiv(a1, a2):
"""
Returns ``True`` if input arrays are shape consistent and all elements
equal.

Shape consistent means they are either the same shape, or one input array
can be broadcasted to create the same shape as the other one.

For full documentation refer to :obj:`numpy.array_equiv`.

Parameters
----------
a1 : {dpnp.ndarray, usm_ndarray, scalar}
First input array.
Both inputs `x1` and `x2` can not be scalars at the same time.
a2 : {dpnp.ndarray, usm_ndarray, scalar}
Second input array.
Both inputs `x1` and `x2` can not be scalars at the same time.

Returns
-------
out : dpnp.ndarray
An array with a data type of `bool`.
``True`` if equivalent, ``False`` otherwise.

Examples
--------
>>> import dpnp as np
>>> a = np.array([1, 2])
>>> b = np.array([1, 2])
>>> c = np.array([1, 3])
>>> np.array_equiv(a, b)
array(True)
>>> np.array_equiv(a, c)
array(False)

Showing the shape equivalence:

>>> b = np.array([[1, 2], [1, 2]])
>>> c = np.array([[1, 2, 1, 2], [1, 2, 1, 2]])
>>> np.array_equiv(a, b)
array(True)
>>> np.array_equiv(a, c)
array(False)

>>> b = np.array([[1, 2], [1, 3]])
>>> np.array_equiv(a, b)
array(False)

"""

dpnp.check_supported_arrays_type(a1, a2, scalar_type=True)
if not dpnp.isscalar(a1) and not dpnp.isscalar(a2):
usm_type_alloc, sycl_queue_alloc = get_usm_allocations([a1, a2])
try:
dpnp.broadcast_arrays(a1, a2)
except ValueError:
return dpnp.array(
False, usm_type=usm_type_alloc, sycl_queue=sycl_queue_alloc
)
return (a1 == a2).all()


_EQUAL_DOCSTRING = """
Calculates equality test results for each element `x1_i` of the input array `x1`
with the respective element `x2_i` of the input array `x2`.
Expand Down
101 changes: 101 additions & 0 deletions tests/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,104 @@ def test_isclose(dtype, rtol, atol):
np_res = numpy.isclose(a, b, 1e-05, 1e-08)
dpnp_res = dpnp.isclose(dpnp_a, dpnp_b, rtol, atol)
assert_allclose(dpnp_res, np_res)


@pytest.mark.parametrize("a", [numpy.array([1, 2]), numpy.array([1, 1])])
@pytest.mark.parametrize(
"b",
[
numpy.array([1, 2]),
numpy.array([1, 2, 3]),
numpy.array([3, 4]),
numpy.array([1, 3]),
numpy.array([1]),
numpy.array([[1], [1]]),
numpy.array([2]),
numpy.array([[1], [2]]),
numpy.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),
],
)
def test_array_equiv(a, b):
result = dpnp.array_equiv(dpnp.array(a), dpnp.array(b))
npolina4 marked this conversation as resolved.
Show resolved Hide resolved
expected = numpy.array_equiv(a, b)

assert_equal(expected, result)


@pytest.mark.parametrize("dtype", get_all_dtypes(no_bool=True, no_complex=True))
def test_array_equiv_dtype(dtype):
a = numpy.array([1, 2], dtype=dtype)
b = numpy.array([1, 2], dtype=dtype)
c = numpy.array([1, 3], dtype=dtype)

result = dpnp.array_equiv(dpnp.array(a), dpnp.array(b))
expected = numpy.array_equiv(a, b)

assert_equal(expected, result)

result = dpnp.array_equiv(dpnp.array(a), dpnp.array(c))
expected = numpy.array_equiv(a, c)

assert_equal(expected, result)


@pytest.mark.parametrize("a", [numpy.array([1, 2]), numpy.array([1, 1])])
def test_array_equiv_scalar(a):
b = 1
result = dpnp.array_equiv(dpnp.array(a), b)
expected = numpy.array_equiv(a, b)

assert_equal(expected, result)


@pytest.mark.parametrize("dtype", get_all_dtypes(no_bool=True, no_complex=True))
@pytest.mark.parametrize("equal_nan", [True, False])
def test_array_equal_dtype(dtype, equal_nan):
a = numpy.array([1, 2], dtype=dtype)
b = numpy.array([1, 2], dtype=dtype)
c = numpy.array([1, 3], dtype=dtype)

result = dpnp.array_equal(dpnp.array(a), dpnp.array(b), equal_nan=equal_nan)
expected = numpy.array_equal(a, b, equal_nan=equal_nan)

assert_equal(expected, result)

result = dpnp.array_equal(dpnp.array(a), dpnp.array(c), equal_nan=equal_nan)
expected = numpy.array_equal(a, c, equal_nan=equal_nan)

assert_equal(expected, result)


@pytest.mark.parametrize(
"a",
[
numpy.array([1, 2]),
numpy.array([1.0, numpy.nan]),
numpy.array([1.0, numpy.inf]),
],
)
def test_array_equal_same_arr(a):
expected = numpy.array_equal(a, a)
b = dpnp.array(a)
result = dpnp.array_equal(b, b)
assert_equal(expected, result)

expected = numpy.array_equal(a, a, equal_nan=True)
result = dpnp.array_equal(b, b, equal_nan=True)
assert_equal(expected, result)


@pytest.mark.parametrize(
"a",
[
numpy.array([1, 2]),
numpy.array([1.0, numpy.nan]),
numpy.array([1.0, numpy.inf]),
],
)
def test_array_equal_nan(a):
a = numpy.array([1.0, numpy.nan])
b = numpy.array([1.0, 2.0])
result = dpnp.array_equal(dpnp.array(a), dpnp.array(b), equal_nan=True)
expected = numpy.array_equal(a, b, equal_nan=True)
assert_equal(expected, result)
2 changes: 2 additions & 0 deletions tests/test_sycl_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,8 @@ def test_2in_1out(func, data1, data2, device):
@pytest.mark.parametrize(
"op",
[
"array_equal",
"array_equiv",
"equal",
"greater",
"greater_equal",
Expand Down
2 changes: 2 additions & 0 deletions tests/test_usm_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ def test_coerced_usm_types_logic_op_1in(op, usm_type_x):
@pytest.mark.parametrize(
"op",
[
"array_equal",
"array_equiv",
"equal",
"greater",
"greater_equal",
Expand Down
Loading
Loading