Skip to content

Commit c5cd356

Browse files
Fix dpnp.insert ignoring out-of-bounds negative indices in multi-element obj (#3041)
`dpnp.insert` has two index paths: the singleton path (`_insert_singleton_index`) already validated bounds, but the multi-element array path (`_insert_array_indices`) normalized negative indices with `indices[indices < 0] += n` without any bounds check. As a result, an out-of-bounds negative index mixed with in-bounds ones — e.g. `dpnp.insert([0, 1, 2], [-6, 0], [9, 8])` — silently produced a wrong result instead of raising. This PR adds the same bounds validation NumPy introduced: if any index is `< -n` or `> n`, `IndexError` is raised with the standard `index {i} is out of bounds for axis {axis} with size {n}` message. In-bounds and out-of-bounds indices mixed in a single call now consistently raise. 1d7c6fa
1 parent 9bcc638 commit c5cd356

4 files changed

Lines changed: 51 additions & 2 deletions

File tree

‎_modules/dpnp/dpnp_iface_manipulation.html‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,39 @@ <h1>Source code for dpnp.dpnp_iface_manipulation</h1><div class="highlight"><pre
12871287
<span class="p">)</span>
12881288

12891289

1290+
<span class="k">def</span><span class="w"> </span><span class="nf">_check_index_bounds</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">indices</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">axis</span><span class="p">):</span>
1291+
<span class="w"> </span><span class="sd">"""</span>
1292+
<span class="sd"> Raise ``IndexError`` if any index in `obj` is out of bounds for `axis`.</span>
1293+
1294+
<span class="sd"> Mirrors the size-1 path in ``_insert_singleton_index``: when `obj` lives on</span>
1295+
<span class="sd"> the host (a Python sequence/scalar or NumPy array) the bounds are validated</span>
1296+
<span class="sd"> with NumPy, avoiding a device sync entirely. A slice cannot be out of bounds</span>
1297+
<span class="sd"> (``obj.indices(n)`` is clamped to ``[0, n]``), so it is skipped. Only a</span>
1298+
<span class="sd"> device array `obj` needs a single host transfer to read its extremes.</span>
1299+
1300+
<span class="sd"> """</span>
1301+
1302+
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="nb">slice</span><span class="p">)</span> <span class="ow">or</span> <span class="n">indices</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
1303+
<span class="k">return</span>
1304+
1305+
<span class="k">if</span> <span class="n">dpnp</span><span class="o">.</span><span class="n">is_supported_array_type</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span>
1306+
<span class="n">min_idx</span><span class="p">,</span> <span class="n">max_idx</span> <span class="o">=</span> <span class="n">dpnp</span><span class="o">.</span><span class="n">stack</span><span class="p">([</span><span class="n">indices</span><span class="o">.</span><span class="n">min</span><span class="p">(),</span> <span class="n">indices</span><span class="o">.</span><span class="n">max</span><span class="p">()])</span><span class="o">.</span><span class="n">asnumpy</span><span class="p">()</span>
1307+
<span class="k">else</span><span class="p">:</span>
1308+
<span class="n">host_obj</span> <span class="o">=</span> <span class="n">numpy</span><span class="o">.</span><span class="n">asarray</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
1309+
<span class="k">if</span> <span class="n">host_obj</span><span class="o">.</span><span class="n">dtype</span> <span class="o">==</span> <span class="n">dpnp</span><span class="o">.</span><span class="n">bool</span><span class="p">:</span>
1310+
<span class="c1"># a boolean mask selects positions, which (for an oversized mask)</span>
1311+
<span class="c1"># can fall out of bounds, so validate the flatnonzero result</span>
1312+
<span class="n">host_obj</span> <span class="o">=</span> <span class="n">numpy</span><span class="o">.</span><span class="n">flatnonzero</span><span class="p">(</span><span class="n">host_obj</span><span class="p">)</span>
1313+
<span class="n">min_idx</span><span class="p">,</span> <span class="n">max_idx</span> <span class="o">=</span> <span class="n">host_obj</span><span class="o">.</span><span class="n">min</span><span class="p">(),</span> <span class="n">host_obj</span><span class="o">.</span><span class="n">max</span><span class="p">()</span>
1314+
1315+
<span class="n">min_idx</span><span class="p">,</span> <span class="n">max_idx</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">min_idx</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">max_idx</span><span class="p">)</span>
1316+
<span class="k">if</span> <span class="n">min_idx</span> <span class="o">&lt;</span> <span class="o">-</span><span class="n">n</span> <span class="ow">or</span> <span class="n">max_idx</span> <span class="o">&gt;</span> <span class="n">n</span><span class="p">:</span>
1317+
<span class="n">oob</span> <span class="o">=</span> <span class="n">min_idx</span> <span class="k">if</span> <span class="n">min_idx</span> <span class="o">&lt;</span> <span class="o">-</span><span class="n">n</span> <span class="k">else</span> <span class="n">max_idx</span>
1318+
<span class="k">raise</span> <span class="ne">IndexError</span><span class="p">(</span>
1319+
<span class="sa">f</span><span class="s2">"index </span><span class="si">{</span><span class="n">oob</span><span class="si">}</span><span class="s2"> is out of bounds for axis </span><span class="si">{</span><span class="n">axis</span><span class="si">}</span><span class="s2"> with size </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s2">"</span>
1320+
<span class="p">)</span>
1321+
1322+
12901323
<span class="k">def</span><span class="w"> </span><span class="nf">_insert_array_indices</span><span class="p">(</span><span class="n">parameters</span><span class="p">,</span> <span class="n">indices</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="n">obj</span><span class="p">):</span>
12911324
<span class="w"> </span><span class="sd">"""</span>
12921325
<span class="sd"> Utility function for ``dpnp.insert`` when indices is an array with</span>
@@ -3556,6 +3589,13 @@ <h1>Source code for dpnp.dpnp_iface_manipulation</h1><div class="highlight"><pre
35563589
<span class="sd"> does not occur in-place: a new array is returned. If</span>
35573590
<span class="sd"> `axis` is ``None``, `out` is a flattened array.</span>
35583591

3592+
<span class="sd"> Warnings</span>
3593+
<span class="sd"> --------</span>
3594+
<span class="sd"> This function might synchronize in order to validate that the indices are</span>
3595+
<span class="sd"> within bounds. This may harm performance in some applications. To avoid</span>
3596+
<span class="sd"> synchronization, pass `obj` as a Python scalar or sequence, or as a NumPy</span>
3597+
<span class="sd"> array.</span>
3598+
35593599
<span class="sd"> See Also</span>
35603600
<span class="sd"> --------</span>
35613601
<span class="sd"> :obj:`dpnp.append` : Append elements at the end of an array.</span>
@@ -3643,8 +3683,10 @@ <h1>Source code for dpnp.dpnp_iface_manipulation</h1><div class="highlight"><pre
36433683
<span class="p">)</span>
36443684

36453685
<span class="k">if</span> <span class="n">indices</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
3686+
<span class="c1"># the size-1 path validates the bounds itself while reading the index</span>
36463687
<span class="k">return</span> <span class="n">_insert_singleton_index</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">indices</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="n">obj</span><span class="p">)</span>
36473688

3689+
<span class="n">_check_index_bounds</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">indices</span><span class="p">,</span> <span class="n">params</span><span class="o">.</span><span class="n">n</span><span class="p">,</span> <span class="n">params</span><span class="o">.</span><span class="n">axis</span><span class="p">)</span>
36483690
<span class="k">return</span> <span class="n">_insert_array_indices</span><span class="p">(</span><span class="n">params</span><span class="p">,</span> <span class="n">indices</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="n">obj</span><span class="p">)</span></div>
36493691

36503692

‎reference/comparison.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1055,7 +1055,7 @@
10551055
<h1>Comparison Table NumPy/ DPNP/ CuPy<a class="headerlink" href="#comparison-table-numpy-dpnp-cupy" title="Link to this heading">¶</a></h1>
10561056
<p>Here is a list of NumPy and CuPy APIs and its corresponding DPNP implementations.</p>
10571057
<p><code class="docutils literal notranslate"><span class="pre">-</span></code> in DPNP column means that DPNP implementation is not provided yet.</p>
1058-
<p><strong>NumPy(v2.5.2) / DPNP(v0.21.0dev7+6.gc578ddcbbf8) / CuPy(v14.2.0) APIs</strong></p>
1058+
<p><strong>NumPy(v2.5.2) / DPNP(v0.21.0dev7+7.g1d7c6fa4e43) / CuPy(v14.2.0) APIs</strong></p>
10591059
<section id="summary">
10601060
<h2>Summary<a class="headerlink" href="#summary" title="Link to this heading">¶</a></h2>
10611061
<div class="table-wrapper docutils container">

‎reference/generated/dpnp.insert.html‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,13 @@ <h1>dpnp.insert<a class="headerlink" href="#dpnp-insert" title="Link to this hea
10861086
<dd class="field-odd"><p><a class="reference internal" href="dpnp.ndarray.html#dpnp.ndarray" title="dpnp.ndarray">dpnp.ndarray</a></p>
10871087
</dd>
10881088
</dl>
1089+
<div class="admonition warning">
1090+
<p class="admonition-title">Warning</p>
1091+
<p>This function might synchronize in order to validate that the indices are
1092+
within bounds. This may harm performance in some applications. To avoid
1093+
synchronization, pass <cite>obj</cite> as a Python scalar or sequence, or as a NumPy
1094+
array.</p>
1095+
</div>
10891096
<div class="admonition seealso">
10901097
<p class="admonition-title">See also</p>
10911098
<dl class="simple">

‎searchindex.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)