From f9559459315696473d3af479c7a03f2ae86a5761 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 17 Sep 2024 11:07:39 -0700 Subject: [PATCH 01/23] Enhance `dpnp_array.fill` method Leverages dpctl's strided fill and memset for setting contiguous memory to 0 --- dpnp/dpnp_algo/dpnp_fill.py | 84 +++++++++++++++++++++++++++++++++++++ dpnp/dpnp_array.py | 6 ++- 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 dpnp/dpnp_algo/dpnp_fill.py diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py new file mode 100644 index 000000000000..6dc941275e8e --- /dev/null +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# ***************************************************************************** +# Copyright (c) 2016-2024, Intel Corporation +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. +# ***************************************************************************** + +import dpctl.tensor as dpt +import numpy as np +from dpctl.tensor._tensor_impl import ( + _copy_usm_ndarray_into_usm_ndarray, + _full_usm_ndarray, + _zeros_usm_ndarray, +) +from dpctl.utils import SequentialOrderManager + +import dpnp + + +def dpnp_fill(arr, val): + dpnp.check_supported_arrays_type(arr) + arr = dpnp.get_usm_ndarray(arr) + exec_q = arr.sycl_queue + + dpnp.check_supported_arrays_type(val, scalar_type=True, all_scalars=True) + # if val is an array, process it + if isinstance(val, (dpnp.dpnp_array, dpt.usm_ndarray)): + val = dpnp.get_usm_ndarray(val) + if val.shape != (): + raise ValueError("`val` must be a scalar") + # asarray moves scalar to the correct device + # and casts to the expected dtype + a_val = dpt.asarray( + val, + dtype=arr.dtype, + usm_type=arr.usm_type, + sycl_queue=exec_q, + ) + a_val = dpt.broadcast_to(a_val, arr.shape) + _manager = SequentialOrderManager[exec_q] + dep_evs = _manager.submitted_events + h_ev, c_ev = _copy_usm_ndarray_into_usm_ndarray( + src=a_val, dst=arr, sycl_queue=exec_q, depends=dep_evs + ) + _manager.add_event_pair(h_ev, c_ev) + return + + dt = arr.dtype + val_type = type(val) + if val_type in [float, complex] and dpnp.issubdtype(dt, dpnp.integer): + val = int(val.real) + elif val_type is complex and dpnp.issubdtype(dt, dpnp.floating): + val = val.real + elif val_type is int and dpnp.issubdtype(dt, dpnp.integer): + val = np.asarray(val, dtype=dt)[()] + + _manager = SequentialOrderManager[exec_q] + dep_evs = _manager.submitted_events + # can leverage efficient memset when val is 0 + if arr.flags["FORC"] and val == 0: + h_ev, zeros_ev = _zeros_usm_ndarray(arr, exec_q) + _manager.add_event_pair(h_ev, zeros_ev) + else: + h_ev, fill_ev = _full_usm_ndarray(val, arr, exec_q) + _manager.add_event_pair(h_ev, fill_ev) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 615f709956a9..77f6cd726c6a 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -903,8 +903,10 @@ def fill(self, value): """ - for i in range(self.size): - self.flat[i] = value + # lazy import avoids circular imports + from .dpnp_algo.dpnp_fill import dpnp_fill + + dpnp_fill(self, value) @property def flags(self): From 13edd8481f79dda5a581c09167137fccf9f66c89 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 17 Sep 2024 11:07:47 -0700 Subject: [PATCH 02/23] Fix missing disclaimer in dpnp_arraycreation.py --- dpnp/dpnp_algo/dpnp_arraycreation.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/dpnp/dpnp_algo/dpnp_arraycreation.py b/dpnp/dpnp_algo/dpnp_arraycreation.py index b493efac9931..d52e27e58ddb 100644 --- a/dpnp/dpnp_algo/dpnp_arraycreation.py +++ b/dpnp/dpnp_algo/dpnp_arraycreation.py @@ -1,3 +1,29 @@ +# -*- coding: utf-8 -*- +# ***************************************************************************** +# Copyright (c) 2016-2024, Intel Corporation +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. +# ***************************************************************************** + import math import operator From 4976a2483508942f17e628fb2654f2b5c2befb62 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 17 Sep 2024 11:08:25 -0700 Subject: [PATCH 03/23] Import `dpnp_array` directly --- dpnp/dpnp_algo/dpnp_fill.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index 6dc941275e8e..4bbaff94cbe1 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -34,6 +34,7 @@ from dpctl.utils import SequentialOrderManager import dpnp +from dpnp.dpnp_array import dpnp_array def dpnp_fill(arr, val): @@ -43,7 +44,7 @@ def dpnp_fill(arr, val): dpnp.check_supported_arrays_type(val, scalar_type=True, all_scalars=True) # if val is an array, process it - if isinstance(val, (dpnp.dpnp_array, dpt.usm_ndarray)): + if isinstance(val, (dpnp_array, dpt.usm_ndarray)): val = dpnp.get_usm_ndarray(val) if val.shape != (): raise ValueError("`val` must be a scalar") From 978a08130a69bbc0d4437843f81e2ae7daf81e58 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 17 Sep 2024 13:39:33 -0700 Subject: [PATCH 04/23] Skip `test_fill_with_numpy_scalar_ndarray` New fill implementation does not permit NumPy array values, consistent with fill_diagonal --- .../cupy/core_tests/test_ndarray_copy_and_view.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py b/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py index d81119813872..b53eeee09b76 100644 --- a/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py +++ b/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py @@ -274,9 +274,10 @@ def test_fill(self, xp, dtype): a.fill(1) return a - @testing.with_requires("numpy>=1.24.0") - @testing.for_all_dtypes_combination(("dtype1", "dtype2")) - @testing.numpy_cupy_array_equal(accept_error=ComplexWarning) + @pytest.mark.skip( + "asynchronous fill does not allow numpy scalar array for consistency" + "with dpnp.fill_diagonal" + ) def test_fill_with_numpy_scalar_ndarray(self, xp, dtype1, dtype2): a = testing.shaped_arange((2, 3, 4), xp, dtype1) a.fill(numpy.ones((), dtype=dtype2)) From 2686af9220b58c76356d79c137f301ca4bb21a73 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 18 Sep 2024 09:03:28 -0700 Subject: [PATCH 05/23] Add dependencies to zeros and full kernels in `dpnp_fill` --- dpnp/dpnp_algo/dpnp_fill.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index 4bbaff94cbe1..1c7b35020175 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -48,8 +48,6 @@ def dpnp_fill(arr, val): val = dpnp.get_usm_ndarray(val) if val.shape != (): raise ValueError("`val` must be a scalar") - # asarray moves scalar to the correct device - # and casts to the expected dtype a_val = dpt.asarray( val, dtype=arr.dtype, @@ -78,8 +76,8 @@ def dpnp_fill(arr, val): dep_evs = _manager.submitted_events # can leverage efficient memset when val is 0 if arr.flags["FORC"] and val == 0: - h_ev, zeros_ev = _zeros_usm_ndarray(arr, exec_q) + h_ev, zeros_ev = _zeros_usm_ndarray(arr, exec_q, depends=dep_evs) _manager.add_event_pair(h_ev, zeros_ev) else: - h_ev, fill_ev = _full_usm_ndarray(val, arr, exec_q) + h_ev, fill_ev = _full_usm_ndarray(val, arr, exec_q, depends=dep_evs) _manager.add_event_pair(h_ev, fill_ev) From 2a826b23bb41d8948af6e7029eb5e946ff1dd05d Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 18 Sep 2024 09:04:33 -0700 Subject: [PATCH 06/23] Remove redundant validation of first `dpnp_fill` argument --- dpnp/dpnp_algo/dpnp_fill.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index 1c7b35020175..cd5c6d60506e 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -38,7 +38,6 @@ def dpnp_fill(arr, val): - dpnp.check_supported_arrays_type(arr) arr = dpnp.get_usm_ndarray(arr) exec_q = arr.sycl_queue From 6e2193b3054e7ef97769c8fb1b30523be94295d4 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 18 Sep 2024 09:53:19 -0700 Subject: [PATCH 07/23] Improve `dpnp_fill` array/scalar path logic --- dpnp/dpnp_algo/dpnp_fill.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index cd5c6d60506e..c7f65fb3e10f 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -34,7 +34,6 @@ from dpctl.utils import SequentialOrderManager import dpnp -from dpnp.dpnp_array import dpnp_array def dpnp_fill(arr, val): @@ -43,7 +42,7 @@ def dpnp_fill(arr, val): dpnp.check_supported_arrays_type(val, scalar_type=True, all_scalars=True) # if val is an array, process it - if isinstance(val, (dpnp_array, dpt.usm_ndarray)): + if dpnp.is_supported_array_type(val): val = dpnp.get_usm_ndarray(val) if val.shape != (): raise ValueError("`val` must be a scalar") @@ -61,6 +60,10 @@ def dpnp_fill(arr, val): ) _manager.add_event_pair(h_ev, c_ev) return + elif not dpnp.isscalar(val): + raise TypeError( + f"Expected `val` to be an array or Python scalar, got {type(val)}" + ) dt = arr.dtype val_type = type(val) From 08b6c26348478abae1e302bf9a43e27f8450b569 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 18 Sep 2024 11:34:47 -0700 Subject: [PATCH 08/23] Disallow inputs to `dpnp_fill` on separate queues --- dpnp/dpnp_algo/dpnp_fill.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index c7f65fb3e10f..cbf3de55c83b 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -25,13 +25,13 @@ # ***************************************************************************** import dpctl.tensor as dpt +import dpctl.utils as dpu import numpy as np from dpctl.tensor._tensor_impl import ( _copy_usm_ndarray_into_usm_ndarray, _full_usm_ndarray, _zeros_usm_ndarray, ) -from dpctl.utils import SequentialOrderManager import dpnp @@ -46,6 +46,10 @@ def dpnp_fill(arr, val): val = dpnp.get_usm_ndarray(val) if val.shape != (): raise ValueError("`val` must be a scalar") + if dpu.get_execution_queue((exec_q, val.sycl_queue)) is None: + raise dpu.ExecutionPlacementError( + "Input arrays have incompatible queues." + ) a_val = dpt.asarray( val, dtype=arr.dtype, @@ -53,7 +57,7 @@ def dpnp_fill(arr, val): sycl_queue=exec_q, ) a_val = dpt.broadcast_to(a_val, arr.shape) - _manager = SequentialOrderManager[exec_q] + _manager = dpu.SequentialOrderManager[exec_q] dep_evs = _manager.submitted_events h_ev, c_ev = _copy_usm_ndarray_into_usm_ndarray( src=a_val, dst=arr, sycl_queue=exec_q, depends=dep_evs @@ -74,7 +78,7 @@ def dpnp_fill(arr, val): elif val_type is int and dpnp.issubdtype(dt, dpnp.integer): val = np.asarray(val, dtype=dt)[()] - _manager = SequentialOrderManager[exec_q] + _manager = dpu.SequentialOrderManager[exec_q] dep_evs = _manager.submitted_events # can leverage efficient memset when val is 0 if arr.flags["FORC"] and val == 0: From c64dc0b84ab045b0e54f72bd7872d016a891cba0 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Mon, 14 Oct 2024 11:00:25 -0700 Subject: [PATCH 09/23] Adjust skip message for `test_fill_with_numpy_scalar_ndarray` --- .../third_party/cupy/core_tests/test_ndarray_copy_and_view.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py b/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py index b53eeee09b76..0a78fd9ecbcd 100644 --- a/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py +++ b/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py @@ -275,8 +275,7 @@ def test_fill(self, xp, dtype): return a @pytest.mark.skip( - "asynchronous fill does not allow numpy scalar array for consistency" - "with dpnp.fill_diagonal" + "Numpy allows Numpy scalar arrays as fill value" ) def test_fill_with_numpy_scalar_ndarray(self, xp, dtype1, dtype2): a = testing.shaped_arange((2, 3, 4), xp, dtype1) From 9691cf0e4a324725e9617c36d2b247ec24ac1cdb Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Mon, 14 Oct 2024 11:41:47 -0700 Subject: [PATCH 10/23] Tweak error messages in `dpnp_fill` --- dpnp/dpnp_algo/dpnp_fill.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index cbf3de55c83b..8eb59c7150ef 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -45,7 +45,7 @@ def dpnp_fill(arr, val): if dpnp.is_supported_array_type(val): val = dpnp.get_usm_ndarray(val) if val.shape != (): - raise ValueError("`val` must be a scalar") + raise ValueError("`val` must be a scalar or 0D-array") if dpu.get_execution_queue((exec_q, val.sycl_queue)) is None: raise dpu.ExecutionPlacementError( "Input arrays have incompatible queues." @@ -66,7 +66,7 @@ def dpnp_fill(arr, val): return elif not dpnp.isscalar(val): raise TypeError( - f"Expected `val` to be an array or Python scalar, got {type(val)}" + f"Expected `val` to be a 0D-array or Python scalar, got {type(val)}" ) dt = arr.dtype From 957b93a094449168962a08819aeb8da00da7baab Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Mon, 14 Oct 2024 11:42:16 -0700 Subject: [PATCH 11/23] Add tests for new `fill` method --- tests/test_fill.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/test_fill.py diff --git a/tests/test_fill.py b/tests/test_fill.py new file mode 100644 index 000000000000..fc78b7635fd7 --- /dev/null +++ b/tests/test_fill.py @@ -0,0 +1,61 @@ +import dpctl +import numpy as np +import pytest +from dpctl.utils import ExecutionPlacementError +from numpy.testing import assert_array_equal + +import dpnp as dnp + + +def test_fill_non_scalar(): + a = dnp.ones(5, dtype="i4") + val = dnp.ones(2, dtype="i4") + + with pytest.raises(ValueError): + a.fill(val) + + with pytest.raises(TypeError): + a.fill(dict()) + + +def test_fill_compute_follows_data(): + q1 = dpctl.SyclQueue() + q2 = dpctl.SyclQueue() + + a = dnp.ones(5, dtype="i4", sycl_queue=q1) + val = dnp.ones((), dtype=a.dtype, sycl_queue=q2) + + with pytest.raises(ExecutionPlacementError): + a.fill(val) + + +def test_fill_strided_array(): + a = dnp.zeros(100, dtype="i4") + b = a[::-2] + + expected = dnp.tile(dnp.asarray([0, 1], dtype=a.dtype), 50) + + b.fill(1) + assert_array_equal(b, 1) + assert_array_equal(a, expected) + + +@pytest.mark.parametrize("order", ["C", "F"]) +def test_fill_strided_2d_array(order): + a = dnp.zeros((10, 10), dtype="i4", order=order) + b = a[::-2, ::2] + + expected = dnp.copy(a) + expected[::-2, ::2] = 1 + + b.fill(1) + assert_array_equal(b, 1) + assert_array_equal(a, expected) + + +@pytest.mark.parametrize("order", ["C", "F"]) +def test_fill_memset(order): + a = dnp.ones((10, 10), dtype="i4", order=order) + a.fill(0) + + assert_array_equal(a, 0) From 87e6560e5ef61b8d05e0d7422fc79942bebb8507 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Mon, 14 Oct 2024 11:50:30 -0700 Subject: [PATCH 12/23] Update docstring for `fill` method --- dpnp/dpnp_array.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 77f6cd726c6a..4d480daa6145 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -885,13 +885,16 @@ def fill(self, value): """ Fill the array with a scalar value. + For full documentation refer to :obj:`numpy.ndarray.fill`. + Parameters ---------- - value : scalar + value : {dpnp.ndarray, usm_ndarray, scalar} All elements of `a` will be assigned this value. Examples -------- + >>> import dpnp as np >>> a = np.array([1, 2]) >>> a.fill(0) >>> a From 985b4fa7572634d899d067e7b43ddbddd7e8bcc7 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Mon, 14 Oct 2024 11:51:16 -0700 Subject: [PATCH 13/23] Fix pre-commit in cupy fill tests --- .../third_party/cupy/core_tests/test_ndarray_copy_and_view.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py b/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py index 0a78fd9ecbcd..e9b61e7ef94c 100644 --- a/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py +++ b/tests/third_party/cupy/core_tests/test_ndarray_copy_and_view.py @@ -274,9 +274,7 @@ def test_fill(self, xp, dtype): a.fill(1) return a - @pytest.mark.skip( - "Numpy allows Numpy scalar arrays as fill value" - ) + @pytest.mark.skip("Numpy allows Numpy scalar arrays as fill value") def test_fill_with_numpy_scalar_ndarray(self, xp, dtype1, dtype2): a = testing.shaped_arange((2, 3, 4), xp, dtype1) a.fill(numpy.ones((), dtype=dtype2)) From d8a3e65b64efe7ac74b0a960733d4507093d29ba Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 22 Oct 2024 10:56:10 -0700 Subject: [PATCH 14/23] Change `asarray` to `astype` in `dpnp_fill` NumPy arrays are no longer permitted and queue coercion does not occur in the `fill` method, so `astype` is sufficient --- dpnp/dpnp_algo/dpnp_fill.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index 8eb59c7150ef..7c1a5066a77b 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -50,12 +50,7 @@ def dpnp_fill(arr, val): raise dpu.ExecutionPlacementError( "Input arrays have incompatible queues." ) - a_val = dpt.asarray( - val, - dtype=arr.dtype, - usm_type=arr.usm_type, - sycl_queue=exec_q, - ) + a_val = dpt.astype(val, arr.dtype) a_val = dpt.broadcast_to(a_val, arr.shape) _manager = dpu.SequentialOrderManager[exec_q] dep_evs = _manager.submitted_events From 70618e1f9c7c2659cacc9ae116d575aaeb0921f2 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 22 Oct 2024 10:56:14 -0700 Subject: [PATCH 15/23] Expand TEST_SCOPE to include `test_fill.py` --- .github/workflows/conda-package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/conda-package.yml b/.github/workflows/conda-package.yml index 16c3352fa8c9..17fad41ac2b9 100644 --- a/.github/workflows/conda-package.yml +++ b/.github/workflows/conda-package.yml @@ -29,6 +29,7 @@ env: test_copy.py test_counting.py test_fft.py + test_fill.py test_flat.py test_histogram.py test_indexing.py From 478397fc5ac180b61f61dfc8e9e7899c53496873 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 22 Oct 2024 10:57:41 -0700 Subject: [PATCH 16/23] Remove redundant check from `dpnp_fill` --- dpnp/dpnp_algo/dpnp_fill.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index 7c1a5066a77b..1d1c6c56cb8e 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -40,7 +40,6 @@ def dpnp_fill(arr, val): arr = dpnp.get_usm_ndarray(arr) exec_q = arr.sycl_queue - dpnp.check_supported_arrays_type(val, scalar_type=True, all_scalars=True) # if val is an array, process it if dpnp.is_supported_array_type(val): val = dpnp.get_usm_ndarray(val) From f782d1c10b6fef94bc14518dae6eb15021092e45 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 22 Oct 2024 11:03:59 -0700 Subject: [PATCH 17/23] Use `_cast_fill_val` private function from `dpctl.tensor._ctors` --- dpnp/dpnp_algo/dpnp_fill.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index 1d1c6c56cb8e..af0e8383f898 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -26,7 +26,7 @@ import dpctl.tensor as dpt import dpctl.utils as dpu -import numpy as np +from dpctl.tensor._ctors import _cast_fill_val from dpctl.tensor._tensor_impl import ( _copy_usm_ndarray_into_usm_ndarray, _full_usm_ndarray, @@ -70,7 +70,7 @@ def dpnp_fill(arr, val): elif val_type is complex and dpnp.issubdtype(dt, dpnp.floating): val = val.real elif val_type is int and dpnp.issubdtype(dt, dpnp.integer): - val = np.asarray(val, dtype=dt)[()] + val = _cast_fill_val(val, dt) _manager = dpu.SequentialOrderManager[exec_q] dep_evs = _manager.submitted_events From e29685e69882d2dfcaf1a50f77730ff6506ab03d Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 22 Oct 2024 11:29:44 -0700 Subject: [PATCH 18/23] Add tests per PR review by @antonwolfy --- tests/test_fill.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_fill.py b/tests/test_fill.py index fc78b7635fd7..c9678397a612 100644 --- a/tests/test_fill.py +++ b/tests/test_fill.py @@ -59,3 +59,20 @@ def test_fill_memset(order): a.fill(0) assert_array_equal(a, 0) + + +def test_fill_float_complex_to_int(): + a = dnp.ones((10, 10), dtype="i4") + + a.fill(complex(2, 0)) + assert_array_equal(a, 2) + + a.fill(float(3)) + assert_array_equal(a, 3) + + +def test_fill_complex_to_float(): + a = dnp.ones((10, 10), dtype="f4") + + a.fill(complex(2, 0)) + assert_array_equal(a, 2) From bd30f855e267b906705cdabe0e64ad894fc29ace Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 23 Oct 2024 12:07:53 -0700 Subject: [PATCH 19/23] Improve validation of `val` for `fill` method --- dpnp/dpnp_algo/dpnp_fill.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index af0e8383f898..00ff048cdc5b 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -24,6 +24,8 @@ # THE POSSIBILITY OF SUCH DAMAGE. # ***************************************************************************** +from numbers import Number + import dpctl.tensor as dpt import dpctl.utils as dpu from dpctl.tensor._ctors import _cast_fill_val @@ -58,9 +60,9 @@ def dpnp_fill(arr, val): ) _manager.add_event_pair(h_ev, c_ev) return - elif not dpnp.isscalar(val): + elif not isinstance(val, Number): raise TypeError( - f"Expected `val` to be a 0D-array or Python scalar, got {type(val)}" + f"Array cannot be filled with `val` of type {type(val)}" ) dt = arr.dtype From 865867a7770881b372114572412061792d247e3c Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 23 Oct 2024 22:09:03 -0700 Subject: [PATCH 20/23] Add to permit NumPy bools as `dpnp_fill` scalar fill values --- dpnp/dpnp_algo/dpnp_fill.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index 00ff048cdc5b..158703d0631c 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -28,6 +28,7 @@ import dpctl.tensor as dpt import dpctl.utils as dpu +import numpy as np from dpctl.tensor._ctors import _cast_fill_val from dpctl.tensor._tensor_impl import ( _copy_usm_ndarray_into_usm_ndarray, @@ -60,9 +61,9 @@ def dpnp_fill(arr, val): ) _manager.add_event_pair(h_ev, c_ev) return - elif not isinstance(val, Number): + elif not isinstance(val, Number) and not isinstance(val, np.bool_): raise TypeError( - f"Array cannot be filled with `val` of type {type(val)}" + f"array cannot be filled with `val` of type {type(val)}" ) dt = arr.dtype From 4ba7186f2d5781bfdd97e25147b92ad85f13bb00 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Thu, 24 Oct 2024 08:32:45 -0700 Subject: [PATCH 21/23] Use `dpnp.bool` in `dpnp_fill` and make `isinstance` check more efficient --- dpnp/dpnp_algo/dpnp_fill.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index 158703d0631c..858f0ce806dc 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -28,7 +28,6 @@ import dpctl.tensor as dpt import dpctl.utils as dpu -import numpy as np from dpctl.tensor._ctors import _cast_fill_val from dpctl.tensor._tensor_impl import ( _copy_usm_ndarray_into_usm_ndarray, @@ -61,7 +60,7 @@ def dpnp_fill(arr, val): ) _manager.add_event_pair(h_ev, c_ev) return - elif not isinstance(val, Number) and not isinstance(val, np.bool_): + elif not isinstance(val, (Number, dpnp.bool)): raise TypeError( f"array cannot be filled with `val` of type {type(val)}" ) From 48c80514858e5eddf8e1243fc83ce03538f4847a Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Thu, 24 Oct 2024 08:35:42 -0700 Subject: [PATCH 22/23] Replace branching for `fill` scalar type with `_cast_fill_value` --- dpnp/dpnp_algo/dpnp_fill.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py index 858f0ce806dc..43fdfa8ca704 100644 --- a/dpnp/dpnp_algo/dpnp_fill.py +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -64,18 +64,11 @@ def dpnp_fill(arr, val): raise TypeError( f"array cannot be filled with `val` of type {type(val)}" ) - - dt = arr.dtype - val_type = type(val) - if val_type in [float, complex] and dpnp.issubdtype(dt, dpnp.integer): - val = int(val.real) - elif val_type is complex and dpnp.issubdtype(dt, dpnp.floating): - val = val.real - elif val_type is int and dpnp.issubdtype(dt, dpnp.integer): - val = _cast_fill_val(val, dt) + val = _cast_fill_val(val, arr.dtype) _manager = dpu.SequentialOrderManager[exec_q] dep_evs = _manager.submitted_events + # can leverage efficient memset when val is 0 if arr.flags["FORC"] and val == 0: h_ev, zeros_ev = _zeros_usm_ndarray(arr, exec_q, depends=dep_evs) From 8adc06ad4d71761f467f5e4ec92e49e338b53466 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Thu, 24 Oct 2024 08:39:36 -0700 Subject: [PATCH 23/23] Add additional tests for `fill` `test_fill_non_scalar` now checks for strings and `test_fill_bool` added to verify bools are properly cast to 1 --- tests/test_fill.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/test_fill.py b/tests/test_fill.py index c9678397a612..cc984f752428 100644 --- a/tests/test_fill.py +++ b/tests/test_fill.py @@ -7,16 +7,19 @@ import dpnp as dnp -def test_fill_non_scalar(): +@pytest.mark.parametrize( + "val, error", + [ + pytest.param(dnp.ones(2, dtype="i4"), ValueError, id="array"), + pytest.param(dict(), TypeError, id="dictionary"), + pytest.param("0", TypeError, id="string"), + ], +) +def test_fill_non_scalar(val, error): a = dnp.ones(5, dtype="i4") - val = dnp.ones(2, dtype="i4") - - with pytest.raises(ValueError): + with pytest.raises(error): a.fill(val) - with pytest.raises(TypeError): - a.fill(dict()) - def test_fill_compute_follows_data(): q1 = dpctl.SyclQueue() @@ -76,3 +79,9 @@ def test_fill_complex_to_float(): a.fill(complex(2, 0)) assert_array_equal(a, 2) + + +def test_fill_bool(): + a = dnp.full(5, fill_value=7, dtype="i4") + a.fill(True) + assert_array_equal(a, 1)