From 0ffa47e20f1b545ecfbe0cb4947446c806a3bd16 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 26 Mar 2025 06:12:58 -0700 Subject: [PATCH 1/5] Implement dpnp.common_type() --- dpnp/dpnp_iface_manipulation.py | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index f156ca6b7969..250dd30261ee 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -38,6 +38,7 @@ """ +import functools import math import operator import warnings @@ -101,6 +102,7 @@ class UniqueInverseResult(NamedTuple): "broadcast_shapes", "broadcast_to", "can_cast", + "common_type", "column_stack", "concat", "concatenate", @@ -1310,6 +1312,62 @@ def can_cast(from_, to, casting="safe"): return dpt.can_cast(dtype_from, to, casting=casting) +def common_type(*arrays): + """ + Return a scalar type which is common to the input arrays. + + The return type will always be an inexact (i.e. floating point or complex) + scalar type, even if all the arrays are integer arrays. + If one of the inputs is an integer array, the minimum precision type + that is returned is determined by the device capabilities. + + For full documentation refer to :obj:`numpy.common_type`. + + Parameters + ---------- + arrays: {dpnp.ndarray, usm_ndarray} + Input arrays. + + Returns + ------- + out: data type + Data type object. + + Examples + -------- + >>> import dpnp as np + >>> np.common_type(np.arange(2, dtype=np.float32)) + numpy.float32 + >>> np.common_type(np.arange(2, dtype=np.float32), np.arange(2)) + numpy.float64 + >>> np.common_type(np.arange(4), np.array([45, 6.j]), np.array([45.0])) + numpy.complex128 + + """ + + if len(arrays) == 0: + return ( + dpnp.float16 + if dpctl.select_default_device().has_aspect_fp16 + else dpnp.float32 + ) + + dpnp.check_supported_arrays_type(*arrays) + + _, exec_q = get_usm_allocations(arrays) + default_float_dtype = dpnp.default_float_type(sycl_queue=exec_q) + dtypes = [] + for a in arrays: + if a.dtype.kind == "b": + raise TypeError("can't get common type for non-numeric array") + if a.dtype.kind in "iu": + dtypes.append(default_float_dtype) + else: + dtypes.append(a.dtype) + + return functools.reduce(numpy.promote_types, dtypes).type + + def column_stack(tup): """ Stacks 1-D and 2-D arrays as columns into a 2-D array. From 1d32bbe1317c27381be8638c63340f9582525b6f Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 26 Mar 2025 06:14:02 -0700 Subject: [PATCH 2/5] Update cupy tests for common_type() --- dpnp/tests/third_party/cupy/test_type_routines.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/dpnp/tests/third_party/cupy/test_type_routines.py b/dpnp/tests/third_party/cupy/test_type_routines.py index 9e59baa7971d..e35b40d90841 100644 --- a/dpnp/tests/third_party/cupy/test_type_routines.py +++ b/dpnp/tests/third_party/cupy/test_type_routines.py @@ -4,7 +4,7 @@ import pytest import dpnp as cupy -from dpnp.tests.helper import has_support_aspect64 +from dpnp.tests.helper import has_support_aspect16, has_support_aspect64 from dpnp.tests.third_party.cupy import testing @@ -47,13 +47,17 @@ def test_can_cast(self, xp, from_dtype, to_dtype): return ret -@pytest.mark.skip("dpnp.common_type() is not implemented yet") class TestCommonType(unittest.TestCase): @testing.numpy_cupy_equal() def test_common_type_empty(self, xp): ret = xp.common_type() assert type(ret) is type + # NumPy always returns float16 for empty input, + # but dpnp returns float32 if the device does not support + # 16-bit precision floating point operations + if xp is numpy and not has_support_aspect16(): + return xp.float32 return ret @testing.for_all_dtypes(no_bool=True) @@ -62,6 +66,11 @@ def test_common_type_single_argument(self, xp, dtype): array = _generate_type_routines_input(xp, dtype, "array") ret = xp.common_type(array) assert type(ret) is type + # NumPy promotes integer types to float64, + # but dpnp may return float32 if the device does not support + # 64-bit precision floating point operations. + if xp is numpy and not has_support_aspect64(): + return xp.float32 return ret @testing.for_all_dtypes_combination( @@ -73,6 +82,8 @@ def test_common_type_two_arguments(self, xp, dtype1, dtype2): array2 = _generate_type_routines_input(xp, dtype2, "array") ret = xp.common_type(array1, array2) assert type(ret) is type + if xp is numpy and not has_support_aspect64(): + return xp.float32 return ret @testing.for_all_dtypes() From cfb2e99749029deaf6549a59d4ab40c4a5f0568c Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 26 Mar 2025 08:04:57 -0700 Subject: [PATCH 3/5] Address remarks --- dpnp/dpnp_iface_manipulation.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index 250dd30261ee..cecb53d839d2 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -1319,7 +1319,8 @@ def common_type(*arrays): The return type will always be an inexact (i.e. floating point or complex) scalar type, even if all the arrays are integer arrays. If one of the inputs is an integer array, the minimum precision type - that is returned is determined by the device capabilities. + that is returned is the default floating point data type for the device + where the input arrays are allocated. For full documentation refer to :obj:`numpy.common_type`. @@ -1339,9 +1340,9 @@ def common_type(*arrays): >>> np.common_type(np.arange(2, dtype=np.float32)) numpy.float32 >>> np.common_type(np.arange(2, dtype=np.float32), np.arange(2)) - numpy.float64 + numpy.float64 # may vary >>> np.common_type(np.arange(4), np.array([45, 6.j]), np.array([45.0])) - numpy.complex128 + numpy.complex128 # may vary """ @@ -1358,9 +1359,9 @@ def common_type(*arrays): default_float_dtype = dpnp.default_float_type(sycl_queue=exec_q) dtypes = [] for a in arrays: - if a.dtype.kind == "b": + if not dpnp.issubdtype(a.dtype, dpnp.number): raise TypeError("can't get common type for non-numeric array") - if a.dtype.kind in "iu": + if dpnp.issubdtype(a.dtype, dpnp.integer): dtypes.append(default_float_dtype) else: dtypes.append(a.dtype) From 01dcdd0828fd05d2cc442fb056f72c08210e8054 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 26 Mar 2025 08:11:42 -0700 Subject: [PATCH 4/5] Move common_type() to dpnp_iface_manipulation.py --- dpnp/dpnp_iface_manipulation.py | 59 ----------------------------- dpnp/dpnp_iface_types.py | 66 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 59 deletions(-) diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index cecb53d839d2..f156ca6b7969 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -38,7 +38,6 @@ """ -import functools import math import operator import warnings @@ -102,7 +101,6 @@ class UniqueInverseResult(NamedTuple): "broadcast_shapes", "broadcast_to", "can_cast", - "common_type", "column_stack", "concat", "concatenate", @@ -1312,63 +1310,6 @@ def can_cast(from_, to, casting="safe"): return dpt.can_cast(dtype_from, to, casting=casting) -def common_type(*arrays): - """ - Return a scalar type which is common to the input arrays. - - The return type will always be an inexact (i.e. floating point or complex) - scalar type, even if all the arrays are integer arrays. - If one of the inputs is an integer array, the minimum precision type - that is returned is the default floating point data type for the device - where the input arrays are allocated. - - For full documentation refer to :obj:`numpy.common_type`. - - Parameters - ---------- - arrays: {dpnp.ndarray, usm_ndarray} - Input arrays. - - Returns - ------- - out: data type - Data type object. - - Examples - -------- - >>> import dpnp as np - >>> np.common_type(np.arange(2, dtype=np.float32)) - numpy.float32 - >>> np.common_type(np.arange(2, dtype=np.float32), np.arange(2)) - numpy.float64 # may vary - >>> np.common_type(np.arange(4), np.array([45, 6.j]), np.array([45.0])) - numpy.complex128 # may vary - - """ - - if len(arrays) == 0: - return ( - dpnp.float16 - if dpctl.select_default_device().has_aspect_fp16 - else dpnp.float32 - ) - - dpnp.check_supported_arrays_type(*arrays) - - _, exec_q = get_usm_allocations(arrays) - default_float_dtype = dpnp.default_float_type(sycl_queue=exec_q) - dtypes = [] - for a in arrays: - if not dpnp.issubdtype(a.dtype, dpnp.number): - raise TypeError("can't get common type for non-numeric array") - if dpnp.issubdtype(a.dtype, dpnp.integer): - dtypes.append(default_float_dtype) - else: - dtypes.append(a.dtype) - - return functools.reduce(numpy.promote_types, dtypes).type - - def column_stack(tup): """ Stacks 1-D and 2-D arrays as columns into a 2-D array. diff --git a/dpnp/dpnp_iface_types.py b/dpnp/dpnp_iface_types.py index 20ffa55f3c4b..11a86193e05a 100644 --- a/dpnp/dpnp_iface_types.py +++ b/dpnp/dpnp_iface_types.py @@ -32,16 +32,25 @@ This module provides public type interface file for the library """ +import functools + +import dpctl import dpctl.tensor as dpt import numpy +import dpnp + from .dpnp_array import dpnp_array +# pylint: disable=no-name-in-module +from .dpnp_utils import get_usm_allocations + __all__ = [ "bool", "bool_", "byte", "cdouble", + "common_type", "complex128", "complex64", "complexfloating", @@ -145,6 +154,63 @@ pi = numpy.pi +def common_type(*arrays): + """ + Return a scalar type which is common to the input arrays. + + The return type will always be an inexact (i.e. floating point or complex) + scalar type, even if all the arrays are integer arrays. + If one of the inputs is an integer array, the minimum precision type + that is returned is the default floating point data type for the device + where the input arrays are allocated. + + For full documentation refer to :obj:`numpy.common_type`. + + Parameters + ---------- + arrays: {dpnp.ndarray, usm_ndarray} + Input arrays. + + Returns + ------- + out: data type + Data type object. + + Examples + -------- + >>> import dpnp as np + >>> np.common_type(np.arange(2, dtype=np.float32)) + numpy.float32 + >>> np.common_type(np.arange(2, dtype=np.float32), np.arange(2)) + numpy.float64 # may vary + >>> np.common_type(np.arange(4), np.array([45, 6.j]), np.array([45.0])) + numpy.complex128 # may vary + + """ + + if len(arrays) == 0: + return ( + dpnp.float16 + if dpctl.select_default_device().has_aspect_fp16 + else dpnp.float32 + ) + + dpnp.check_supported_arrays_type(*arrays) + + _, exec_q = get_usm_allocations(arrays) + default_float_dtype = dpnp.default_float_type(sycl_queue=exec_q) + dtypes = [] + for a in arrays: + if not dpnp.issubdtype(a.dtype, dpnp.number): + raise TypeError("can't get common type for non-numeric array") + if dpnp.issubdtype(a.dtype, dpnp.integer): + dtypes.append(default_float_dtype) + else: + dtypes.append(a.dtype) + + return functools.reduce(numpy.promote_types, dtypes).type + + # pylint: disable=redefined-outer-name def finfo(dtype): """ From 91c093398eb0e4c97a217a18c66185d9446917e6 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 27 Mar 2025 06:45:44 -0700 Subject: [PATCH 5/5] Add See Also section to common_type docs --- dpnp/dpnp_iface_types.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dpnp/dpnp_iface_types.py b/dpnp/dpnp_iface_types.py index 11a86193e05a..6834ad466276 100644 --- a/dpnp/dpnp_iface_types.py +++ b/dpnp/dpnp_iface_types.py @@ -176,6 +176,10 @@ def common_type(*arrays): out: data type Data type object. + See Also + -------- + :obj:`dpnp.dtype` : Create a data type object. + Examples -------- >>> import dpnp as np