From 8d0149e4ad2b629df8e869ab103eed5a2ec91d5b Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Fri, 24 Apr 2026 05:17:26 -0700 Subject: [PATCH 1/5] Remove __bool__ fallback from _is_boolean in _slicing.pxi --- dpnp/tensor/_slicing.pxi | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dpnp/tensor/_slicing.pxi b/dpnp/tensor/_slicing.pxi index f387aef8afd..2f22894c4b1 100644 --- a/dpnp/tensor/_slicing.pxi +++ b/dpnp/tensor/_slicing.pxi @@ -104,12 +104,6 @@ cdef bint _is_boolean(object x) except *: return f in "?" else: return False - if callable(getattr(x, "__bool__", None)): - try: - x.__bool__() - except (TypeError, ValueError): - return False - return True return False From 672a45c7230b39085183fb2d372d835e642b4404 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Fri, 24 Apr 2026 05:18:53 -0700 Subject: [PATCH 2/5] Support range/list as advanced index keys in dpnp_array --- dpnp/dpnp_array.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 00a1b2d00e5..1e680ac706c 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -38,6 +38,8 @@ import warnings +import numpy + import dpnp import dpnp.tensor as dpt import dpnp.tensor._type_utils as dtu @@ -46,23 +48,39 @@ from .exceptions import AxisError +def _unwrap_index_element(x): + """ + Unwrap a single index element for the tensor indexing layer. + + Converts dpnp arrays to usm_ndarray and array-like objects (range, list) + to numpy arrays with intp dtype for NumPy-compatible advanced indexing. + + """ + + if isinstance(x, dpnp_array): + return x.get_array() + if isinstance(x, (range, list)): + return numpy.asarray(x, dtype=numpy.intp) + return x + + def _get_unwrapped_index_key(key): """ Get an unwrapped index key. Return a key where each nested instance of DPNP array is unwrapped into - USM ndarray for further processing in DPCTL advanced indexing functions. + USM ndarray, and array-like objects (range, list) are converted to numpy + arrays for further processing in advanced indexing functions. """ if isinstance(key, tuple): - if any(isinstance(x, dpnp_array) for x in key): - # create a new tuple from the input key with unwrapped DPNP arrays - return tuple( - x.get_array() if isinstance(x, dpnp_array) else x for x in key - ) + if any(isinstance(x, (dpnp_array, range, list)) for x in key): + return tuple(_unwrap_index_element(x) for x in key) elif isinstance(key, dpnp_array): return key.get_array() + elif isinstance(key, (range, list)): + return numpy.asarray(key, dtype=numpy.intp) return key From 7ef44b727ac22d22636b14842b0a3b76c3d1a458 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Fri, 24 Apr 2026 05:21:58 -0700 Subject: [PATCH 3/5] Add tests for range/list advanced indexing --- dpnp/tests/test_indexing.py | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/dpnp/tests/test_indexing.py b/dpnp/tests/test_indexing.py index bfdcf0ed30a..2edc8214f3e 100644 --- a/dpnp/tests/test_indexing.py +++ b/dpnp/tests/test_indexing.py @@ -353,6 +353,59 @@ def test_indexing_array_negative_strides(self): arr[slices] = 10 assert_equal(arr, 10.0, strict=False) + @pytest.mark.parametrize( + "idx", + [ + (range(2), range(2)), + ([0, 1], [0, 1]), + ], + ids=["range", "list"], + ) + def test_array_like_index_getitem(self, idx): + np_a = numpy.arange(36).reshape(2, 2, 3, 3) + dp_a = dpnp.arange(36).reshape(2, 2, 3, 3) + assert_array_equal(dp_a[idx], np_a[idx]) + + @pytest.mark.parametrize( + "idx", + [ + (range(2), range(2)), + ([0, 1], [0, 1]), + ], + ids=["range", "list"], + ) + def test_array_like_index_setitem(self, idx): + np_a = numpy.arange(36).reshape(2, 2, 3, 3) + dp_a = dpnp.arange(36).reshape(2, 2, 3, 3) + np_a[idx] = 0 + dp_a[idx] = 0 + assert_array_equal(dp_a, np_a) + + def test_array_like_index_inplace_add(self): + np_a = numpy.arange(36).reshape(2, 2, 3, 3) + dp_a = dpnp.arange(36).reshape(2, 2, 3, 3) + np_tmp = -numpy.ones((2, 3, 3), dtype=numpy.intp) + dp_tmp = -dpnp.ones((2, 3, 3), dtype=numpy.intp) + + np_a[range(2), range(2)] += 2 * np_tmp + dp_a[range(2), range(2)] += 2 * dp_tmp + assert_array_equal(dp_a, np_a) + + @pytest.mark.parametrize( + "idx", + [ + range(2), + [0, 1], + range(0), + [], + ], + ids=["range", "list", "empty_range", "empty_list"], + ) + def test_array_like_single_index(self, idx): + np_a = numpy.arange(24).reshape(2, 3, 4) + dp_a = dpnp.arange(24).reshape(2, 3, 4) + assert_array_equal(dp_a[idx], np_a[idx]) + class TestIx: @pytest.mark.parametrize( From 5f5b0b173d5701f66fcdc33a72ee989ae6f688c5 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Fri, 24 Apr 2026 07:21:04 -0700 Subject: [PATCH 4/5] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6239fd9c1f..81b7578044f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* Fixed incorrect in-place advanced indexing for 4D arrays when using `range` or `list` as index keys [#2872](https://github.com/IntelPython/dpnp/pull/2872) + ### Security From 830ef88b5bc8f02e1b1f08eea046d291f3a139d0 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Fri, 24 Apr 2026 10:13:02 -0700 Subject: [PATCH 5/5] Handle list/empty list advanced indexing correctly --- dpnp/dpnp_array.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 1e680ac706c..7b4391c6f82 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -59,8 +59,16 @@ def _unwrap_index_element(x): if isinstance(x, dpnp_array): return x.get_array() - if isinstance(x, (range, list)): + if isinstance(x, range): return numpy.asarray(x, dtype=numpy.intp) + if isinstance(x, list): + # keep boolean lists as boolean + arr = numpy.asarray(x) + # cast empty lists (float64 in NumPy) to intp + # for correct tensor indexing + if arr.size == 0: + arr = arr.astype(numpy.intp) + return arr return x @@ -77,10 +85,8 @@ def _get_unwrapped_index_key(key): if isinstance(key, tuple): if any(isinstance(x, (dpnp_array, range, list)) for x in key): return tuple(_unwrap_index_element(x) for x in key) - elif isinstance(key, dpnp_array): - return key.get_array() - elif isinstance(key, (range, list)): - return numpy.asarray(key, dtype=numpy.intp) + elif isinstance(key, (dpnp_array, range, list)): + return _unwrap_index_element(key) return key