Skip to content

Commit 0517413

Browse files
authored
implement dpnp.delete (#2145)
* implement dpnp.delete * fix usm_type test * Add more tests * fix issue for no fp64 support * address comments * add new tests * avoid additionacopy to host when obj is NumPy, list, etc
1 parent cb801da commit 0517413

File tree

5 files changed

+441
-3
lines changed

5 files changed

+441
-3
lines changed

dpnp/dpnp_iface_manipulation.py

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
import dpnp
5050

5151
from .dpnp_array import dpnp_array
52+
53+
# pylint: disable=no-name-in-module
54+
from .dpnp_utils import get_usm_allocations
5255
from .dpnp_utils.dpnp_utils_pad import dpnp_pad
5356

5457
__all__ = [
@@ -66,6 +69,7 @@
6669
"concat",
6770
"concatenate",
6871
"copyto",
72+
"delete",
6973
"dsplit",
7074
"dstack",
7175
"expand_dims",
@@ -115,6 +119,135 @@ def _check_stack_arrays(arrays):
115119
)
116120

117121

122+
def _delete_with_slice(a, obj, axis):
123+
"""Utility function for ``dpnp.delete`` when obj is slice."""
124+
125+
a, a_ndim, order, axis, slobj, n, a_shape = _calc_parameters(a, axis)
126+
start, stop, step = obj.indices(n)
127+
xr = range(start, stop, step)
128+
num_del = len(xr)
129+
130+
if num_del <= 0:
131+
return a.copy(order=order)
132+
133+
# Invert if step is negative:
134+
if step < 0:
135+
step = -step
136+
start = xr[-1]
137+
stop = xr[0] + 1
138+
139+
a_shape[axis] -= num_del
140+
new = dpnp.empty(
141+
a_shape,
142+
dtype=a.dtype,
143+
order=order,
144+
sycl_queue=a.sycl_queue,
145+
usm_type=a.usm_type,
146+
)
147+
# copy initial chunk
148+
if start == 0:
149+
pass
150+
else:
151+
slobj[axis] = slice(None, start)
152+
new[tuple(slobj)] = a[tuple(slobj)]
153+
# copy end chunk
154+
if stop == n:
155+
pass
156+
else:
157+
slobj[axis] = slice(stop - num_del, None)
158+
slobj2 = [slice(None)] * a_ndim
159+
slobj2[axis] = slice(stop, None)
160+
new[tuple(slobj)] = a[tuple(slobj2)]
161+
# copy middle pieces
162+
if step == 1:
163+
pass
164+
else: # use array indexing.
165+
keep = dpnp.ones(
166+
stop - start,
167+
dtype=dpnp.bool,
168+
sycl_queue=a.sycl_queue,
169+
usm_type=a.usm_type,
170+
)
171+
keep[: stop - start : step] = False
172+
slobj[axis] = slice(start, stop - num_del)
173+
slobj2 = [slice(None)] * a_ndim
174+
slobj2[axis] = slice(start, stop)
175+
a = a[tuple(slobj2)]
176+
slobj2[axis] = keep
177+
new[tuple(slobj)] = a[tuple(slobj2)]
178+
179+
return new
180+
181+
182+
def _delete_without_slice(a, obj, axis, single_value, exec_q, usm_type):
183+
"""Utility function for ``dpnp.delete`` when obj is int or array of int."""
184+
185+
a, a_ndim, order, axis, slobj, n, a_shape = _calc_parameters(a, axis)
186+
if single_value:
187+
# optimization for a single value
188+
if obj < -n or obj >= n:
189+
raise IndexError(
190+
f"index {obj} is out of bounds for axis {axis} with "
191+
f"size {n}"
192+
)
193+
if obj < 0:
194+
obj += n
195+
a_shape[axis] -= 1
196+
new = dpnp.empty(
197+
a_shape,
198+
dtype=a.dtype,
199+
order=order,
200+
sycl_queue=exec_q,
201+
usm_type=usm_type,
202+
)
203+
slobj[axis] = slice(None, obj)
204+
new[tuple(slobj)] = a[tuple(slobj)]
205+
slobj[axis] = slice(obj, None)
206+
slobj2 = [slice(None)] * a_ndim
207+
slobj2[axis] = slice(obj + 1, None)
208+
new[tuple(slobj)] = a[tuple(slobj2)]
209+
else:
210+
if obj.dtype == dpnp.bool:
211+
if obj.shape != (n,):
212+
raise ValueError(
213+
"boolean array argument `obj` to delete must be "
214+
f"one-dimensional and match the axis length of {n}"
215+
)
216+
217+
# optimization, the other branch is slower
218+
keep = ~obj
219+
else:
220+
keep = dpnp.ones(
221+
n, dtype=dpnp.bool, sycl_queue=exec_q, usm_type=usm_type
222+
)
223+
keep[obj,] = False
224+
225+
slobj[axis] = keep
226+
new = a[tuple(slobj)]
227+
228+
return new
229+
230+
231+
def _calc_parameters(a, axis):
232+
"""Utility function for ``dpnp.delete`` and ``dpnp.insert``."""
233+
234+
a_ndim = a.ndim
235+
order = "F" if a.flags.fnc else "C"
236+
if axis is None:
237+
if a_ndim != 1:
238+
a = dpnp.ravel(a)
239+
a_ndim = 1
240+
axis = 0
241+
else:
242+
axis = normalize_axis_index(axis, a_ndim)
243+
244+
slobj = [slice(None)] * a_ndim
245+
n = a.shape[axis]
246+
a_shape = list(a.shape)
247+
248+
return a, a_ndim, order, axis, slobj, n, a_shape
249+
250+
118251
def _unique_1d(
119252
ar,
120253
return_index=False,
@@ -1206,6 +1339,109 @@ def copyto(dst, src, casting="same_kind", where=True):
12061339
dst_usm[mask_usm] = src_usm[mask_usm]
12071340

12081341

1342+
def delete(arr, obj, axis=None):
1343+
"""
1344+
Return a new array with sub-arrays along an axis deleted. For a one
1345+
dimensional array, this returns those entries not returned by
1346+
``arr[obj]``.
1347+
1348+
For full documentation refer to :obj:`numpy.delete`.
1349+
1350+
Parameters
1351+
----------
1352+
arr : {dpnp.ndarray, usm_ndarray}
1353+
Input array.
1354+
obj : {slice, int, array-like of ints or boolean}
1355+
Indicate indices of sub-arrays to remove along the specified axis.
1356+
Boolean indices are treated as a mask of elements to remove.
1357+
axis : {None, int}, optional
1358+
The axis along which to delete the subarray defined by `obj`.
1359+
If `axis` is ``None``, `obj` is applied to the flattened array.
1360+
Default: ``None``.
1361+
1362+
Returns
1363+
-------
1364+
out : dpnp.ndarray
1365+
A copy of `arr` with the elements specified by `obj` removed. Note
1366+
that `delete` does not occur in-place. If `axis` is ``None``, `out` is
1367+
a flattened array.
1368+
1369+
See Also
1370+
--------
1371+
:obj:`dpnp.insert` : Insert elements into an array.
1372+
:obj:`dpnp.append` : Append elements at the end of an array.
1373+
1374+
Notes
1375+
-----
1376+
Often it is preferable to use a boolean mask. For example:
1377+
1378+
>>> import dpnp as np
1379+
>>> arr = np.arange(12) + 1
1380+
>>> mask = np.ones(len(arr), dtype=np.bool)
1381+
>>> mask[0] = mask[2] = mask[4] = False
1382+
>>> result = arr[mask,...]
1383+
1384+
is equivalent to ``np.delete(arr, [0, 2, 4], axis=0)``, but allows further
1385+
use of `mask`.
1386+
1387+
Examples
1388+
--------
1389+
>>> import dpnp as np
1390+
>>> arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
1391+
>>> arr
1392+
array([[ 1, 2, 3, 4],
1393+
[ 5, 6, 7, 8],
1394+
[ 9, 10, 11, 12]])
1395+
>>> np.delete(arr, 1, 0)
1396+
array([[ 1, 2, 3, 4],
1397+
[ 9, 10, 11, 12]])
1398+
1399+
>>> np.delete(arr, slice(None, None, 2), 1)
1400+
array([[ 2, 4],
1401+
[ 6, 8],
1402+
[10, 12]])
1403+
>>> np.delete(arr, [1, 3, 5], None)
1404+
array([ 1, 3, 5, 7, 8, 9, 10, 11, 12])
1405+
1406+
"""
1407+
1408+
dpnp.check_supported_arrays_type(arr)
1409+
1410+
if isinstance(obj, slice):
1411+
return _delete_with_slice(arr, obj, axis)
1412+
1413+
if dpnp.is_supported_array_type(obj):
1414+
usm_type, exec_q = get_usm_allocations([arr, obj])
1415+
else:
1416+
usm_type, exec_q = arr.usm_type, arr.sycl_queue
1417+
1418+
if isinstance(obj, (int, dpnp.integer)) and not isinstance(obj, bool):
1419+
single_value = True
1420+
indices = obj
1421+
else:
1422+
single_value = False
1423+
is_array = isinstance(obj, (dpnp_array, numpy.ndarray, dpt.usm_ndarray))
1424+
indices = dpnp.asarray(obj, sycl_queue=exec_q, usm_type=usm_type)
1425+
# if `obj` is originally an empty list, after converting it into
1426+
# an array, it will have float dtype, so we need to change its dtype
1427+
# to integer. However, if `obj` is originally an empty array with
1428+
# float dtype, it is a mistake by user and it will raise an error later
1429+
if indices.size == 0 and not is_array:
1430+
indices = indices.astype(dpnp.intp)
1431+
elif indices.size == 1 and indices.dtype.kind in "ui":
1432+
# For a size 1 integer array we can use the single-value path
1433+
# (most dtypes, except boolean, should just fail later).
1434+
if isinstance(obj, (dpnp_array, dpt.usm_ndarray)):
1435+
indices = indices.item()
1436+
else:
1437+
indices = numpy.asarray(obj).item()
1438+
single_value = True
1439+
1440+
return _delete_without_slice(
1441+
arr, indices, axis, single_value, exec_q, usm_type
1442+
)
1443+
1444+
12091445
def dsplit(ary, indices_or_sections):
12101446
"""
12111447
Split array into multiple sub-arrays along the 3rd axis (depth).

0 commit comments

Comments
 (0)