dask-array 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dask_array/__init__.py +228 -0
- dask_array/_backends.py +76 -0
- dask_array/_backends_array.py +99 -0
- dask_array/_blockwise.py +1410 -0
- dask_array/_broadcast.py +272 -0
- dask_array/_chunk.py +445 -0
- dask_array/_chunk_types.py +54 -0
- dask_array/_collection.py +1644 -0
- dask_array/_concatenate.py +331 -0
- dask_array/_core_utils.py +1365 -0
- dask_array/_dispatch.py +141 -0
- dask_array/_einsum.py +277 -0
- dask_array/_expr.py +544 -0
- dask_array/_expr_flow.py +586 -0
- dask_array/_gufunc.py +805 -0
- dask_array/_histogram.py +617 -0
- dask_array/_map_blocks.py +652 -0
- dask_array/_new_collection.py +10 -0
- dask_array/_numpy_compat.py +135 -0
- dask_array/_overlap.py +1159 -0
- dask_array/_rechunk.py +1050 -0
- dask_array/_reshape.py +710 -0
- dask_array/_routines.py +102 -0
- dask_array/_shuffle.py +448 -0
- dask_array/_stack.py +264 -0
- dask_array/_svg.py +291 -0
- dask_array/_templates.py +29 -0
- dask_array/_test_utils.py +257 -0
- dask_array/_ufunc.py +385 -0
- dask_array/_utils.py +349 -0
- dask_array/_visualize.py +223 -0
- dask_array/_xarray.py +337 -0
- dask_array/core/__init__.py +34 -0
- dask_array/core/_blockwise_funcs.py +312 -0
- dask_array/core/_conversion.py +422 -0
- dask_array/core/_from_graph.py +97 -0
- dask_array/creation/__init__.py +71 -0
- dask_array/creation/_arange.py +121 -0
- dask_array/creation/_diag.py +116 -0
- dask_array/creation/_diagonal.py +241 -0
- dask_array/creation/_eye.py +103 -0
- dask_array/creation/_linspace.py +102 -0
- dask_array/creation/_mesh.py +134 -0
- dask_array/creation/_ones_zeros.py +454 -0
- dask_array/creation/_pad.py +270 -0
- dask_array/creation/_repeat.py +55 -0
- dask_array/creation/_tile.py +36 -0
- dask_array/creation/_tri.py +28 -0
- dask_array/creation/_utils.py +296 -0
- dask_array/fft.py +320 -0
- dask_array/io/__init__.py +39 -0
- dask_array/io/_base.py +10 -0
- dask_array/io/_from_array.py +257 -0
- dask_array/io/_from_delayed.py +95 -0
- dask_array/io/_from_graph.py +54 -0
- dask_array/io/_from_npy_stack.py +67 -0
- dask_array/io/_store.py +336 -0
- dask_array/io/_tiledb.py +159 -0
- dask_array/io/_to_npy_stack.py +65 -0
- dask_array/io/_zarr.py +449 -0
- dask_array/linalg/__init__.py +39 -0
- dask_array/linalg/_cholesky.py +234 -0
- dask_array/linalg/_lu.py +300 -0
- dask_array/linalg/_norm.py +94 -0
- dask_array/linalg/_qr.py +601 -0
- dask_array/linalg/_solve.py +349 -0
- dask_array/linalg/_svd.py +394 -0
- dask_array/linalg/_tensordot.py +334 -0
- dask_array/linalg/_utils.py +74 -0
- dask_array/manipulation/__init__.py +45 -0
- dask_array/manipulation/_expand.py +321 -0
- dask_array/manipulation/_flip.py +92 -0
- dask_array/manipulation/_roll.py +78 -0
- dask_array/manipulation/_transpose.py +309 -0
- dask_array/random/__init__.py +125 -0
- dask_array/random/_choice.py +181 -0
- dask_array/random/_expr.py +256 -0
- dask_array/random/_generator.py +441 -0
- dask_array/random/_random_state.py +259 -0
- dask_array/random/_utils.py +84 -0
- dask_array/reductions/__init__.py +84 -0
- dask_array/reductions/_arg_reduction.py +130 -0
- dask_array/reductions/_common.py +1082 -0
- dask_array/reductions/_cumulative.py +522 -0
- dask_array/reductions/_percentile.py +261 -0
- dask_array/reductions/_reduction.py +725 -0
- dask_array/reductions/_trace.py +56 -0
- dask_array/routines/__init__.py +133 -0
- dask_array/routines/_apply.py +84 -0
- dask_array/routines/_bincount.py +112 -0
- dask_array/routines/_broadcast.py +111 -0
- dask_array/routines/_coarsen.py +115 -0
- dask_array/routines/_diff.py +79 -0
- dask_array/routines/_gradient.py +158 -0
- dask_array/routines/_indexing.py +65 -0
- dask_array/routines/_insert_delete.py +132 -0
- dask_array/routines/_misc.py +122 -0
- dask_array/routines/_nonzero.py +72 -0
- dask_array/routines/_search.py +123 -0
- dask_array/routines/_select.py +113 -0
- dask_array/routines/_statistics.py +171 -0
- dask_array/routines/_topk.py +82 -0
- dask_array/routines/_triangular.py +74 -0
- dask_array/routines/_unique.py +232 -0
- dask_array/routines/_where.py +62 -0
- dask_array/slicing/__init__.py +67 -0
- dask_array/slicing/_basic.py +550 -0
- dask_array/slicing/_blocks.py +138 -0
- dask_array/slicing/_bool_index.py +145 -0
- dask_array/slicing/_setitem.py +329 -0
- dask_array/slicing/_squeeze.py +101 -0
- dask_array/slicing/_utils.py +1133 -0
- dask_array/slicing/_vindex.py +282 -0
- dask_array/stacking/__init__.py +15 -0
- dask_array/stacking/_block.py +83 -0
- dask_array/stacking/_simple.py +58 -0
- dask_array/templates/array.html.j2 +48 -0
- dask_array/tests/__init__.py +0 -0
- dask_array/tests/conftest.py +22 -0
- dask_array/tests/test_api.py +40 -0
- dask_array/tests/test_binary_op_chunks.py +107 -0
- dask_array/tests/test_coarse_slice_through_blockwise.py +362 -0
- dask_array/tests/test_collection.py +799 -0
- dask_array/tests/test_creation.py +1102 -0
- dask_array/tests/test_expr_flow.py +143 -0
- dask_array/tests/test_linalg.py +1130 -0
- dask_array/tests/test_map_blocks_multi_output.py +104 -0
- dask_array/tests/test_rechunk_pushdown.py +214 -0
- dask_array/tests/test_reductions.py +1091 -0
- dask_array/tests/test_routines.py +2853 -0
- dask_array/tests/test_shuffle_chunks.py +67 -0
- dask_array/tests/test_slice_pushdown.py +968 -0
- dask_array/tests/test_slice_through_blockwise.py +678 -0
- dask_array/tests/test_slice_through_overlap.py +366 -0
- dask_array/tests/test_slice_through_reshape.py +272 -0
- dask_array/tests/test_slicing.py +839 -0
- dask_array/tests/test_transpose_slice_pushdown.py +208 -0
- dask_array/tests/test_visualize.py +94 -0
- dask_array/tests/test_xarray.py +193 -0
- dask_array-0.1.0.dist-info/METADATA +48 -0
- dask_array-0.1.0.dist-info/RECORD +144 -0
- dask_array-0.1.0.dist-info/WHEEL +4 -0
- dask_array-0.1.0.dist-info/entry_points.txt +2 -0
- dask_array-0.1.0.dist-info/licenses/LICENSE +29 -0
|
@@ -0,0 +1,1133 @@
|
|
|
1
|
+
"""Slicing utility functions copied from dask.array.slicing.
|
|
2
|
+
|
|
3
|
+
These are local copies to reduce dependency on dask.array.slicing.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import bisect
|
|
9
|
+
import math
|
|
10
|
+
import warnings
|
|
11
|
+
from numbers import Integral, Number
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from tlz import memoize
|
|
15
|
+
|
|
16
|
+
from dask.base import is_dask_collection
|
|
17
|
+
from dask.utils import cached_cumsum, is_arraylike
|
|
18
|
+
|
|
19
|
+
colon = slice(None, None, None)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SlicingNoop(Exception):
|
|
23
|
+
"""This indicates that a slicing operation is a no-op. The caller has to handle this"""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ============================================================================
|
|
29
|
+
# Helper functions
|
|
30
|
+
# ============================================================================
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _array_like_safe(np_func, da_func, a, like, **kwargs):
|
|
34
|
+
"""Helper for asanyarray_safe."""
|
|
35
|
+
from dask_array._collection import Array
|
|
36
|
+
|
|
37
|
+
if like is a and hasattr(a, "__array_function__"):
|
|
38
|
+
return a
|
|
39
|
+
|
|
40
|
+
if isinstance(like, Array):
|
|
41
|
+
return da_func(a, **kwargs)
|
|
42
|
+
|
|
43
|
+
# Handle dask arrays backed by cupy
|
|
44
|
+
if isinstance(a, Array):
|
|
45
|
+
try:
|
|
46
|
+
from dask.utils import is_cupy_type
|
|
47
|
+
|
|
48
|
+
if is_cupy_type(a._meta):
|
|
49
|
+
a = a.compute(scheduler="sync")
|
|
50
|
+
except ImportError:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
if hasattr(like, "__array_function__"):
|
|
54
|
+
return np_func(a, like=like, **kwargs)
|
|
55
|
+
|
|
56
|
+
if type(like).__module__.startswith("scipy.sparse"):
|
|
57
|
+
kwargs.pop("order", None)
|
|
58
|
+
if np.isscalar(a):
|
|
59
|
+
a = np.array([[a]])
|
|
60
|
+
return type(like)(a, **kwargs)
|
|
61
|
+
|
|
62
|
+
return np_func(a, **kwargs)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def asanyarray_safe(a, like, **kwargs):
|
|
66
|
+
"""Convert to array using np.asanyarray with proper dispatching.
|
|
67
|
+
|
|
68
|
+
If a is dask.array, return dask.array.asanyarray(a, **kwargs),
|
|
69
|
+
otherwise return np.asanyarray(a, like=like, **kwargs), dispatching
|
|
70
|
+
the call to the library that implements the like array.
|
|
71
|
+
"""
|
|
72
|
+
from dask_array.core._conversion import asanyarray
|
|
73
|
+
|
|
74
|
+
return _array_like_safe(np.asanyarray, asanyarray, a, like, **kwargs)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ============================================================================
|
|
78
|
+
# sanitize_index and helpers
|
|
79
|
+
# ============================================================================
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _sanitize_index_element(ind):
|
|
83
|
+
"""Sanitize a one-element index."""
|
|
84
|
+
if isinstance(ind, Number):
|
|
85
|
+
ind2 = int(ind)
|
|
86
|
+
if ind2 != ind:
|
|
87
|
+
raise IndexError(f"Bad index. Must be integer-like: {ind}")
|
|
88
|
+
else:
|
|
89
|
+
return ind2
|
|
90
|
+
elif ind is None:
|
|
91
|
+
return None
|
|
92
|
+
elif is_dask_collection(ind):
|
|
93
|
+
if ind.dtype.kind != "i" or ind.size != 1:
|
|
94
|
+
raise IndexError(f"Bad index. Must be integer-like: {ind}")
|
|
95
|
+
return ind
|
|
96
|
+
else:
|
|
97
|
+
raise TypeError("Invalid index type", type(ind), ind)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def sanitize_index(ind):
|
|
101
|
+
"""Sanitize the elements for indexing along one axis.
|
|
102
|
+
|
|
103
|
+
Examples
|
|
104
|
+
--------
|
|
105
|
+
>>> sanitize_index([2, 3, 5])
|
|
106
|
+
array([2, 3, 5])
|
|
107
|
+
>>> sanitize_index([True, False, True, False])
|
|
108
|
+
array([0, 2])
|
|
109
|
+
>>> sanitize_index(np.array([1, 2, 3]))
|
|
110
|
+
array([1, 2, 3])
|
|
111
|
+
>>> sanitize_index(np.array([False, True, True]))
|
|
112
|
+
array([1, 2])
|
|
113
|
+
>>> type(sanitize_index(np.int32(0)))
|
|
114
|
+
<class 'int'>
|
|
115
|
+
>>> sanitize_index(1.0)
|
|
116
|
+
1
|
|
117
|
+
>>> sanitize_index(0.5)
|
|
118
|
+
Traceback (most recent call last):
|
|
119
|
+
...
|
|
120
|
+
IndexError: Bad index. Must be integer-like: 0.5
|
|
121
|
+
"""
|
|
122
|
+
if ind is None:
|
|
123
|
+
return None
|
|
124
|
+
elif isinstance(ind, slice):
|
|
125
|
+
return slice(
|
|
126
|
+
_sanitize_index_element(ind.start),
|
|
127
|
+
_sanitize_index_element(ind.stop),
|
|
128
|
+
_sanitize_index_element(ind.step),
|
|
129
|
+
)
|
|
130
|
+
elif isinstance(ind, Number):
|
|
131
|
+
return _sanitize_index_element(ind)
|
|
132
|
+
elif is_dask_collection(ind):
|
|
133
|
+
return ind
|
|
134
|
+
index_array = asanyarray_safe(ind, like=ind)
|
|
135
|
+
if index_array.dtype == bool:
|
|
136
|
+
nonzero = np.nonzero(index_array)
|
|
137
|
+
if len(nonzero) == 1:
|
|
138
|
+
nonzero = nonzero[0]
|
|
139
|
+
if is_arraylike(nonzero):
|
|
140
|
+
return nonzero
|
|
141
|
+
else:
|
|
142
|
+
return np.asanyarray(nonzero)
|
|
143
|
+
elif np.issubdtype(index_array.dtype, np.integer):
|
|
144
|
+
return index_array
|
|
145
|
+
elif np.issubdtype(index_array.dtype, np.floating):
|
|
146
|
+
int_index = index_array.astype(np.intp)
|
|
147
|
+
if np.allclose(index_array, int_index):
|
|
148
|
+
return int_index
|
|
149
|
+
else:
|
|
150
|
+
check_int = np.isclose(index_array, int_index)
|
|
151
|
+
first_err = index_array.ravel()[np.flatnonzero(~check_int)[0]]
|
|
152
|
+
raise IndexError(f"Bad index. Must be integer-like: {first_err}")
|
|
153
|
+
else:
|
|
154
|
+
raise TypeError("Invalid index type", type(ind), ind)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ============================================================================
|
|
158
|
+
# replace_ellipsis
|
|
159
|
+
# ============================================================================
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def replace_ellipsis(n, index):
|
|
163
|
+
"""Replace ... with slices, :, :, :
|
|
164
|
+
|
|
165
|
+
Examples
|
|
166
|
+
--------
|
|
167
|
+
>>> replace_ellipsis(4, (3, Ellipsis, 2))
|
|
168
|
+
(3, slice(None, None, None), slice(None, None, None), 2)
|
|
169
|
+
|
|
170
|
+
>>> replace_ellipsis(2, (Ellipsis, None))
|
|
171
|
+
(slice(None, None, None), slice(None, None, None), None)
|
|
172
|
+
"""
|
|
173
|
+
# Careful about using in or index because index may contain arrays
|
|
174
|
+
isellipsis = [i for i, ind in enumerate(index) if ind is Ellipsis]
|
|
175
|
+
if not isellipsis:
|
|
176
|
+
return index
|
|
177
|
+
else:
|
|
178
|
+
loc = isellipsis[0]
|
|
179
|
+
extra_dimensions = n - (len(index) - sum(i is None for i in index) - 1)
|
|
180
|
+
return index[:loc] + (slice(None, None, None),) * extra_dimensions + index[loc + 1 :]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ============================================================================
|
|
184
|
+
# normalize_slice and helpers
|
|
185
|
+
# ============================================================================
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def normalize_slice(idx, dim):
|
|
189
|
+
"""Normalize slices to canonical form.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
idx: slice or other index
|
|
194
|
+
dim: dimension length
|
|
195
|
+
|
|
196
|
+
Examples
|
|
197
|
+
--------
|
|
198
|
+
>>> normalize_slice(slice(0, 10, 1), 10)
|
|
199
|
+
slice(None, None, None)
|
|
200
|
+
"""
|
|
201
|
+
if isinstance(idx, slice):
|
|
202
|
+
if math.isnan(dim):
|
|
203
|
+
return idx
|
|
204
|
+
start, stop, step = idx.indices(dim)
|
|
205
|
+
if step > 0:
|
|
206
|
+
if start == 0:
|
|
207
|
+
start = None
|
|
208
|
+
if stop >= dim:
|
|
209
|
+
stop = None
|
|
210
|
+
if step == 1:
|
|
211
|
+
step = None
|
|
212
|
+
if stop is not None and start is not None and stop < start:
|
|
213
|
+
stop = start
|
|
214
|
+
elif step < 0:
|
|
215
|
+
if start >= dim - 1:
|
|
216
|
+
start = None
|
|
217
|
+
if stop < 0:
|
|
218
|
+
stop = None
|
|
219
|
+
return slice(start, stop, step)
|
|
220
|
+
return idx
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ============================================================================
|
|
224
|
+
# posify_index
|
|
225
|
+
# ============================================================================
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def posify_index(shape, ind):
|
|
229
|
+
"""Flip negative indices around to positive ones.
|
|
230
|
+
|
|
231
|
+
Examples
|
|
232
|
+
--------
|
|
233
|
+
>>> posify_index(10, 3)
|
|
234
|
+
3
|
|
235
|
+
>>> posify_index(10, -3)
|
|
236
|
+
7
|
|
237
|
+
>>> posify_index(10, [3, -3])
|
|
238
|
+
array([3, 7])
|
|
239
|
+
|
|
240
|
+
>>> posify_index((10, 20), (3, -3))
|
|
241
|
+
(3, 17)
|
|
242
|
+
>>> posify_index((10, 20), (3, [3, 4, -3])) # doctest: +NORMALIZE_WHITESPACE
|
|
243
|
+
(3, array([ 3, 4, 17]))
|
|
244
|
+
"""
|
|
245
|
+
if isinstance(ind, tuple):
|
|
246
|
+
return tuple(map(posify_index, shape, ind))
|
|
247
|
+
if isinstance(ind, Integral):
|
|
248
|
+
if ind < 0 and not math.isnan(shape):
|
|
249
|
+
return ind + shape
|
|
250
|
+
else:
|
|
251
|
+
return ind
|
|
252
|
+
if isinstance(ind, (np.ndarray, list)) and not math.isnan(shape):
|
|
253
|
+
ind = np.asanyarray(ind)
|
|
254
|
+
return np.where(ind < 0, ind + shape, ind)
|
|
255
|
+
return ind
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# ============================================================================
|
|
259
|
+
# check_index
|
|
260
|
+
# ============================================================================
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def check_index(axis, ind, dimension):
|
|
264
|
+
"""Check validity of index for a given dimension.
|
|
265
|
+
|
|
266
|
+
Examples
|
|
267
|
+
--------
|
|
268
|
+
>>> check_index(0, 3, 5)
|
|
269
|
+
>>> check_index(0, 5, 5)
|
|
270
|
+
Traceback (most recent call last):
|
|
271
|
+
...
|
|
272
|
+
IndexError: Index 5 is out of bounds for axis 0 with size 5
|
|
273
|
+
|
|
274
|
+
>>> check_index(1, 6, 5)
|
|
275
|
+
Traceback (most recent call last):
|
|
276
|
+
...
|
|
277
|
+
IndexError: Index 6 is out of bounds for axis 1 with size 5
|
|
278
|
+
|
|
279
|
+
>>> check_index(1, -1, 5)
|
|
280
|
+
>>> check_index(1, -6, 5)
|
|
281
|
+
Traceback (most recent call last):
|
|
282
|
+
...
|
|
283
|
+
IndexError: Index -6 is out of bounds for axis 1 with size 5
|
|
284
|
+
|
|
285
|
+
>>> check_index(0, [1, 2], 5)
|
|
286
|
+
>>> check_index(0, [6, 3], 5)
|
|
287
|
+
Traceback (most recent call last):
|
|
288
|
+
...
|
|
289
|
+
IndexError: Index is out of bounds for axis 0 with size 5
|
|
290
|
+
|
|
291
|
+
>>> check_index(1, slice(0, 3), 5)
|
|
292
|
+
|
|
293
|
+
>>> check_index(0, [True], 1)
|
|
294
|
+
>>> check_index(0, [True, True], 3)
|
|
295
|
+
Traceback (most recent call last):
|
|
296
|
+
...
|
|
297
|
+
IndexError: Boolean array with size 2 is not long enough for axis 0 with size 3
|
|
298
|
+
>>> check_index(0, [True, True, True], 1)
|
|
299
|
+
Traceback (most recent call last):
|
|
300
|
+
...
|
|
301
|
+
IndexError: Boolean array with size 3 is not long enough for axis 0 with size 1
|
|
302
|
+
"""
|
|
303
|
+
if isinstance(ind, list):
|
|
304
|
+
ind = np.asanyarray(ind)
|
|
305
|
+
|
|
306
|
+
# unknown dimension, assumed to be in bounds
|
|
307
|
+
if np.isnan(dimension):
|
|
308
|
+
return
|
|
309
|
+
elif is_dask_collection(ind):
|
|
310
|
+
return
|
|
311
|
+
elif is_arraylike(ind):
|
|
312
|
+
if ind.dtype == bool:
|
|
313
|
+
if ind.size != dimension:
|
|
314
|
+
raise IndexError(
|
|
315
|
+
f"Boolean array with size {ind.size} is not long enough for axis {axis} with size {dimension}"
|
|
316
|
+
)
|
|
317
|
+
elif (ind >= dimension).any() or (ind < -dimension).any():
|
|
318
|
+
raise IndexError(f"Index is out of bounds for axis {axis} with size {dimension}")
|
|
319
|
+
elif isinstance(ind, slice):
|
|
320
|
+
return
|
|
321
|
+
elif ind is None:
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
elif ind >= dimension or ind < -dimension:
|
|
325
|
+
raise IndexError(f"Index {ind} is out of bounds for axis {axis} with size {dimension}")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# ============================================================================
|
|
329
|
+
# _slice_1d
|
|
330
|
+
# ============================================================================
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _slice_1d(dim_shape, lengths, index):
|
|
334
|
+
"""Returns a dict of {blocknum: slice}.
|
|
335
|
+
|
|
336
|
+
This function figures out where each slice should start in each
|
|
337
|
+
block for a single dimension. If the slice won't return any elements
|
|
338
|
+
in the block, that block will not be in the output.
|
|
339
|
+
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
dim_shape - the number of elements in this dimension.
|
|
343
|
+
This should be a positive, non-zero integer
|
|
344
|
+
lengths - the number of elements per block in this dimension
|
|
345
|
+
This should be a positive, non-zero integer
|
|
346
|
+
index - a description of the elements in this dimension that we want
|
|
347
|
+
This might be an integer, a slice(), or an Ellipsis
|
|
348
|
+
|
|
349
|
+
Returns
|
|
350
|
+
-------
|
|
351
|
+
dictionary where the keys are the integer index of the blocks that
|
|
352
|
+
should be sliced and the values are the slices
|
|
353
|
+
|
|
354
|
+
Examples
|
|
355
|
+
--------
|
|
356
|
+
Trivial slicing
|
|
357
|
+
|
|
358
|
+
>>> _slice_1d(100, [60, 40], slice(None, None, None))
|
|
359
|
+
{0: slice(None, None, None), 1: slice(None, None, None)}
|
|
360
|
+
|
|
361
|
+
100 length array cut into length 20 pieces, slice 0:35
|
|
362
|
+
|
|
363
|
+
>>> _slice_1d(100, [20, 20, 20, 20, 20], slice(0, 35))
|
|
364
|
+
{0: slice(None, None, None), 1: slice(0, 15, 1)}
|
|
365
|
+
|
|
366
|
+
Support irregular blocks and various slices
|
|
367
|
+
|
|
368
|
+
>>> _slice_1d(100, [20, 10, 10, 10, 25, 25], slice(10, 35))
|
|
369
|
+
{0: slice(10, 20, 1), 1: slice(None, None, None), 2: slice(0, 5, 1)}
|
|
370
|
+
|
|
371
|
+
Support step sizes
|
|
372
|
+
|
|
373
|
+
>>> _slice_1d(100, [15, 14, 13], slice(10, 41, 3))
|
|
374
|
+
{0: slice(10, 15, 3), 1: slice(1, 14, 3), 2: slice(2, 12, 3)}
|
|
375
|
+
|
|
376
|
+
>>> _slice_1d(100, [20, 20, 20, 20, 20], slice(0, 100, 40)) # step > blocksize
|
|
377
|
+
{0: slice(0, 20, 40), 2: slice(0, 20, 40), 4: slice(0, 20, 40)}
|
|
378
|
+
|
|
379
|
+
Also support indexing single elements
|
|
380
|
+
|
|
381
|
+
>>> _slice_1d(100, [20, 20, 20, 20, 20], 25)
|
|
382
|
+
{1: 5}
|
|
383
|
+
|
|
384
|
+
And negative slicing
|
|
385
|
+
|
|
386
|
+
>>> _slice_1d(100, [20, 20, 20, 20, 20], slice(100, 0, -3)) # doctest: +NORMALIZE_WHITESPACE
|
|
387
|
+
{4: slice(-1, -21, -3),
|
|
388
|
+
3: slice(-2, -21, -3),
|
|
389
|
+
2: slice(-3, -21, -3),
|
|
390
|
+
1: slice(-1, -21, -3),
|
|
391
|
+
0: slice(-2, -20, -3)}
|
|
392
|
+
|
|
393
|
+
>>> _slice_1d(100, [20, 20, 20, 20, 20], slice(100, 12, -3)) # doctest: +NORMALIZE_WHITESPACE
|
|
394
|
+
{4: slice(-1, -21, -3),
|
|
395
|
+
3: slice(-2, -21, -3),
|
|
396
|
+
2: slice(-3, -21, -3),
|
|
397
|
+
1: slice(-1, -21, -3),
|
|
398
|
+
0: slice(-2, -8, -3)}
|
|
399
|
+
|
|
400
|
+
>>> _slice_1d(100, [20, 20, 20, 20, 20], slice(100, -12, -3))
|
|
401
|
+
{4: slice(-1, -12, -3)}
|
|
402
|
+
"""
|
|
403
|
+
chunk_boundaries = cached_cumsum(lengths)
|
|
404
|
+
|
|
405
|
+
if isinstance(index, Integral):
|
|
406
|
+
# use right-side search to be consistent with previous result
|
|
407
|
+
i = bisect.bisect_right(chunk_boundaries, index)
|
|
408
|
+
if i > 0:
|
|
409
|
+
# the very first chunk has no relative shift
|
|
410
|
+
ind = index - chunk_boundaries[i - 1]
|
|
411
|
+
else:
|
|
412
|
+
ind = index
|
|
413
|
+
return {int(i): int(ind)}
|
|
414
|
+
|
|
415
|
+
assert isinstance(index, slice)
|
|
416
|
+
|
|
417
|
+
if index == colon:
|
|
418
|
+
return dict.fromkeys(range(len(lengths)), colon)
|
|
419
|
+
|
|
420
|
+
step = index.step or 1
|
|
421
|
+
if step > 0:
|
|
422
|
+
start = index.start or 0
|
|
423
|
+
stop = index.stop if index.stop is not None else dim_shape
|
|
424
|
+
else:
|
|
425
|
+
start = index.start if index.start is not None else dim_shape - 1
|
|
426
|
+
start = dim_shape - 1 if start >= dim_shape else start
|
|
427
|
+
stop = -(dim_shape + 1) if index.stop is None else index.stop
|
|
428
|
+
|
|
429
|
+
# posify start and stop
|
|
430
|
+
if start < 0:
|
|
431
|
+
start += dim_shape
|
|
432
|
+
if stop < 0:
|
|
433
|
+
stop += dim_shape
|
|
434
|
+
|
|
435
|
+
d = dict()
|
|
436
|
+
if step > 0:
|
|
437
|
+
istart = bisect.bisect_right(chunk_boundaries, start)
|
|
438
|
+
istop = bisect.bisect_left(chunk_boundaries, stop)
|
|
439
|
+
|
|
440
|
+
# the bound is not exactly tight; make it tighter?
|
|
441
|
+
istop = min(istop + 1, len(lengths))
|
|
442
|
+
|
|
443
|
+
# jump directly to istart
|
|
444
|
+
if istart > 0:
|
|
445
|
+
start = start - chunk_boundaries[istart - 1]
|
|
446
|
+
stop = stop - chunk_boundaries[istart - 1]
|
|
447
|
+
|
|
448
|
+
for i in range(istart, istop):
|
|
449
|
+
length = lengths[i]
|
|
450
|
+
if start < length and stop > 0:
|
|
451
|
+
d[i] = slice(start, min(stop, length), step)
|
|
452
|
+
start = (start - length) % step
|
|
453
|
+
else:
|
|
454
|
+
start = start - length
|
|
455
|
+
stop -= length
|
|
456
|
+
else:
|
|
457
|
+
rstart = start # running start
|
|
458
|
+
|
|
459
|
+
istart = bisect.bisect_left(chunk_boundaries, start)
|
|
460
|
+
istop = bisect.bisect_right(chunk_boundaries, stop)
|
|
461
|
+
|
|
462
|
+
# the bound is not exactly tight; make it tighter?
|
|
463
|
+
istart = min(istart + 1, len(chunk_boundaries) - 1)
|
|
464
|
+
istop = max(istop - 1, -1)
|
|
465
|
+
|
|
466
|
+
for i in range(istart, istop, -1):
|
|
467
|
+
chunk_stop = chunk_boundaries[i]
|
|
468
|
+
# create a chunk start and stop
|
|
469
|
+
if i == 0:
|
|
470
|
+
chunk_start = 0
|
|
471
|
+
else:
|
|
472
|
+
chunk_start = chunk_boundaries[i - 1]
|
|
473
|
+
|
|
474
|
+
# if our slice is in this chunk
|
|
475
|
+
if (chunk_start <= rstart < chunk_stop) and (rstart > stop):
|
|
476
|
+
d[i] = slice(
|
|
477
|
+
rstart - chunk_stop,
|
|
478
|
+
max(chunk_start - chunk_stop - 1, stop - chunk_stop),
|
|
479
|
+
step,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# compute the next running start point,
|
|
483
|
+
offset = (rstart - (chunk_start - 1)) % step
|
|
484
|
+
rstart = chunk_start + offset - 1
|
|
485
|
+
|
|
486
|
+
# replace 0:20:1 with : if appropriate
|
|
487
|
+
for k, v in d.items():
|
|
488
|
+
if v == slice(0, lengths[k], 1):
|
|
489
|
+
d[k] = slice(None, None, None)
|
|
490
|
+
|
|
491
|
+
if not d: # special case x[:0]
|
|
492
|
+
d[0] = slice(0, 0, 1)
|
|
493
|
+
|
|
494
|
+
return d
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
# ============================================================================
|
|
498
|
+
# new_blockdim
|
|
499
|
+
# ============================================================================
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def new_blockdim(dim_shape, lengths, index):
|
|
503
|
+
"""Compute new block dimension sizes after slicing.
|
|
504
|
+
|
|
505
|
+
Examples
|
|
506
|
+
--------
|
|
507
|
+
>>> new_blockdim(100, [20, 10, 20, 10, 40], slice(0, 90, 2))
|
|
508
|
+
[10, 5, 10, 5, 15]
|
|
509
|
+
|
|
510
|
+
>>> new_blockdim(100, [20, 10, 20, 10, 40], [5, 1, 30, 22])
|
|
511
|
+
[4]
|
|
512
|
+
|
|
513
|
+
>>> new_blockdim(100, [20, 10, 20, 10, 40], slice(90, 10, -2))
|
|
514
|
+
[16, 5, 10, 5, 4]
|
|
515
|
+
"""
|
|
516
|
+
from operator import itemgetter
|
|
517
|
+
|
|
518
|
+
if index == slice(None, None, None):
|
|
519
|
+
return lengths
|
|
520
|
+
if isinstance(index, list):
|
|
521
|
+
return [len(index)]
|
|
522
|
+
assert not isinstance(index, Integral)
|
|
523
|
+
pairs = sorted(_slice_1d(dim_shape, lengths, index).items(), key=itemgetter(0))
|
|
524
|
+
slices = [slice(0, lengths[i], 1) if slc == slice(None, None, None) else slc for i, slc in pairs]
|
|
525
|
+
if isinstance(index, slice) and index.step and index.step < 0:
|
|
526
|
+
slices.reverse()
|
|
527
|
+
return [int(math.ceil((1.0 * slc.stop - slc.start) / slc.step)) for slc in slices]
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
# ============================================================================
|
|
531
|
+
# normalize_index (uses many helpers above)
|
|
532
|
+
# ============================================================================
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def normalize_index(idx, shape):
|
|
536
|
+
"""Normalize slicing indexes.
|
|
537
|
+
|
|
538
|
+
1. Replaces ellipses with many full slices
|
|
539
|
+
2. Adds full slices to end of index
|
|
540
|
+
3. Checks bounding conditions
|
|
541
|
+
4. Replace multidimensional numpy arrays with dask arrays
|
|
542
|
+
5. Replaces numpy arrays with lists
|
|
543
|
+
6. Posify's integers and lists
|
|
544
|
+
7. Normalizes slices to canonical form
|
|
545
|
+
|
|
546
|
+
Examples
|
|
547
|
+
--------
|
|
548
|
+
>>> normalize_index(1, (10,))
|
|
549
|
+
(1,)
|
|
550
|
+
>>> normalize_index(-1, (10,))
|
|
551
|
+
(9,)
|
|
552
|
+
>>> normalize_index([-1], (10,))
|
|
553
|
+
(array([9]),)
|
|
554
|
+
>>> normalize_index(slice(-3, 10, 1), (10,))
|
|
555
|
+
(slice(7, None, None),)
|
|
556
|
+
>>> normalize_index((Ellipsis, None), (10,))
|
|
557
|
+
(slice(None, None, None), None)
|
|
558
|
+
"""
|
|
559
|
+
from dask_array._collection import Array
|
|
560
|
+
from dask_array.core import from_array
|
|
561
|
+
|
|
562
|
+
if not isinstance(idx, tuple):
|
|
563
|
+
idx = (idx,)
|
|
564
|
+
|
|
565
|
+
# if a > 1D numpy.array is provided, cast it to a dask array
|
|
566
|
+
if len(idx) > 0 and len(shape) > 1:
|
|
567
|
+
i = idx[0]
|
|
568
|
+
if is_arraylike(i) and not isinstance(i, Array) and i.shape == shape:
|
|
569
|
+
idx = (from_array(i), *idx[1:])
|
|
570
|
+
|
|
571
|
+
idx = replace_ellipsis(len(shape), idx)
|
|
572
|
+
n_sliced_dims = 0
|
|
573
|
+
for i in idx:
|
|
574
|
+
if hasattr(i, "ndim") and i.ndim >= 1:
|
|
575
|
+
n_sliced_dims += i.ndim
|
|
576
|
+
elif i is None:
|
|
577
|
+
continue
|
|
578
|
+
else:
|
|
579
|
+
n_sliced_dims += 1
|
|
580
|
+
|
|
581
|
+
idx = idx + (slice(None),) * (len(shape) - n_sliced_dims)
|
|
582
|
+
if len([i for i in idx if i is not None]) > len(shape):
|
|
583
|
+
raise IndexError("Too many indices for array")
|
|
584
|
+
|
|
585
|
+
none_shape = []
|
|
586
|
+
i = 0
|
|
587
|
+
for ind in idx:
|
|
588
|
+
if ind is not None:
|
|
589
|
+
none_shape.append(shape[i])
|
|
590
|
+
i += 1
|
|
591
|
+
else:
|
|
592
|
+
none_shape.append(None)
|
|
593
|
+
|
|
594
|
+
for axis, (i, d) in enumerate(zip(idx, none_shape)):
|
|
595
|
+
if d is not None:
|
|
596
|
+
check_index(axis, i, d)
|
|
597
|
+
idx = tuple(map(sanitize_index, idx))
|
|
598
|
+
idx = tuple(map(normalize_slice, idx, none_shape))
|
|
599
|
+
idx = posify_index(none_shape, idx)
|
|
600
|
+
return idx
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
# ============================================================================
|
|
604
|
+
# parse_assignment_indices
|
|
605
|
+
# ============================================================================
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def parse_assignment_indices(indices, shape):
|
|
609
|
+
"""Reformat the indices for assignment.
|
|
610
|
+
|
|
611
|
+
The aim of this is to convert the indices to a standardised form
|
|
612
|
+
so that it is easier to ascertain which chunks are touched by the
|
|
613
|
+
indices.
|
|
614
|
+
|
|
615
|
+
This function is intended to be called by `setitem_array`.
|
|
616
|
+
|
|
617
|
+
A slice object that is decreasing (i.e. with a negative step), is
|
|
618
|
+
recast as an increasing slice (i.e. with a positive step. For
|
|
619
|
+
example ``slice(7,3,-1)`` would be cast as ``slice(4,8,1)``. This
|
|
620
|
+
is to facilitate finding which blocks are touched by the
|
|
621
|
+
index. The dimensions for which this has occurred are returned by
|
|
622
|
+
the function.
|
|
623
|
+
|
|
624
|
+
Parameters
|
|
625
|
+
----------
|
|
626
|
+
indices : numpy-style indices
|
|
627
|
+
Indices to array defining the elements to be assigned.
|
|
628
|
+
shape : sequence of `int`
|
|
629
|
+
The shape of the array.
|
|
630
|
+
|
|
631
|
+
Returns
|
|
632
|
+
-------
|
|
633
|
+
parsed_indices : `list`
|
|
634
|
+
The reformatted indices that are equivalent to the input
|
|
635
|
+
indices.
|
|
636
|
+
implied_shape : `list`
|
|
637
|
+
The shape implied by the parsed indices. For instance, indices
|
|
638
|
+
of ``(slice(0,2), 5, [4,1,-1])`` will have implied shape
|
|
639
|
+
``[2,3]``.
|
|
640
|
+
reverse : `list`
|
|
641
|
+
The positions of the dimensions whose indices in the
|
|
642
|
+
parsed_indices output are reversed slices.
|
|
643
|
+
implied_shape_positions: `list`
|
|
644
|
+
The positions of the dimensions whose indices contribute to
|
|
645
|
+
the implied_shape. For instance, indices of ``(slice(0,2), 5,
|
|
646
|
+
[4,1,-1])`` will have implied_shape ``[2,3]`` and
|
|
647
|
+
implied_shape_positions ``[0,2]``.
|
|
648
|
+
|
|
649
|
+
Examples
|
|
650
|
+
--------
|
|
651
|
+
>>> parse_assignment_indices((slice(1, -1),), (8,))
|
|
652
|
+
([slice(1, 7, 1)], [6], [], [0])
|
|
653
|
+
|
|
654
|
+
>>> parse_assignment_indices(([1, 2, 6, 5],), (8,))
|
|
655
|
+
([array([1, 2, 6, 5])], [4], [], [0])
|
|
656
|
+
|
|
657
|
+
>>> parse_assignment_indices((3, slice(-1, 2, -1)), (7, 8))
|
|
658
|
+
([3, slice(3, 8, 1)], [5], [1], [1])
|
|
659
|
+
|
|
660
|
+
>>> parse_assignment_indices((slice(-1, 2, -1), 3, [1, 2]), (7, 8, 9))
|
|
661
|
+
([slice(3, 7, 1), 3, array([1, 2])], [4, 2], [0], [0, 2])
|
|
662
|
+
|
|
663
|
+
>>> parse_assignment_indices((slice(0, 5), slice(3, None, 2)), (5, 4))
|
|
664
|
+
([slice(0, 5, 1), slice(3, 4, 2)], [5, 1], [], [0, 1])
|
|
665
|
+
|
|
666
|
+
>>> parse_assignment_indices((slice(0, 5), slice(3, 3, 2)), (5, 4))
|
|
667
|
+
([slice(0, 5, 1), slice(3, 3, 2)], [5, 0], [], [0])
|
|
668
|
+
|
|
669
|
+
"""
|
|
670
|
+
if not isinstance(indices, tuple):
|
|
671
|
+
indices = (indices,)
|
|
672
|
+
|
|
673
|
+
# Disallow scalar boolean indexing, and also indexing by scalar
|
|
674
|
+
# numpy or dask array.
|
|
675
|
+
for index in indices:
|
|
676
|
+
if index is True or index is False:
|
|
677
|
+
raise NotImplementedError(f"dask does not yet implement assignment to a scalar boolean index: {index!r}")
|
|
678
|
+
|
|
679
|
+
if (is_arraylike(index) or is_dask_collection(index)) and not index.ndim:
|
|
680
|
+
raise NotImplementedError(
|
|
681
|
+
f"dask does not yet implement assignment to a scalar numpy or dask array index: {index!r}"
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
# Initialize output variables
|
|
685
|
+
implied_shape = []
|
|
686
|
+
implied_shape_positions = []
|
|
687
|
+
reverse = []
|
|
688
|
+
parsed_indices = list(normalize_index(indices, shape))
|
|
689
|
+
|
|
690
|
+
n_lists = 0
|
|
691
|
+
|
|
692
|
+
for i, (index, size) in enumerate(zip(parsed_indices, shape)):
|
|
693
|
+
is_slice = isinstance(index, slice)
|
|
694
|
+
if is_slice:
|
|
695
|
+
# Index is a slice
|
|
696
|
+
start, stop, step = index.indices(size)
|
|
697
|
+
if step < 0 and stop == -1:
|
|
698
|
+
stop = None
|
|
699
|
+
|
|
700
|
+
index = slice(start, stop, step)
|
|
701
|
+
|
|
702
|
+
if step < 0:
|
|
703
|
+
# When the slice step is negative, transform the
|
|
704
|
+
# original slice to a new slice with a positive step
|
|
705
|
+
# such that the result of the new slice is the reverse
|
|
706
|
+
# of the result of the original slice.
|
|
707
|
+
start, stop, step = index.indices(size)
|
|
708
|
+
step *= -1
|
|
709
|
+
div, mod = divmod(start - stop - 1, step)
|
|
710
|
+
div_step = div * step
|
|
711
|
+
start -= div_step
|
|
712
|
+
stop = start + div_step + 1
|
|
713
|
+
|
|
714
|
+
index = slice(start, stop, step)
|
|
715
|
+
reverse.append(i)
|
|
716
|
+
|
|
717
|
+
start, stop, step = index.indices(size)
|
|
718
|
+
|
|
719
|
+
# Note: We now have stop >= start and step >= 0
|
|
720
|
+
|
|
721
|
+
div, mod = divmod(stop - start, step)
|
|
722
|
+
if not div and not mod:
|
|
723
|
+
# stop equals start => zero-sized slice for this
|
|
724
|
+
# dimension
|
|
725
|
+
implied_shape.append(0)
|
|
726
|
+
else:
|
|
727
|
+
if mod != 0:
|
|
728
|
+
div += 1
|
|
729
|
+
|
|
730
|
+
implied_shape.append(div)
|
|
731
|
+
implied_shape_positions.append(i)
|
|
732
|
+
|
|
733
|
+
elif isinstance(index, (int, np.integer)):
|
|
734
|
+
# Index is an integer
|
|
735
|
+
index = int(index)
|
|
736
|
+
|
|
737
|
+
elif is_arraylike(index) or is_dask_collection(index):
|
|
738
|
+
# Index is 1-d array
|
|
739
|
+
n_lists += 1
|
|
740
|
+
if n_lists > 1:
|
|
741
|
+
raise NotImplementedError(
|
|
742
|
+
"dask is currently limited to at most one "
|
|
743
|
+
"dimension's assignment index being a "
|
|
744
|
+
"1-d array of integers or booleans. "
|
|
745
|
+
f"Got: {indices}"
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
if index.ndim != 1:
|
|
749
|
+
raise IndexError(f"Incorrect shape ({index.shape}) of integer indices for dimension with size {size}")
|
|
750
|
+
|
|
751
|
+
index_size = index.size
|
|
752
|
+
if index.dtype == bool and not math.isnan(index_size) and index_size != size:
|
|
753
|
+
raise IndexError(
|
|
754
|
+
"boolean index did not match indexed array along "
|
|
755
|
+
f"dimension {i}; dimension is {size} but "
|
|
756
|
+
f"corresponding boolean dimension is {index_size}"
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
# Posify an integer dask array (integer numpy arrays were
|
|
760
|
+
# posified in `normalize_index`)
|
|
761
|
+
if is_dask_collection(index):
|
|
762
|
+
if index.dtype == bool:
|
|
763
|
+
index_size = np.nan
|
|
764
|
+
else:
|
|
765
|
+
index = np.where(index < 0, index + size, index)
|
|
766
|
+
|
|
767
|
+
implied_shape.append(index_size)
|
|
768
|
+
implied_shape_positions.append(i)
|
|
769
|
+
|
|
770
|
+
parsed_indices[i] = index
|
|
771
|
+
|
|
772
|
+
return parsed_indices, implied_shape, reverse, implied_shape_positions
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
# ============================================================================
|
|
776
|
+
# setitem (chunk function)
|
|
777
|
+
# ============================================================================
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def setitem(x, v, indices):
|
|
781
|
+
"""Chunk function for array assignment.
|
|
782
|
+
|
|
783
|
+
Assign v to indices of x.
|
|
784
|
+
|
|
785
|
+
Parameters
|
|
786
|
+
----------
|
|
787
|
+
x : numpy/cupy/etc. array
|
|
788
|
+
The array to be assigned to.
|
|
789
|
+
v : numpy/cupy/etc. array
|
|
790
|
+
The values which will be assigned.
|
|
791
|
+
indices : list of `slice`, `int`, or numpy array
|
|
792
|
+
The indices describing the elements of x to be assigned from
|
|
793
|
+
v. One index per axis.
|
|
794
|
+
|
|
795
|
+
Note that an individual index can not be a `list`, use a 1-d
|
|
796
|
+
numpy array instead.
|
|
797
|
+
|
|
798
|
+
If a 1-d numpy array index contains the non-valid value of the
|
|
799
|
+
size of the corresponding dimension of x, then those index
|
|
800
|
+
elements will be removed prior to the assignment (see
|
|
801
|
+
`block_index_from_1d_index` function).
|
|
802
|
+
|
|
803
|
+
Returns
|
|
804
|
+
-------
|
|
805
|
+
numpy/cupy/etc. array
|
|
806
|
+
A new independent array with assigned elements, unless v is
|
|
807
|
+
empty (i.e. has zero size) in which case then the input array
|
|
808
|
+
is returned and the indices are ignored.
|
|
809
|
+
|
|
810
|
+
Examples
|
|
811
|
+
--------
|
|
812
|
+
>>> x = np.arange(8).reshape(2, 4)
|
|
813
|
+
>>> setitem(x, np.array(-99), [np.array([False, True])])
|
|
814
|
+
array([[ 0, 1, 2, 3],
|
|
815
|
+
[-99, -99, -99, -99]])
|
|
816
|
+
>>> x
|
|
817
|
+
array([[0, 1, 2, 3],
|
|
818
|
+
[4, 5, 6, 7]])
|
|
819
|
+
>>> setitem(x, np.array([-88, -99]), [slice(None), np.array([1, 3])])
|
|
820
|
+
array([[ 0, -88, 2, -99],
|
|
821
|
+
[ 4, -88, 6, -99]])
|
|
822
|
+
>>> setitem(x, -x, [slice(None)])
|
|
823
|
+
array([[ 0, -1, -2, -3],
|
|
824
|
+
[-4, -5, -6, -7]])
|
|
825
|
+
>>> x
|
|
826
|
+
array([[0, 1, 2, 3],
|
|
827
|
+
[4, 5, 6, 7]])
|
|
828
|
+
>>> setitem(x, np.array([-88, -99]), [slice(None), np.array([4, 4, 3, 4, 1, 4])])
|
|
829
|
+
array([[ 0, -99, 2, -88],
|
|
830
|
+
[ 4, -99, 6, -88]])
|
|
831
|
+
>>> value = np.where(x < 0)[0]
|
|
832
|
+
>>> value.size
|
|
833
|
+
0
|
|
834
|
+
>>> y = setitem(x, value, [Ellipsis])
|
|
835
|
+
>>> y is x
|
|
836
|
+
True
|
|
837
|
+
"""
|
|
838
|
+
if not math.prod(v.shape):
|
|
839
|
+
return x
|
|
840
|
+
|
|
841
|
+
# Normalize integer array indices
|
|
842
|
+
for i, (index, block_size) in enumerate(zip(indices, x.shape)):
|
|
843
|
+
if isinstance(index, np.ndarray) and index.dtype != bool:
|
|
844
|
+
# Strip out any non-valid place-holder values
|
|
845
|
+
index = index[np.where(index < block_size)[0]]
|
|
846
|
+
indices[i] = index
|
|
847
|
+
|
|
848
|
+
# If x is not masked but v is, then turn the x into a masked
|
|
849
|
+
# array.
|
|
850
|
+
if not np.ma.isMA(x) and np.ma.isMA(v):
|
|
851
|
+
x = x.view(np.ma.MaskedArray)
|
|
852
|
+
|
|
853
|
+
# Copy the array to guarantee no other objects are corrupted.
|
|
854
|
+
# When x is the output of a scalar __getitem__ call, it is a
|
|
855
|
+
# np.generic, which is read-only. Convert it to a (writeable)
|
|
856
|
+
# 0-d array. x could also be a cupy array etc.
|
|
857
|
+
x = np.asarray(x) if isinstance(x, np.generic) else x.copy()
|
|
858
|
+
|
|
859
|
+
# Do the assignment
|
|
860
|
+
try:
|
|
861
|
+
x[tuple(indices)] = v
|
|
862
|
+
except ValueError as e:
|
|
863
|
+
raise ValueError("shape mismatch: value array could not be broadcast to indexing result") from e
|
|
864
|
+
|
|
865
|
+
return x
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
# ============================================================================
|
|
869
|
+
# shuffle_slice and helpers
|
|
870
|
+
# ============================================================================
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def make_block_sorted_slices(index, chunks):
|
|
874
|
+
"""Generate blockwise-sorted index pairs for shuffling an array.
|
|
875
|
+
|
|
876
|
+
Parameters
|
|
877
|
+
----------
|
|
878
|
+
index : ndarray
|
|
879
|
+
An array of index positions.
|
|
880
|
+
chunks : tuple
|
|
881
|
+
Chunks from the original dask array
|
|
882
|
+
|
|
883
|
+
Returns
|
|
884
|
+
-------
|
|
885
|
+
index2 : ndarray
|
|
886
|
+
Same values as `index`, but each block has been sorted
|
|
887
|
+
index3 : ndarray
|
|
888
|
+
The location of the values of `index` in `index2`
|
|
889
|
+
|
|
890
|
+
Examples
|
|
891
|
+
--------
|
|
892
|
+
>>> index = np.array([6, 0, 4, 2, 7, 1, 5, 3])
|
|
893
|
+
>>> chunks = ((4, 4),)
|
|
894
|
+
>>> a, b = make_block_sorted_slices(index, chunks)
|
|
895
|
+
|
|
896
|
+
Notice that the first set of 4 items are sorted, and the
|
|
897
|
+
second set of 4 items are sorted.
|
|
898
|
+
|
|
899
|
+
>>> a
|
|
900
|
+
array([0, 2, 4, 6, 1, 3, 5, 7])
|
|
901
|
+
>>> b
|
|
902
|
+
array([3, 0, 2, 1, 7, 4, 6, 5])
|
|
903
|
+
"""
|
|
904
|
+
from dask_array._core_utils import slices_from_chunks
|
|
905
|
+
|
|
906
|
+
slices = slices_from_chunks(chunks)
|
|
907
|
+
|
|
908
|
+
if len(slices[0]) > 1:
|
|
909
|
+
slices = [slice_[0] for slice_ in slices]
|
|
910
|
+
|
|
911
|
+
offsets = np.roll(np.cumsum(chunks[0]), 1)
|
|
912
|
+
offsets[0] = 0
|
|
913
|
+
|
|
914
|
+
index2 = np.empty_like(index)
|
|
915
|
+
index3 = np.empty_like(index)
|
|
916
|
+
|
|
917
|
+
for slice_, offset in zip(slices, offsets):
|
|
918
|
+
a = index[slice_]
|
|
919
|
+
b = np.sort(a)
|
|
920
|
+
c = offset + np.argsort(b.take(np.argsort(a)))
|
|
921
|
+
index2[slice_] = b
|
|
922
|
+
index3[slice_] = c
|
|
923
|
+
|
|
924
|
+
return index2, index3
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
def shuffle_slice(x, index):
|
|
928
|
+
"""A relatively efficient way to shuffle `x` according to `index`.
|
|
929
|
+
|
|
930
|
+
Parameters
|
|
931
|
+
----------
|
|
932
|
+
x : Array
|
|
933
|
+
index : ndarray
|
|
934
|
+
This should be an ndarray the same length as `x` containing
|
|
935
|
+
each index position in ``range(0, len(x))``.
|
|
936
|
+
|
|
937
|
+
Returns
|
|
938
|
+
-------
|
|
939
|
+
Array
|
|
940
|
+
"""
|
|
941
|
+
chunks1 = chunks2 = x.chunks
|
|
942
|
+
if x.ndim > 1:
|
|
943
|
+
chunks1 = (chunks1[0],)
|
|
944
|
+
index2, index3 = make_block_sorted_slices(index, chunks1)
|
|
945
|
+
with warnings.catch_warnings():
|
|
946
|
+
warnings.simplefilter("ignore", category=Warning)
|
|
947
|
+
return x[index2].rechunk(chunks2)[index3]
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
# ============================================================================
|
|
951
|
+
# expander (used by some slice operations)
|
|
952
|
+
# ============================================================================
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
@memoize
|
|
956
|
+
def _expander(where):
|
|
957
|
+
if not where:
|
|
958
|
+
|
|
959
|
+
def expand(seq, val):
|
|
960
|
+
return seq
|
|
961
|
+
|
|
962
|
+
return expand
|
|
963
|
+
else:
|
|
964
|
+
decl = """def expand(seq, val):
|
|
965
|
+
return ({left}) + tuple({right})
|
|
966
|
+
"""
|
|
967
|
+
left = []
|
|
968
|
+
j = 0
|
|
969
|
+
for i in range(max(where) + 1):
|
|
970
|
+
if i in where:
|
|
971
|
+
left.append("val, ")
|
|
972
|
+
else:
|
|
973
|
+
left.append(f"seq[{j}], ")
|
|
974
|
+
j += 1
|
|
975
|
+
right = f"seq[{j}:]"
|
|
976
|
+
left = "".join(left)
|
|
977
|
+
decl = decl.format(**locals())
|
|
978
|
+
ns = {}
|
|
979
|
+
exec(compile(decl, "<dynamic>", "exec"), ns, ns)
|
|
980
|
+
return ns["expand"]
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
def expander(where):
|
|
984
|
+
"""Create a function to insert value at many locations in sequence.
|
|
985
|
+
|
|
986
|
+
Examples
|
|
987
|
+
--------
|
|
988
|
+
>>> expander([0, 2])(['a', 'b', 'c'], 'z')
|
|
989
|
+
('z', 'a', 'z', 'b', 'c')
|
|
990
|
+
"""
|
|
991
|
+
return _expander(tuple(where))
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
# ============================================================================
|
|
995
|
+
# fuse_slice and helpers
|
|
996
|
+
# ============================================================================
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
def _normalize_slice_for_fusion(s):
|
|
1000
|
+
"""Replace Nones in slices with integers for fusion.
|
|
1001
|
+
|
|
1002
|
+
Unlike normalize_slice which takes a dimension parameter, this version
|
|
1003
|
+
is used for slice fusion where we don't need the dimension.
|
|
1004
|
+
|
|
1005
|
+
>>> _normalize_slice_for_fusion(slice(None, None, None))
|
|
1006
|
+
slice(0, None, 1)
|
|
1007
|
+
"""
|
|
1008
|
+
start, stop, step = s.start, s.stop, s.step
|
|
1009
|
+
if start is None:
|
|
1010
|
+
start = 0
|
|
1011
|
+
if step is None:
|
|
1012
|
+
step = 1
|
|
1013
|
+
if start < 0 or step < 0 or stop is not None and stop < 0:
|
|
1014
|
+
raise NotImplementedError()
|
|
1015
|
+
return slice(start, stop, step)
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
def _check_for_nonfusible_fancy_indexing(fancy, normal):
|
|
1019
|
+
"""Check for fancy indexing and normal indexing conflicts.
|
|
1020
|
+
|
|
1021
|
+
Disallow things like:
|
|
1022
|
+
x[:, [1, 2], :][0, :, :] -> x[0, [1, 2], :] or
|
|
1023
|
+
x[0, :, :][:, [1, 2], :] -> x[0, [1, 2], :]
|
|
1024
|
+
"""
|
|
1025
|
+
from itertools import zip_longest
|
|
1026
|
+
|
|
1027
|
+
for f, n in zip_longest(fancy, normal, fillvalue=slice(None)):
|
|
1028
|
+
if type(f) is not list and isinstance(n, Integral):
|
|
1029
|
+
raise NotImplementedError(
|
|
1030
|
+
"Can't handle normal indexing with "
|
|
1031
|
+
"integers and fancy indexing if the "
|
|
1032
|
+
"integers and fancy indices don't "
|
|
1033
|
+
"align with the same dimensions."
|
|
1034
|
+
)
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
def fuse_slice(a, b):
|
|
1038
|
+
"""Fuse stacked slices together.
|
|
1039
|
+
|
|
1040
|
+
Fuse a pair of repeated slices into a single slice:
|
|
1041
|
+
|
|
1042
|
+
>>> fuse_slice(slice(1000, 2000), slice(10, 15))
|
|
1043
|
+
slice(1010, 1015, None)
|
|
1044
|
+
|
|
1045
|
+
This also works for tuples of slices
|
|
1046
|
+
|
|
1047
|
+
>>> fuse_slice((slice(100, 200), slice(100, 200, 10)),
|
|
1048
|
+
... (slice(10, 15), [5, 2]))
|
|
1049
|
+
(slice(110, 115, None), [150, 120])
|
|
1050
|
+
|
|
1051
|
+
And a variety of other interesting cases
|
|
1052
|
+
|
|
1053
|
+
>>> fuse_slice(slice(1000, 2000), 10) # integers
|
|
1054
|
+
1010
|
|
1055
|
+
|
|
1056
|
+
>>> fuse_slice(slice(1000, 2000, 5), slice(10, 20, 2))
|
|
1057
|
+
slice(1050, 1100, 10)
|
|
1058
|
+
|
|
1059
|
+
>>> fuse_slice(slice(1000, 2000, 5), [1, 2, 3]) # lists
|
|
1060
|
+
[1005, 1010, 1015]
|
|
1061
|
+
|
|
1062
|
+
>>> fuse_slice(None, slice(None, None)) # doctest: +SKIP
|
|
1063
|
+
None
|
|
1064
|
+
"""
|
|
1065
|
+
# None only works if the second side is a full slice
|
|
1066
|
+
if a is None and isinstance(b, slice) and b == slice(None, None):
|
|
1067
|
+
return None
|
|
1068
|
+
|
|
1069
|
+
# Replace None with 0 and one in start and step
|
|
1070
|
+
if isinstance(a, slice):
|
|
1071
|
+
a = _normalize_slice_for_fusion(a)
|
|
1072
|
+
if isinstance(b, slice):
|
|
1073
|
+
b = _normalize_slice_for_fusion(b)
|
|
1074
|
+
|
|
1075
|
+
if isinstance(a, slice) and isinstance(b, Integral):
|
|
1076
|
+
if b < 0:
|
|
1077
|
+
raise NotImplementedError()
|
|
1078
|
+
return a.start + b * a.step
|
|
1079
|
+
|
|
1080
|
+
if isinstance(a, slice) and isinstance(b, slice):
|
|
1081
|
+
start = a.start + a.step * b.start
|
|
1082
|
+
if b.stop is not None:
|
|
1083
|
+
stop = a.start + a.step * b.stop
|
|
1084
|
+
else:
|
|
1085
|
+
stop = None
|
|
1086
|
+
if a.stop is not None:
|
|
1087
|
+
if stop is not None:
|
|
1088
|
+
stop = min(a.stop, stop)
|
|
1089
|
+
else:
|
|
1090
|
+
stop = a.stop
|
|
1091
|
+
step = a.step * b.step
|
|
1092
|
+
if step == 1:
|
|
1093
|
+
step = None
|
|
1094
|
+
return slice(start, stop, step)
|
|
1095
|
+
|
|
1096
|
+
if isinstance(b, list):
|
|
1097
|
+
return [fuse_slice(a, bb) for bb in b]
|
|
1098
|
+
if isinstance(a, list) and isinstance(b, (Integral, slice)):
|
|
1099
|
+
return a[b]
|
|
1100
|
+
|
|
1101
|
+
if isinstance(a, tuple) and not isinstance(b, tuple):
|
|
1102
|
+
b = (b,)
|
|
1103
|
+
|
|
1104
|
+
# If given two tuples walk through both, being mindful of uneven sizes
|
|
1105
|
+
# and newaxes
|
|
1106
|
+
if isinstance(a, tuple) and isinstance(b, tuple):
|
|
1107
|
+
# Check for non-fusible cases with fancy-indexing
|
|
1108
|
+
a_has_lists = any(isinstance(item, list) for item in a)
|
|
1109
|
+
b_has_lists = any(isinstance(item, list) for item in b)
|
|
1110
|
+
if a_has_lists and b_has_lists:
|
|
1111
|
+
raise NotImplementedError("Can't handle multiple list indexing")
|
|
1112
|
+
elif a_has_lists:
|
|
1113
|
+
_check_for_nonfusible_fancy_indexing(a, b)
|
|
1114
|
+
elif b_has_lists:
|
|
1115
|
+
_check_for_nonfusible_fancy_indexing(b, a)
|
|
1116
|
+
|
|
1117
|
+
j = 0
|
|
1118
|
+
result = list()
|
|
1119
|
+
for i in range(len(a)):
|
|
1120
|
+
# axis ceased to exist or we're out of b
|
|
1121
|
+
if isinstance(a[i], Integral) or j == len(b):
|
|
1122
|
+
result.append(a[i])
|
|
1123
|
+
continue
|
|
1124
|
+
while b[j] is None: # insert any Nones on the rhs
|
|
1125
|
+
result.append(None)
|
|
1126
|
+
j += 1
|
|
1127
|
+
result.append(fuse_slice(a[i], b[j])) # Common case
|
|
1128
|
+
j += 1
|
|
1129
|
+
while j < len(b): # anything leftover on the right?
|
|
1130
|
+
result.append(b[j])
|
|
1131
|
+
j += 1
|
|
1132
|
+
return tuple(result)
|
|
1133
|
+
raise NotImplementedError()
|