diff --git a/.github/workflows/conda-package.yml b/.github/workflows/conda-package.yml index adef827c41d0..bfbfd44b08ef 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 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 diff --git a/dpnp/dpnp_algo/dpnp_fill.py b/dpnp/dpnp_algo/dpnp_fill.py new file mode 100644 index 000000000000..43fdfa8ca704 --- /dev/null +++ b/dpnp/dpnp_algo/dpnp_fill.py @@ -0,0 +1,78 @@ +# -*- 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. +# ***************************************************************************** + +from numbers import Number + +import dpctl.tensor as dpt +import dpctl.utils as dpu +from dpctl.tensor._ctors import _cast_fill_val +from dpctl.tensor._tensor_impl import ( + _copy_usm_ndarray_into_usm_ndarray, + _full_usm_ndarray, + _zeros_usm_ndarray, +) + +import dpnp + + +def dpnp_fill(arr, val): + arr = dpnp.get_usm_ndarray(arr) + exec_q = arr.sycl_queue + + # if val is an array, process it + if dpnp.is_supported_array_type(val): + val = dpnp.get_usm_ndarray(val) + if val.shape != (): + 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." + ) + 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 + 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 + elif not isinstance(val, (Number, dpnp.bool)): + raise TypeError( + f"array cannot be filled with `val` of type {type(val)}" + ) + 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) + _manager.add_event_pair(h_ev, zeros_ev) + else: + h_ev, fill_ev = _full_usm_ndarray(val, arr, exec_q, depends=dep_evs) + _manager.add_event_pair(h_ev, fill_ev) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 1c05d7807fa0..1dde60771dc9 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -928,13 +928,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 @@ -946,8 +949,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): diff --git a/tests/test_fill.py b/tests/test_fill.py new file mode 100644 index 000000000000..cc984f752428 --- /dev/null +++ b/tests/test_fill.py @@ -0,0 +1,87 @@ +import dpctl +import numpy as np +import pytest +from dpctl.utils import ExecutionPlacementError +from numpy.testing import assert_array_equal + +import dpnp as dnp + + +@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") + with pytest.raises(error): + a.fill(val) + + +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) + + +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) + + +def test_fill_bool(): + a = dnp.full(5, fill_value=7, dtype="i4") + a.fill(True) + assert_array_equal(a, 1) 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..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 - @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("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))