array-split 0.6.5__zip
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.
- array_split-0.6.5/LICENSE.txt +16 -0
- array_split-0.6.5/PKG-INFO +255 -0
- array_split-0.6.5/README.rst +221 -0
- array_split-0.6.5/array_split/__init__.py +81 -0
- array_split-0.6.5/array_split/_version.py +21 -0
- array_split-0.6.5/array_split/copyright.txt +1 -0
- array_split-0.6.5/array_split/license.py +83 -0
- array_split-0.6.5/array_split/license.txt +14 -0
- array_split-0.6.5/array_split/logging.py +242 -0
- array_split-0.6.5/array_split/split.py +1647 -0
- array_split-0.6.5/array_split/split_plot.py +43 -0
- array_split-0.6.5/array_split/split_test.py +1517 -0
- array_split-0.6.5/array_split/tests.py +147 -0
- array_split-0.6.5/array_split/unittest.py +352 -0
- array_split-0.6.5/array_split.egg-info/PKG-INFO +255 -0
- array_split-0.6.5/array_split.egg-info/SOURCES.txt +22 -0
- array_split-0.6.5/array_split.egg-info/dependency_links.txt +1 -0
- array_split-0.6.5/array_split.egg-info/not-zip-safe +1 -0
- array_split-0.6.5/array_split.egg-info/requires.txt +1 -0
- array_split-0.6.5/array_split.egg-info/top_level.txt +1 -0
- array_split-0.6.5/pyproject.toml +48 -0
- array_split-0.6.5/setup.cfg +16 -0
- array_split-0.6.5/setup.py +14 -0
|
@@ -0,0 +1,1647 @@
|
|
|
1
|
+
"""
|
|
2
|
+
===================================
|
|
3
|
+
The :mod:`array_split.split` Module
|
|
4
|
+
===================================
|
|
5
|
+
|
|
6
|
+
.. currentmodule:: array_split.split
|
|
7
|
+
|
|
8
|
+
Defines array splitting functions and classes.
|
|
9
|
+
|
|
10
|
+
Classes and Functions
|
|
11
|
+
=====================
|
|
12
|
+
|
|
13
|
+
.. autosummary::
|
|
14
|
+
:toctree: generated/
|
|
15
|
+
|
|
16
|
+
shape_factors - Compute *largest* factors of a given integer.
|
|
17
|
+
calculate_num_slices_per_axis - Computes per-axis divisions for a multi-dimensional shape.
|
|
18
|
+
calculate_tile_shape_for_max_bytes - Calculate a tile shape subject to max bytes restriction.
|
|
19
|
+
convert_halo_to_array_form - converts halo argument to :samp:`(ndim, 2)` shaped array.
|
|
20
|
+
ShapeSplitter - Splits a given shape into slices.
|
|
21
|
+
shape_split - Splits a specified shape and returns :obj:`numpy.ndarray` of :obj:`slice` elements.
|
|
22
|
+
array_split - Equivalent to :func:`numpy.array_split`.
|
|
23
|
+
|
|
24
|
+
Attributes
|
|
25
|
+
==========
|
|
26
|
+
|
|
27
|
+
.. autodata:: ARRAY_BOUNDS
|
|
28
|
+
.. autodata:: NO_BOUNDS
|
|
29
|
+
|
|
30
|
+
Utilities
|
|
31
|
+
=========
|
|
32
|
+
|
|
33
|
+
.. autosummary::
|
|
34
|
+
:toctree: generated/
|
|
35
|
+
|
|
36
|
+
is_scalar - Return :samp:`True` if argument is numeric scalar.
|
|
37
|
+
is_sequence - Return :samp:`True` if argument is a sequence.
|
|
38
|
+
is_indices - Return :samp:`True` if argument is a sequence.
|
|
39
|
+
pad_with_object - End pads a sequence with specified object.
|
|
40
|
+
pad_with_none - End pads a sequence with :samp:`None` elements.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
from __future__ import absolute_import
|
|
44
|
+
import numpy as _np
|
|
45
|
+
from .license import license as _license, copyright as _copyright, version as _version
|
|
46
|
+
from . import logging as _logging
|
|
47
|
+
|
|
48
|
+
__copyright__ = _copyright()
|
|
49
|
+
__version__ = _version()
|
|
50
|
+
__author__ = "Shane J. Latham"
|
|
51
|
+
__license__ = _license()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def is_scalar(obj):
|
|
55
|
+
"""
|
|
56
|
+
Returns :samp:`True` if argument :samp:`{obj}` is
|
|
57
|
+
a numeric type.
|
|
58
|
+
|
|
59
|
+
:type obj: :obj:`object`
|
|
60
|
+
:param obj: Return :samp:`True` if this is a scalar.
|
|
61
|
+
:rtype: :obj:`bool`
|
|
62
|
+
:return: :samp:`True` if :samp:`{obj}` is a numeric scalar.
|
|
63
|
+
|
|
64
|
+
Example::
|
|
65
|
+
|
|
66
|
+
>>> is_scalar(5)
|
|
67
|
+
True
|
|
68
|
+
>>> is_scalar(2.0)
|
|
69
|
+
True
|
|
70
|
+
>>> import numpy as np
|
|
71
|
+
>>> is_scalar(np.ones((10,), dtype="uint16")[0])
|
|
72
|
+
True
|
|
73
|
+
>>> is_scalar([1, 2, 3])
|
|
74
|
+
False
|
|
75
|
+
>>> is_scalar([i for i in range(0, 3)])
|
|
76
|
+
False
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
return hasattr(obj, "__int__") or hasattr(obj, "__long__")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def is_sequence(obj):
|
|
83
|
+
"""
|
|
84
|
+
Returns :samp:`True` if argument :samp:`{obj}` is
|
|
85
|
+
a sequence (e.g. a :obj:`list` or :obj:`tuple`, etc).
|
|
86
|
+
|
|
87
|
+
:type obj: :obj:`object`
|
|
88
|
+
:param obj: Return :samp:`True` if this is a sequence.
|
|
89
|
+
:rtype: :obj:`bool`
|
|
90
|
+
:return: :samp:`True` if :samp:`{obj}` is a sequence.
|
|
91
|
+
|
|
92
|
+
Example::
|
|
93
|
+
|
|
94
|
+
>>> is_sequence([1, 2, 3])
|
|
95
|
+
True
|
|
96
|
+
>>> is_sequence([i for i in range(0, 3)])
|
|
97
|
+
True
|
|
98
|
+
>>> is_sequence(5)
|
|
99
|
+
False
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
return (
|
|
103
|
+
hasattr(obj, "__len__")
|
|
104
|
+
or
|
|
105
|
+
hasattr(obj, "__getitem__")
|
|
106
|
+
or
|
|
107
|
+
hasattr(obj, "__iter__")
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def is_indices(indices_or_sections):
|
|
112
|
+
"""
|
|
113
|
+
Test for the :samp:`{indices_or_sections}` argument of :meth:`ShapeSplitter.__init__`
|
|
114
|
+
to determine whether it is specifying *total number of tiles* or sequence of
|
|
115
|
+
*cut* indices. Returns :samp:`True` if argument :samp:`{indices_or_sections}` is
|
|
116
|
+
a sequence (e.g. a :obj:`list` or :obj:`tuple`, etc).
|
|
117
|
+
|
|
118
|
+
:type indices_or_sections: :obj:`object`
|
|
119
|
+
:param indices_or_sections: Return :samp:`True` if this is a sequence.
|
|
120
|
+
:rtype: :obj:`bool`
|
|
121
|
+
:return: :samp:`is_sequence({indices_or_sections})`.
|
|
122
|
+
"""
|
|
123
|
+
return is_sequence(indices_or_sections)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def pad_with_object(sequence, new_length, obj=None):
|
|
127
|
+
"""
|
|
128
|
+
Returns :samp:`sequence` :obj:`list` end-padded with :samp:`{obj}`
|
|
129
|
+
elements so that the length of the returned list equals :samp:`{new_length}`.
|
|
130
|
+
|
|
131
|
+
:type sequence: iterable
|
|
132
|
+
:param sequence: Return *listified* sequence which has been end-padded.
|
|
133
|
+
:type new_length: :obj:`int`
|
|
134
|
+
:param new_length: The length of the returned list.
|
|
135
|
+
:type obj: :obj:`object`
|
|
136
|
+
:param obj: Object used as padding elements.
|
|
137
|
+
:rtype: :obj:`list`
|
|
138
|
+
:return: A :obj:`list` of length :samp:`{new_length}`.
|
|
139
|
+
:raises ValueError: if :samp:`len({sequence}) > {new_length})`.
|
|
140
|
+
|
|
141
|
+
Example::
|
|
142
|
+
|
|
143
|
+
>>> pad_with_object([1, 2, 3], 5, obj=0)
|
|
144
|
+
[1, 2, 3, 0, 0]
|
|
145
|
+
>>> pad_with_object([1, 2, 3], 5, obj=None)
|
|
146
|
+
[1, 2, 3, None, None]
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
if len(sequence) < new_length:
|
|
150
|
+
sequence = \
|
|
151
|
+
list(sequence) + [obj, ] * (new_length - len(sequence))
|
|
152
|
+
elif len(sequence) > new_length:
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"Got len(sequence)=%s which exceeds new_length=%s"
|
|
155
|
+
%
|
|
156
|
+
(len(sequence), new_length)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return sequence
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def pad_with_none(sequence, new_length):
|
|
163
|
+
"""
|
|
164
|
+
Returns :samp:`sequence` :obj:`list` end-padded with :samp:`None`
|
|
165
|
+
elements so that the length of the returned list equals :samp:`{new_length}`.
|
|
166
|
+
|
|
167
|
+
:type sequence: iterable
|
|
168
|
+
:param sequence: Return *listified* sequence which has been end-padded.
|
|
169
|
+
:type new_length: :obj:`int`
|
|
170
|
+
:param new_length: The length of the returned list.
|
|
171
|
+
:rtype: :obj:`list`
|
|
172
|
+
:return: A :obj:`list` of length :samp:`{new_length}`.
|
|
173
|
+
:raises ValueError: if :samp:`len({sequence}) > {new_length})`.
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
return pad_with_object(sequence, new_length, obj=None)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def shape_factors(n, dim=2):
|
|
180
|
+
"""
|
|
181
|
+
Returns a :obj:`numpy.ndarray` of factors :samp:`f` such
|
|
182
|
+
that :samp:`(len(f) == {dim}) and (numpy.prod(f) == {n})`.
|
|
183
|
+
The returned factors are as *square* (*cubic*, etc) as possible.
|
|
184
|
+
For example::
|
|
185
|
+
|
|
186
|
+
>>> shape_factors(24, 1)
|
|
187
|
+
array([24])
|
|
188
|
+
>>> shape_factors(24, 2)
|
|
189
|
+
array([4, 6])
|
|
190
|
+
>>> shape_factors(24, 3)
|
|
191
|
+
array([2, 3, 4])
|
|
192
|
+
>>> shape_factors(24, 4)
|
|
193
|
+
array([2, 2, 2, 3])
|
|
194
|
+
>>> shape_factors(24, 5)
|
|
195
|
+
array([1, 2, 2, 2, 3])
|
|
196
|
+
>>> shape_factors(24, 6)
|
|
197
|
+
array([1, 1, 2, 2, 2, 3])
|
|
198
|
+
|
|
199
|
+
:type n: :obj:`int`
|
|
200
|
+
:param n: Integer which is factored into :samp:`{dim}` factors.
|
|
201
|
+
:type dim: :obj:`int`
|
|
202
|
+
:param dim: Number of factors.
|
|
203
|
+
:rtype: :obj:`numpy.ndarray`
|
|
204
|
+
:return: A :samp:`({dim},)` shaped array of integers which are factors of :samp:`{n}`.
|
|
205
|
+
"""
|
|
206
|
+
if dim <= 1:
|
|
207
|
+
factors = [n, ]
|
|
208
|
+
else:
|
|
209
|
+
for f in range(int(n ** (1.0 / float(dim))) + 1, 0, -1):
|
|
210
|
+
if (n % f) == 0:
|
|
211
|
+
factors = [f, ] + list(shape_factors(n // f, dim=dim - 1))
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
factors.sort()
|
|
215
|
+
return _np.array(factors)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def calculate_tile_shape_for_max_bytes(
|
|
219
|
+
array_shape,
|
|
220
|
+
array_itemsize,
|
|
221
|
+
max_tile_bytes,
|
|
222
|
+
max_tile_shape=None,
|
|
223
|
+
sub_tile_shape=None,
|
|
224
|
+
halo=None
|
|
225
|
+
):
|
|
226
|
+
"""
|
|
227
|
+
Returns a tile shape :samp:`tile_shape`
|
|
228
|
+
such that :samp:`numpy.prod(tile_shape)*numpy.sum({array_itemsize}) <= {max_tile_bytes}`.
|
|
229
|
+
Also, if :samp:`{max_tile_shape} is not None`
|
|
230
|
+
then :samp:`numpy.all(tile_shape <= {max_tile_shape}) is True` and
|
|
231
|
+
if :samp:`{sub_tile_shape} is not None`
|
|
232
|
+
the :samp:`numpy.all((tile_shape % {sub_tile_shape}) == 0) is True`.
|
|
233
|
+
|
|
234
|
+
:type array_shape: sequence of :obj:`int`
|
|
235
|
+
:param array_shape: Shape of the array which is to be split into tiles.
|
|
236
|
+
:type array_itemsize: :obj:`int`
|
|
237
|
+
:param array_itemsize: The number of bytes per element of the array to be tiled.
|
|
238
|
+
:type max_tile_bytes: :obj:`int`
|
|
239
|
+
:param max_tile_bytes: The maximum number of bytes for the returned :samp:`tile_shape`.
|
|
240
|
+
:type max_tile_shape: sequence of :obj:`int`
|
|
241
|
+
:param max_tile_shape: Per axis maximum shapes for the returned :samp:`tile_shape`.
|
|
242
|
+
:type sub_tile_shape: sequence of :obj:`int`
|
|
243
|
+
:param sub_tile_shape: The returned :samp:`tile_shape` will be an even multiple
|
|
244
|
+
of this sub-tile shape.
|
|
245
|
+
:type halo: :obj:`int`, sequence of :obj:`int`, or :samp:`(len({array_shape}), 2)`
|
|
246
|
+
shaped :obj:`numpy.ndarray`
|
|
247
|
+
:param halo: How tiles are extended in each axis direction with *halo*
|
|
248
|
+
elements. See :ref:`the-halo-parameter-examples` for meaning of :samp:`{halo}` values.
|
|
249
|
+
:rtype: :obj:`numpy.ndarray`
|
|
250
|
+
:return: A 1D array of shape :samp:`(len(array_shape),)` indicating a *tile shape*
|
|
251
|
+
which will (approximately) uniformly divide the given :samp:`{array_shape}` into
|
|
252
|
+
tiles (sub-arrays).
|
|
253
|
+
|
|
254
|
+
Examples::
|
|
255
|
+
|
|
256
|
+
>>> from array_split.split import calculate_tile_shape_for_max_bytes
|
|
257
|
+
>>> calculate_tile_shape_for_max_bytes(
|
|
258
|
+
... array_shape=[512,],
|
|
259
|
+
... array_itemsize=1,
|
|
260
|
+
... max_tile_bytes=512
|
|
261
|
+
... )
|
|
262
|
+
array([512])
|
|
263
|
+
>>> calculate_tile_shape_for_max_bytes(
|
|
264
|
+
... array_shape=[512,],
|
|
265
|
+
... array_itemsize=2, # Doubling the itemsize halves the tile size.
|
|
266
|
+
... max_tile_bytes=512
|
|
267
|
+
... )
|
|
268
|
+
array([256])
|
|
269
|
+
>>> calculate_tile_shape_for_max_bytes(
|
|
270
|
+
... array_shape=[512,],
|
|
271
|
+
... array_itemsize=1,
|
|
272
|
+
... max_tile_bytes=512-1 # tile shape will now be halved
|
|
273
|
+
... )
|
|
274
|
+
array([256])
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
logger = _logging.getLogger(__name__ + ".calculate_tile_shape_for_max_bytes")
|
|
280
|
+
logger.debug("calculate_tile_shape_for_max_bytes: enter:")
|
|
281
|
+
logger.debug("array_shape=%s", array_shape)
|
|
282
|
+
logger.debug("array_itemsize=%s", array_itemsize)
|
|
283
|
+
logger.debug("max_tile_bytes=%s", max_tile_bytes)
|
|
284
|
+
logger.debug("max_tile_shape=%s", max_tile_shape)
|
|
285
|
+
logger.debug("sub_tile_shape=%s", sub_tile_shape)
|
|
286
|
+
logger.debug("halo=%s", halo)
|
|
287
|
+
|
|
288
|
+
array_shape = _np.array(array_shape, dtype="int64")
|
|
289
|
+
array_itemsize = _np.sum(array_itemsize, dtype="int64")
|
|
290
|
+
|
|
291
|
+
if max_tile_shape is None:
|
|
292
|
+
max_tile_shape = _np.array(array_shape, copy=True)
|
|
293
|
+
max_tile_shape = \
|
|
294
|
+
_np.array(_np.minimum(max_tile_shape, array_shape), copy=True, dtype=array_shape.dtype)
|
|
295
|
+
|
|
296
|
+
if sub_tile_shape is None:
|
|
297
|
+
sub_tile_shape = _np.ones((len(array_shape),), dtype="int64")
|
|
298
|
+
|
|
299
|
+
sub_tile_shape = _np.array(sub_tile_shape, dtype="int64")
|
|
300
|
+
|
|
301
|
+
halo = convert_halo_to_array_form(halo=halo, ndim=len(array_shape))
|
|
302
|
+
|
|
303
|
+
if _np.any(array_shape < sub_tile_shape):
|
|
304
|
+
raise ValueError(
|
|
305
|
+
"Got array_shape=%s element less than corresponding sub_tile_shape=%s element."
|
|
306
|
+
%
|
|
307
|
+
(
|
|
308
|
+
array_shape,
|
|
309
|
+
sub_tile_shape
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
logger.debug("max_tile_shape=%s", max_tile_shape)
|
|
314
|
+
logger.debug("sub_tile_shape=%s", sub_tile_shape)
|
|
315
|
+
logger.debug("halo=%s", halo)
|
|
316
|
+
array_sub_tile_split_shape = ((array_shape - 1) // sub_tile_shape) + 1
|
|
317
|
+
tile_sub_tile_split_shape = array_shape // sub_tile_shape
|
|
318
|
+
if len(tile_sub_tile_split_shape) <= 1:
|
|
319
|
+
tile_sub_tile_split_shape[0] = \
|
|
320
|
+
int(_np.floor(
|
|
321
|
+
(
|
|
322
|
+
(max_tile_bytes / float(array_itemsize))
|
|
323
|
+
-
|
|
324
|
+
_np.sum(halo)
|
|
325
|
+
)
|
|
326
|
+
/
|
|
327
|
+
float(sub_tile_shape[0])
|
|
328
|
+
))
|
|
329
|
+
|
|
330
|
+
tile_sub_tile_split_shape = \
|
|
331
|
+
_np.minimum(
|
|
332
|
+
tile_sub_tile_split_shape,
|
|
333
|
+
max_tile_shape // sub_tile_shape
|
|
334
|
+
)
|
|
335
|
+
logger.debug("Pre loop: tile_sub_tile_split_shape=%s", tile_sub_tile_split_shape)
|
|
336
|
+
|
|
337
|
+
current_axis = 0
|
|
338
|
+
while (
|
|
339
|
+
(current_axis < len(tile_sub_tile_split_shape))
|
|
340
|
+
and
|
|
341
|
+
(
|
|
342
|
+
(
|
|
343
|
+
_np.prod(tile_sub_tile_split_shape * sub_tile_shape + _np.sum(halo, axis=1))
|
|
344
|
+
*
|
|
345
|
+
array_itemsize
|
|
346
|
+
)
|
|
347
|
+
>
|
|
348
|
+
max_tile_bytes
|
|
349
|
+
)
|
|
350
|
+
):
|
|
351
|
+
if current_axis < (len(tile_sub_tile_split_shape) - 1):
|
|
352
|
+
tile_sub_tile_split_shape[current_axis] = 1
|
|
353
|
+
tile_sub_tile_split_shape[current_axis] = \
|
|
354
|
+
(
|
|
355
|
+
max_tile_bytes
|
|
356
|
+
//
|
|
357
|
+
(
|
|
358
|
+
_np.prod(
|
|
359
|
+
tile_sub_tile_split_shape *
|
|
360
|
+
sub_tile_shape +
|
|
361
|
+
_np.sum(
|
|
362
|
+
halo,
|
|
363
|
+
axis=1))
|
|
364
|
+
*
|
|
365
|
+
array_itemsize
|
|
366
|
+
)
|
|
367
|
+
)
|
|
368
|
+
tile_sub_tile_split_shape[current_axis] = \
|
|
369
|
+
max([1, tile_sub_tile_split_shape[current_axis]])
|
|
370
|
+
else:
|
|
371
|
+
sub_tile_shape_h = sub_tile_shape.copy()
|
|
372
|
+
sub_tile_shape_h[0:current_axis] += _np.sum(halo[0:current_axis, :], axis=1)
|
|
373
|
+
tile_sub_tile_split_shape[current_axis] = \
|
|
374
|
+
int(_np.floor(
|
|
375
|
+
(
|
|
376
|
+
(max_tile_bytes / float(array_itemsize))
|
|
377
|
+
-
|
|
378
|
+
_np.sum(halo[current_axis]) * _np.prod(sub_tile_shape_h[0:current_axis])
|
|
379
|
+
)
|
|
380
|
+
/
|
|
381
|
+
float(_np.prod(sub_tile_shape_h))
|
|
382
|
+
))
|
|
383
|
+
current_axis += 1
|
|
384
|
+
|
|
385
|
+
logger.debug("Post loop: tile_sub_tile_split_shape=%s", tile_sub_tile_split_shape)
|
|
386
|
+
tile_shape = _np.minimum(array_shape, tile_sub_tile_split_shape * sub_tile_shape)
|
|
387
|
+
logger.debug("pre cannonicalise tile_shape=%s", tile_shape)
|
|
388
|
+
|
|
389
|
+
tile_split_shape = ((array_shape - 1) // tile_shape) + 1
|
|
390
|
+
logger.debug("tile_split_shape=%s", tile_split_shape)
|
|
391
|
+
|
|
392
|
+
tile_shape = (((array_sub_tile_split_shape - 1) // tile_split_shape) + 1) * sub_tile_shape
|
|
393
|
+
logger.debug("post cannonicalise tile_shape=%s", tile_shape)
|
|
394
|
+
|
|
395
|
+
return tile_shape
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def calculate_num_slices_per_axis(num_slices_per_axis, num_slices, max_slices_per_axis=None):
|
|
399
|
+
"""
|
|
400
|
+
Returns a :obj:`numpy.ndarray` (:samp:`return_array` say) where non-positive elements of
|
|
401
|
+
the :samp:`{num_slices_per_axis}` sequence have been replaced with
|
|
402
|
+
positive integer values such that :samp:`numpy.prod(return_array) == num_slices`
|
|
403
|
+
and::
|
|
404
|
+
|
|
405
|
+
numpy.all(
|
|
406
|
+
return_array[numpy.where(num_slices_per_axis <= 0)]
|
|
407
|
+
<=
|
|
408
|
+
max_slices_per_axis[numpy.where(num_slices_per_axis <= 0)]
|
|
409
|
+
) is True
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
:type num_slices_per_axis: sequence of :obj:`int`
|
|
413
|
+
:param num_slices_per_axis: Constraint for per-axis sub-divisions.
|
|
414
|
+
Non-positive elements indicate values to be replaced in the
|
|
415
|
+
returned array. Positive values are identical to the corresponding
|
|
416
|
+
element in the returned array.
|
|
417
|
+
:type num_slices: integer
|
|
418
|
+
:param num_slices: Indicates the number of slices (rectangular sub-arrays)
|
|
419
|
+
formed by performing sub-divisions per axis. The returned array :samp:`return_array`
|
|
420
|
+
has elements assigned such that :samp:`numpy.prod(return_array) == {num_slices}`.
|
|
421
|
+
:type max_slices_per_axis: sequence of :obj:`int` (or :samp:`None`)
|
|
422
|
+
:param max_slices_per_axis: Constraint specifying maximum number of per-axis sub-divisions.
|
|
423
|
+
If :samp:`None` defaults to :samp:`numpy.array([numpy.inf,]*len({num_slices_per_axis}))`.
|
|
424
|
+
:rtype: :obj:`numpy.ndarray`
|
|
425
|
+
:return: An array :samp:`return_array`
|
|
426
|
+
such that :samp:`numpy.prod(return_array) == num_slices`.
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
Examples::
|
|
430
|
+
|
|
431
|
+
>>> from array_split.split import calculate_num_slices_per_axis
|
|
432
|
+
>>>
|
|
433
|
+
>>> calculate_num_slices_per_axis([0, 0, 0], 16)
|
|
434
|
+
array([4, 2, 2])
|
|
435
|
+
>>> calculate_num_slices_per_axis([1, 0, 0], 16)
|
|
436
|
+
array([1, 4, 4])
|
|
437
|
+
>>> calculate_num_slices_per_axis([1, 0, 0], 16, [2, 2, 16])
|
|
438
|
+
array([1, 2, 8])
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
"""
|
|
442
|
+
logger = _logging.getLogger(__name__)
|
|
443
|
+
|
|
444
|
+
ret_array = _np.array(num_slices_per_axis, copy=True)
|
|
445
|
+
if max_slices_per_axis is None:
|
|
446
|
+
max_slices_per_axis = _np.array([_np.inf, ] * len(num_slices_per_axis))
|
|
447
|
+
|
|
448
|
+
max_slices_per_axis = _np.array(max_slices_per_axis)
|
|
449
|
+
|
|
450
|
+
if _np.any(max_slices_per_axis <= 0):
|
|
451
|
+
raise ValueError("Got non-positive value in max_slices_per_axis=%s" % max_slices_per_axis)
|
|
452
|
+
|
|
453
|
+
while _np.any(ret_array <= 0):
|
|
454
|
+
prd = _np.prod(ret_array[_np.where(ret_array > 0)]) # returns 1 for zero-length array
|
|
455
|
+
if (num_slices < prd) or ((num_slices % prd) > 0):
|
|
456
|
+
raise ValueError(
|
|
457
|
+
(
|
|
458
|
+
"Unable to construct grid of num_slices=%s elements from "
|
|
459
|
+
+
|
|
460
|
+
"num_slices_per_axis=%s (with max_slices_per_axis=%s)"
|
|
461
|
+
)
|
|
462
|
+
%
|
|
463
|
+
(num_slices, num_slices_per_axis, max_slices_per_axis)
|
|
464
|
+
)
|
|
465
|
+
ridx = _np.where(ret_array <= 0)
|
|
466
|
+
f = shape_factors(num_slices // prd, ridx[0].shape[0])[::-1]
|
|
467
|
+
if _np.all(f < max_slices_per_axis[ridx]):
|
|
468
|
+
ret_array[ridx] = f
|
|
469
|
+
else:
|
|
470
|
+
for i in range(ridx[0].shape[0]):
|
|
471
|
+
if f[i] >= max_slices_per_axis[ridx[0][i]]:
|
|
472
|
+
ret_array[ridx[0][i]] = max_slices_per_axis[ridx[0][i]]
|
|
473
|
+
prd = _np.prod(ret_array[_np.where(ret_array > 0)])
|
|
474
|
+
while (num_slices % prd) > 0:
|
|
475
|
+
ret_array[ridx[0][i]] -= 1
|
|
476
|
+
prd = _np.prod(ret_array[_np.where(ret_array > 0)])
|
|
477
|
+
logger.debug(
|
|
478
|
+
"ridx=%s, f=%s, ret_array=%s, max_slices_per_axis=%s",
|
|
479
|
+
ridx, f, ret_array, max_slices_per_axis
|
|
480
|
+
)
|
|
481
|
+
return ret_array
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
_array_shape_param_doc =\
|
|
485
|
+
"""
|
|
486
|
+
:type array_shape: sequence of :obj:`int`
|
|
487
|
+
:param array_shape: The shape to be *split*.
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
_array_start_param_doc =\
|
|
491
|
+
"""
|
|
492
|
+
:type array_start: :samp:`None` or sequence of :obj:`int`
|
|
493
|
+
:param array_start: The start index. Defaults to :samp:`[0,]*len(array_shape)`.
|
|
494
|
+
The array indexing extents are assumed to range from :samp:`{array_start}`
|
|
495
|
+
to :samp:`{array_start} + {array_shape}`.
|
|
496
|
+
See :ref:`the-array_start-parameter-examples` examples.
|
|
497
|
+
"""
|
|
498
|
+
_array_itemsize_param_doc =\
|
|
499
|
+
"""
|
|
500
|
+
:type array_itemsize: int or sequence of :obj:`int`
|
|
501
|
+
:param array_itemsize: Number of bytes per array element.
|
|
502
|
+
Only relevant when :samp:`{max_tile_bytes}` is specified.
|
|
503
|
+
See :ref:`splitting-by-maximum-bytes-per-tile-examples` examples.
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
_array_tile_bounds_policy_param_doc =\
|
|
507
|
+
"""
|
|
508
|
+
:type tile_bounds_policy: :obj:`str`
|
|
509
|
+
:param tile_bounds_policy: Specifies whether tiles can extend beyond the array boundaries.
|
|
510
|
+
Only relevant for halo values greater than one. If :samp:`{tile_bounds_policy}`
|
|
511
|
+
is :data:`ARRAY_BOUNDS`
|
|
512
|
+
then the calculated tiles will not extend beyond the array
|
|
513
|
+
extents :samp:`{array_start}` and :samp:`{array_start} + {array_shape}`.
|
|
514
|
+
If :samp:`{tile_bounds_policy}` is :data:`NO_BOUNDS`
|
|
515
|
+
then the returned tiles will extend beyond
|
|
516
|
+
the :samp:`{array_start}` and :samp:`{array_start} + {array_shape}` extend
|
|
517
|
+
for positive :samp:`{halo}` values. See :ref:`the-halo-parameter-examples` examples.
|
|
518
|
+
"""
|
|
519
|
+
|
|
520
|
+
_ShapeSplitter__init__params_doc =\
|
|
521
|
+
"""
|
|
522
|
+
:type indices_or_sections: :samp:`None`, :obj:`int` or sequence of :obj:`int`
|
|
523
|
+
:param indices_or_sections: If an integer, indicates the number of
|
|
524
|
+
elements in the calculated *split* array. If a sequence, indicates
|
|
525
|
+
the indices (per axis) at which the splits occur.
|
|
526
|
+
See :ref:`splitting-by-number-of-tiles-examples` examples.
|
|
527
|
+
:type axis: :samp:`None`, :obj:`int` or sequence of :obj:`int`
|
|
528
|
+
:param axis: If an integer, indicates the axis which is to be split.
|
|
529
|
+
If a sequence integers, indicates the number of slices per axis,
|
|
530
|
+
i.e. if :samp:`{axis} = [3, 5]` then axis :samp:`0` is cut into
|
|
531
|
+
3 slices and axis :samp:`1` is cut into 5 slices for a total
|
|
532
|
+
of 15 (:samp:`3*5`) rectangular slices in the returned :samp:`(3, 5)`
|
|
533
|
+
shaped split.
|
|
534
|
+
See :ref:`splitting-by-number-of-tiles-examples` examples
|
|
535
|
+
and :ref:`splitting-by-per-axis-split-indices-examples` examples.
|
|
536
|
+
%s%s
|
|
537
|
+
:type tile_shape: :samp:`None` or sequence of :obj:`int`
|
|
538
|
+
:param tile_shape: When not :samp:`None`, specifies explicit shape for tiles.
|
|
539
|
+
Should be same length as :samp:`{array_shape}`.
|
|
540
|
+
See :ref:`splitting-by-tile-shape-examples` examples.
|
|
541
|
+
:type max_tile_bytes: :samp:`None` or :obj:`int`
|
|
542
|
+
:param max_tile_bytes: The maximum number of bytes for calculated :samp:`tile_shape`.
|
|
543
|
+
See :ref:`splitting-by-maximum-bytes-per-tile-examples` examples.
|
|
544
|
+
:type max_tile_shape: :samp:`None` or sequence of :obj:`int`
|
|
545
|
+
:param max_tile_shape: Per axis maximum shapes for the calculated :samp:`tile_shape`.
|
|
546
|
+
Only relevant when :samp:`{max_tile_bytes}` is specified. Should be same length
|
|
547
|
+
as :samp:`{array_shape}`.
|
|
548
|
+
See :ref:`splitting-by-maximum-bytes-per-tile-examples` examples.
|
|
549
|
+
:type sub_tile_shape: :samp:`None` or sequence of :obj:`int`
|
|
550
|
+
:param sub_tile_shape: When not :samp:`None`, the calculated :samp:`tile_shape` will
|
|
551
|
+
be an even multiple of this sub-tile shape. Only relevant when :samp:`{max_tile_bytes}`
|
|
552
|
+
is specified. Should be same length as :samp:`{array_shape}`.
|
|
553
|
+
See :ref:`splitting-by-maximum-bytes-per-tile-examples` examples.%s%s
|
|
554
|
+
"""
|
|
555
|
+
_halo_param_doc =\
|
|
556
|
+
"""
|
|
557
|
+
:type halo: :samp:`None`, :obj:`int`, sequence of :obj:`int`, or :samp:`(len({array_shape}), 2)`
|
|
558
|
+
shaped :obj:`numpy.ndarray`
|
|
559
|
+
:param halo: How tiles are extended per axis in -ve and +ve directions with *halo*
|
|
560
|
+
elements. See :ref:`the-halo-parameter-examples` examples.
|
|
561
|
+
"""
|
|
562
|
+
|
|
563
|
+
#: Indicates that tiles are always within the array bounds.
|
|
564
|
+
#: See :ref:`the-halo-parameter-examples` examples.
|
|
565
|
+
__ARRAY_BOUNDS = "array_bounds"
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
@property
|
|
569
|
+
def ARRAY_BOUNDS(): # pylint: disable=invalid-name
|
|
570
|
+
"""
|
|
571
|
+
Indicates that tiles are always within the array bounds,
|
|
572
|
+
resulting in tiles which have truncated halos.
|
|
573
|
+
See :ref:`the-halo-parameter-examples` examples.
|
|
574
|
+
"""
|
|
575
|
+
return __ARRAY_BOUNDS
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
#: Indicates that tiles may extend beyond the array bounds.
|
|
579
|
+
#: See :ref:`the-halo-parameter-examples` examples.
|
|
580
|
+
__NO_BOUNDS = "no_bounds"
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
@property
|
|
584
|
+
def NO_BOUNDS(): # pylint: disable=invalid-name
|
|
585
|
+
"""
|
|
586
|
+
Indicates that tiles may have halos which extend beyond the array bounds.
|
|
587
|
+
See :ref:`the-halo-parameter-examples` examples.
|
|
588
|
+
"""
|
|
589
|
+
return __NO_BOUNDS
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def convert_halo_to_array_form(halo, ndim):
|
|
593
|
+
"""
|
|
594
|
+
Converts the :samp:`{halo}` argument to a :samp:`(ndim, 2)`
|
|
595
|
+
shaped array.
|
|
596
|
+
|
|
597
|
+
:type halo: :samp:`None`, :obj:`int`, an :samp:`{ndim}` length sequence
|
|
598
|
+
of :samp:`int` or :samp:`({ndim}, 2)` shaped array
|
|
599
|
+
of :samp:`int`
|
|
600
|
+
:param halo: Halo to be converted to :samp:`({ndim}, 2)` shaped array form.
|
|
601
|
+
:type ndim: :obj:`int`
|
|
602
|
+
:param ndim: Number of dimensions.
|
|
603
|
+
:rtype: :obj:`numpy.ndarray`
|
|
604
|
+
:return: A :samp:`({ndim}, 2)` shaped array of :obj:`numpy.int64` elements.
|
|
605
|
+
|
|
606
|
+
Examples::
|
|
607
|
+
|
|
608
|
+
>>> convert_halo_to_array_form(halo=2, ndim=4)
|
|
609
|
+
array([[2, 2],
|
|
610
|
+
[2, 2],
|
|
611
|
+
[2, 2],
|
|
612
|
+
[2, 2]])
|
|
613
|
+
>>> convert_halo_to_array_form(halo=[0, 1, 2], ndim=3)
|
|
614
|
+
array([[0, 0],
|
|
615
|
+
[1, 1],
|
|
616
|
+
[2, 2]])
|
|
617
|
+
>>> convert_halo_to_array_form(halo=[[0, 1], [2, 3], [3, 4]], ndim=3)
|
|
618
|
+
array([[0, 1],
|
|
619
|
+
[2, 3],
|
|
620
|
+
[3, 4]])
|
|
621
|
+
|
|
622
|
+
"""
|
|
623
|
+
dtyp = _np.int64
|
|
624
|
+
if halo is None:
|
|
625
|
+
halo = _np.zeros((ndim, 2), dtype=dtyp)
|
|
626
|
+
elif is_scalar(halo):
|
|
627
|
+
halo = _np.zeros((ndim, 2), dtype=dtyp) + halo
|
|
628
|
+
elif (ndim == 1) and (_np.array(halo).shape == (2,)):
|
|
629
|
+
halo = _np.array([halo, ], copy=True, dtype=dtyp)
|
|
630
|
+
elif len(_np.array(halo).shape) == 1:
|
|
631
|
+
halo = _np.array([halo, halo], dtype=dtyp).T.copy()
|
|
632
|
+
else:
|
|
633
|
+
halo = _np.array(halo, copy=True, dtype=dtyp)
|
|
634
|
+
|
|
635
|
+
if halo.shape[0] != ndim:
|
|
636
|
+
raise ValueError(
|
|
637
|
+
"Got halo.shape=%s, expecting halo.shape=(%s, 2)"
|
|
638
|
+
%
|
|
639
|
+
(halo.shape, ndim)
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
return halo
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
class ShapeSplitter(object):
|
|
646
|
+
|
|
647
|
+
"""
|
|
648
|
+
Implements array shape splitting. There are three main (top-level) methods:
|
|
649
|
+
|
|
650
|
+
:meth:`__init__`
|
|
651
|
+
Initialisation of parameters which define the split.
|
|
652
|
+
:meth:`set_split_extents`
|
|
653
|
+
Calculates the per-axis indices for the cuts. Sets
|
|
654
|
+
the :attr:`split_shape`, :attr:`split_begs`
|
|
655
|
+
and :attr:`split_ends` attributes.
|
|
656
|
+
:meth:`calculate_split`
|
|
657
|
+
Calls :meth:`set_split_extents` followed
|
|
658
|
+
by :meth:`calculate_split_from_extents` to
|
|
659
|
+
return the :obj:`numpy.ndarray` of :obj:`tuple` elements (slices).
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
Example::
|
|
663
|
+
|
|
664
|
+
>>> import numpy as np
|
|
665
|
+
>>> ary = np.arange(0, 10)
|
|
666
|
+
>>> splitter = ShapeSplitter(ary.shape, 3)
|
|
667
|
+
>>> split = splitter.calculate_split()
|
|
668
|
+
>>> split.shape
|
|
669
|
+
(3,)
|
|
670
|
+
>>> split
|
|
671
|
+
array([(slice(0, 4, None),), (slice(4, 7, None),), (slice(7, 10, None),)],
|
|
672
|
+
dtype=[('0', 'O')])
|
|
673
|
+
>>> [ary[slyce] for slyce in split.flatten().tolist()]
|
|
674
|
+
[array([0, 1, 2, 3]), array([4, 5, 6]), array([7, 8, 9])]
|
|
675
|
+
>>>
|
|
676
|
+
>>> splitter.split_shape # equivalent to split.shape above
|
|
677
|
+
array([3])
|
|
678
|
+
>>> splitter.split_begs # start indices for tile extents
|
|
679
|
+
[array([0, 4, 7])]
|
|
680
|
+
>>> splitter.split_ends # stop indices for tile extents
|
|
681
|
+
[array([ 4, 7, 10])]
|
|
682
|
+
|
|
683
|
+
"""
|
|
684
|
+
|
|
685
|
+
#: Class attribute for :obj:`logging.Logger` logging.
|
|
686
|
+
logger = _logging.getLogger(__name__ + ".ShapeSplitter")
|
|
687
|
+
|
|
688
|
+
#: Class attribute indicating list of valid values for :attr:`tile_bound_policy`.
|
|
689
|
+
#: See :data:`ARRAY_BOUNDS` and :data:`NO_BOUNDS`.
|
|
690
|
+
valid_tile_bounds_policies = [ARRAY_BOUNDS, NO_BOUNDS]
|
|
691
|
+
|
|
692
|
+
def __init__(
|
|
693
|
+
self,
|
|
694
|
+
array_shape,
|
|
695
|
+
indices_or_sections=None,
|
|
696
|
+
axis=None,
|
|
697
|
+
array_start=None,
|
|
698
|
+
array_itemsize=1,
|
|
699
|
+
tile_shape=None,
|
|
700
|
+
max_tile_bytes=None,
|
|
701
|
+
max_tile_shape=None,
|
|
702
|
+
sub_tile_shape=None,
|
|
703
|
+
halo=None,
|
|
704
|
+
tile_bounds_policy=ARRAY_BOUNDS
|
|
705
|
+
):
|
|
706
|
+
# Initialise *private* attributes.
|
|
707
|
+
self.__array_shape = None
|
|
708
|
+
self.__array_start = None
|
|
709
|
+
self.__array_itemsize = None
|
|
710
|
+
self.__indices_per_axis = None
|
|
711
|
+
self.__split_size = None
|
|
712
|
+
self.__split_num_slices_per_axis = None
|
|
713
|
+
self.__tile_shape = None
|
|
714
|
+
self.__max_tile_bytes = None
|
|
715
|
+
self.__max_tile_shape = None
|
|
716
|
+
self.__sub_tile_shape = None
|
|
717
|
+
self.__halo = None
|
|
718
|
+
self.__tile_bounds_policy = None
|
|
719
|
+
self.__tile_beg_min = None
|
|
720
|
+
self.__tile_end_max = None
|
|
721
|
+
self.__split_shape = None
|
|
722
|
+
self.__split_begs = None
|
|
723
|
+
self.__split_ends = None
|
|
724
|
+
|
|
725
|
+
# Now set properties from arguments
|
|
726
|
+
self.array_shape = _np.array(array_shape)
|
|
727
|
+
|
|
728
|
+
if array_start is None:
|
|
729
|
+
array_start = _np.zeros_like(self.array_shape)
|
|
730
|
+
|
|
731
|
+
self.array_start = array_start
|
|
732
|
+
|
|
733
|
+
self.array_itemsize = array_itemsize
|
|
734
|
+
|
|
735
|
+
indices_per_axis = None
|
|
736
|
+
if is_indices(indices_or_sections):
|
|
737
|
+
num_subarrays = None
|
|
738
|
+
indices_per_axis = indices_or_sections
|
|
739
|
+
if (
|
|
740
|
+
((axis is None) or is_scalar(axis))
|
|
741
|
+
and
|
|
742
|
+
(not _np.any([is_sequence(_e) for _e in indices_or_sections]))
|
|
743
|
+
):
|
|
744
|
+
if axis is None:
|
|
745
|
+
axis = 0
|
|
746
|
+
# Make indices_per_axis a list of lists, so that
|
|
747
|
+
# element 0 is a list of indices for axis 0
|
|
748
|
+
indices_per_axis = [None, ] * len(array_shape)
|
|
749
|
+
indices_per_axis[axis] = indices_or_sections
|
|
750
|
+
else:
|
|
751
|
+
indices_per_axis = None
|
|
752
|
+
num_subarrays = indices_or_sections
|
|
753
|
+
|
|
754
|
+
self.indices_per_axis = indices_per_axis
|
|
755
|
+
|
|
756
|
+
self.split_size = num_subarrays
|
|
757
|
+
split_num_slices_per_axis = None
|
|
758
|
+
if (self.split_size is not None) or (axis is not None):
|
|
759
|
+
if axis is None:
|
|
760
|
+
axis = 0
|
|
761
|
+
if is_sequence(axis):
|
|
762
|
+
split_num_slices_per_axis = pad_with_object(axis, len(self.array_shape), 1)
|
|
763
|
+
elif self.split_size is not None:
|
|
764
|
+
split_num_slices_per_axis = pad_with_object([], len(self.array_shape), 1)
|
|
765
|
+
split_num_slices_per_axis[axis] = self.split_size
|
|
766
|
+
|
|
767
|
+
self.split_num_slices_per_axis = split_num_slices_per_axis
|
|
768
|
+
|
|
769
|
+
self.tile_shape = tile_shape
|
|
770
|
+
|
|
771
|
+
self.max_tile_bytes = max_tile_bytes
|
|
772
|
+
|
|
773
|
+
self.max_tile_shape = max_tile_shape
|
|
774
|
+
|
|
775
|
+
self.sub_tile_shape = sub_tile_shape
|
|
776
|
+
|
|
777
|
+
halo = self.convert_halo_to_array_form(halo)
|
|
778
|
+
self.halo = halo
|
|
779
|
+
|
|
780
|
+
if tile_bounds_policy is None:
|
|
781
|
+
tile_bounds_policy = ARRAY_BOUNDS
|
|
782
|
+
|
|
783
|
+
self.tile_bounds_policy = tile_bounds_policy
|
|
784
|
+
|
|
785
|
+
self.tile_beg_min = self.array_start
|
|
786
|
+
|
|
787
|
+
self.tile_end_max = self.array_start + self.array_shape
|
|
788
|
+
|
|
789
|
+
self.split_shape = None
|
|
790
|
+
|
|
791
|
+
self.split_begs = None
|
|
792
|
+
|
|
793
|
+
self.split_ends = None
|
|
794
|
+
|
|
795
|
+
def convert_halo_to_array_form(self, halo):
|
|
796
|
+
"""
|
|
797
|
+
Converts the :samp:`{halo}` argument to a :samp:`({self}.array_shape.size, 2)`
|
|
798
|
+
shaped array.
|
|
799
|
+
|
|
800
|
+
:type halo: :samp:`None`, :obj:`int`, :samp:`self.array_shape.size` length sequence
|
|
801
|
+
of :samp:`int` or :samp:`(self.array_shape.size, 2)` shaped array
|
|
802
|
+
of :samp:`int`
|
|
803
|
+
:param halo: Halo to be converted to :samp:`(len(self.array_shape), 2)` shaped array form.
|
|
804
|
+
:rtype: :obj:`numpy.ndarray`
|
|
805
|
+
:return: A :samp:`(len(self.array_shape), 2)` shaped array of :obj:`numpy.int64` elements.
|
|
806
|
+
"""
|
|
807
|
+
return convert_halo_to_array_form(halo=halo, ndim=len(self.array_shape))
|
|
808
|
+
|
|
809
|
+
@property
|
|
810
|
+
def array_shape(self):
|
|
811
|
+
"""
|
|
812
|
+
The shape of the array which is to be split. A sequence of :obj:`int` indicating the
|
|
813
|
+
per-axis sizes which are to be split.
|
|
814
|
+
"""
|
|
815
|
+
return self.__array_shape
|
|
816
|
+
|
|
817
|
+
@array_shape.setter
|
|
818
|
+
def array_shape(self, array_shape):
|
|
819
|
+
self.__array_shape = array_shape
|
|
820
|
+
|
|
821
|
+
@property
|
|
822
|
+
def array_start(self):
|
|
823
|
+
"""
|
|
824
|
+
The start index. A sequence of :obj:`int` indicating the start of indexing for
|
|
825
|
+
the tile slices. Defaults to :samp:`numpy.zeros_like({self}.array_shape)`.
|
|
826
|
+
"""
|
|
827
|
+
return self.__array_start
|
|
828
|
+
|
|
829
|
+
@array_start.setter
|
|
830
|
+
def array_start(self, array_start):
|
|
831
|
+
self.__array_start = array_start
|
|
832
|
+
|
|
833
|
+
@property
|
|
834
|
+
def array_itemsize(self):
|
|
835
|
+
"""
|
|
836
|
+
The number of bytes per array element, see :attr:`max_tile_bytes`.
|
|
837
|
+
"""
|
|
838
|
+
return self.__array_itemsize
|
|
839
|
+
|
|
840
|
+
@array_itemsize.setter
|
|
841
|
+
def array_itemsize(self, array_itemsize):
|
|
842
|
+
self.__array_itemsize = array_itemsize
|
|
843
|
+
|
|
844
|
+
@property
|
|
845
|
+
def indices_per_axis(self):
|
|
846
|
+
"""
|
|
847
|
+
The per-axis indices indicating the cuts for the split.
|
|
848
|
+
A :obj:`list` of 1D :obj:`numpy.ndarray` objects such
|
|
849
|
+
that :samp:`{self}.indices_per_axis[i]` indicates the
|
|
850
|
+
cut positions for axis :samp:`i`.
|
|
851
|
+
"""
|
|
852
|
+
return self.__indices_per_axis
|
|
853
|
+
|
|
854
|
+
@indices_per_axis.setter
|
|
855
|
+
def indices_per_axis(self, indices_per_axis):
|
|
856
|
+
self.__indices_per_axis = indices_per_axis
|
|
857
|
+
|
|
858
|
+
@property
|
|
859
|
+
def split_size(self):
|
|
860
|
+
"""
|
|
861
|
+
An :obj:`int` indicating the number of tiles in the calculated split.
|
|
862
|
+
"""
|
|
863
|
+
return self.__split_size
|
|
864
|
+
|
|
865
|
+
@split_size.setter
|
|
866
|
+
def split_size(self, split_size):
|
|
867
|
+
self.__split_size = split_size
|
|
868
|
+
|
|
869
|
+
@property
|
|
870
|
+
def split_num_slices_per_axis(self):
|
|
871
|
+
"""
|
|
872
|
+
Number of slices per axis.
|
|
873
|
+
A 1D :obj:`numpy.ndarray` of :obj:`int` indicating the number of slices (sections)
|
|
874
|
+
per axis, so that :samp:`{self}.split_num_slices_per_axis[i]` is an integer
|
|
875
|
+
indicating the number of sections along axis :samp:`i` in the calculated split.
|
|
876
|
+
"""
|
|
877
|
+
return self.__split_num_slices_per_axis
|
|
878
|
+
|
|
879
|
+
@split_num_slices_per_axis.setter
|
|
880
|
+
def split_num_slices_per_axis(self, split_num_slices_per_axis):
|
|
881
|
+
self.__split_num_slices_per_axis = split_num_slices_per_axis
|
|
882
|
+
|
|
883
|
+
@property
|
|
884
|
+
def tile_shape(self):
|
|
885
|
+
"""
|
|
886
|
+
The shape of all tiles in the calculated split.
|
|
887
|
+
A 1D :samp:`numpy.ndarray` of :obj:`int` indicating the per-axis
|
|
888
|
+
number of elements for tiles in the calculated split.
|
|
889
|
+
"""
|
|
890
|
+
return self.__tile_shape
|
|
891
|
+
|
|
892
|
+
@tile_shape.setter
|
|
893
|
+
def tile_shape(self, tile_shape):
|
|
894
|
+
self.__tile_shape = tile_shape
|
|
895
|
+
|
|
896
|
+
@property
|
|
897
|
+
def max_tile_bytes(self):
|
|
898
|
+
"""
|
|
899
|
+
The maximum number of bytes for any tile (including :attr:`halo`) in the returned split.
|
|
900
|
+
An :obj:`int` which constrains the tile shape such that any tile
|
|
901
|
+
from the computed split is no bigger than :samp:`{max_tile_bytes}`.
|
|
902
|
+
"""
|
|
903
|
+
return self.__max_tile_bytes
|
|
904
|
+
|
|
905
|
+
@max_tile_bytes.setter
|
|
906
|
+
def max_tile_bytes(self, max_tile_bytes):
|
|
907
|
+
self.__max_tile_bytes = max_tile_bytes
|
|
908
|
+
|
|
909
|
+
@property
|
|
910
|
+
def max_tile_shape(self):
|
|
911
|
+
"""
|
|
912
|
+
Per-axis maximum sizes for calculated tiles.
|
|
913
|
+
A 1D :samp:`numpy.ndarray` of :obj:`int` indicating the per-axis
|
|
914
|
+
maximum number of elements for tiles in the calculated split.
|
|
915
|
+
"""
|
|
916
|
+
return self.__max_tile_shape
|
|
917
|
+
|
|
918
|
+
@max_tile_shape.setter
|
|
919
|
+
def max_tile_shape(self, max_tile_shape):
|
|
920
|
+
self.__max_tile_shape = max_tile_shape
|
|
921
|
+
|
|
922
|
+
@property
|
|
923
|
+
def sub_tile_shape(self):
|
|
924
|
+
"""
|
|
925
|
+
Calculated tile shape will be an integer multiple of this sub-tile shape.
|
|
926
|
+
i.e. :samp:`(self.tile_shape[i] % self.sub_tile_shape[i]) == 0`,
|
|
927
|
+
for :samp:`i in range(0, len(self.tile_shape))`.
|
|
928
|
+
A 1D :samp:`numpy.ndarray` of :obj:`int` indicating sub-tile shape.
|
|
929
|
+
"""
|
|
930
|
+
return self.__sub_tile_shape
|
|
931
|
+
|
|
932
|
+
@sub_tile_shape.setter
|
|
933
|
+
def sub_tile_shape(self, sub_tile_shape):
|
|
934
|
+
self.__sub_tile_shape = sub_tile_shape
|
|
935
|
+
|
|
936
|
+
@property
|
|
937
|
+
def halo(self):
|
|
938
|
+
"""
|
|
939
|
+
Per-axis -ve and +ve halo sizes for extending tiles to overlap with neighbouring tiles.
|
|
940
|
+
A :samp:`(N, 2)` shaped array indicating the
|
|
941
|
+
"""
|
|
942
|
+
return self.__halo
|
|
943
|
+
|
|
944
|
+
@halo.setter
|
|
945
|
+
def halo(self, halo):
|
|
946
|
+
self.__halo = convert_halo_to_array_form(halo, ndim=self.array_shape.size)
|
|
947
|
+
|
|
948
|
+
@property
|
|
949
|
+
def tile_bounds_policy(self):
|
|
950
|
+
"""
|
|
951
|
+
A string indicating whether tile halo extents can extend beyond the array domain.
|
|
952
|
+
Valid values are indicated by :attr:`valid_tile_bounds_policies`.
|
|
953
|
+
"""
|
|
954
|
+
return self.__tile_bounds_policy
|
|
955
|
+
|
|
956
|
+
@tile_bounds_policy.setter
|
|
957
|
+
def tile_bounds_policy(self, tile_bounds_policy):
|
|
958
|
+
self.__tile_bounds_policy = tile_bounds_policy
|
|
959
|
+
|
|
960
|
+
@property
|
|
961
|
+
def tile_beg_min(self):
|
|
962
|
+
"""
|
|
963
|
+
The per-axis minimum index for :attr:`slice.start`. The per-axis lower bound for
|
|
964
|
+
tile start indices. A 1D :obj:`numpy.ndarray`.
|
|
965
|
+
"""
|
|
966
|
+
return self.__tile_beg_min
|
|
967
|
+
|
|
968
|
+
@tile_beg_min.setter
|
|
969
|
+
def tile_beg_min(self, tile_beg_min):
|
|
970
|
+
self.__tile_beg_min = tile_beg_min
|
|
971
|
+
|
|
972
|
+
@property
|
|
973
|
+
def tile_end_max(self):
|
|
974
|
+
"""
|
|
975
|
+
The per-axis maximum index for :attr:`slice.stop`. The per-axis upper bound for
|
|
976
|
+
tile stop indices. A 1D :obj:`numpy.ndarray`.
|
|
977
|
+
"""
|
|
978
|
+
return self.__tile_end_max
|
|
979
|
+
|
|
980
|
+
@tile_end_max.setter
|
|
981
|
+
def tile_end_max(self, tile_end_max):
|
|
982
|
+
self.__tile_end_max = tile_end_max
|
|
983
|
+
|
|
984
|
+
@property
|
|
985
|
+
def split_shape(self):
|
|
986
|
+
"""
|
|
987
|
+
The shape of the calculated split array. Indicates the per-axis number
|
|
988
|
+
of sections in the calculated split. A 1D :obj:`numpy.ndarray`.
|
|
989
|
+
"""
|
|
990
|
+
return self.__split_shape
|
|
991
|
+
|
|
992
|
+
@split_shape.setter
|
|
993
|
+
def split_shape(self, split_shape):
|
|
994
|
+
self.__split_shape = split_shape
|
|
995
|
+
|
|
996
|
+
@property
|
|
997
|
+
def split_begs(self):
|
|
998
|
+
"""
|
|
999
|
+
The list of per-axis start indices for :obj:`slice` objects.
|
|
1000
|
+
A :obj:`list` of 1D :obj:`numpy.ndarray` objects indicating
|
|
1001
|
+
the :attr:`slice.start` index for for tiles.
|
|
1002
|
+
"""
|
|
1003
|
+
return self.__split_begs
|
|
1004
|
+
|
|
1005
|
+
@split_begs.setter
|
|
1006
|
+
def split_begs(self, split_begs):
|
|
1007
|
+
self.__split_begs = split_begs
|
|
1008
|
+
|
|
1009
|
+
@property
|
|
1010
|
+
def split_ends(self):
|
|
1011
|
+
"""
|
|
1012
|
+
The list of per-axis stop indices for :obj:`slice` objects.
|
|
1013
|
+
A :obj:`list` of 1D :obj:`numpy.ndarray` objects indicating
|
|
1014
|
+
the :attr:`slice.stop` index for for tiles.
|
|
1015
|
+
"""
|
|
1016
|
+
return self.__split_ends
|
|
1017
|
+
|
|
1018
|
+
@split_ends.setter
|
|
1019
|
+
def split_ends(self, split_ends):
|
|
1020
|
+
self.__split_ends = split_ends
|
|
1021
|
+
|
|
1022
|
+
def check_tile_bounds_policy(self):
|
|
1023
|
+
"""
|
|
1024
|
+
Raises :obj:`ValueError` if :attr:`tile_bounds_policy`
|
|
1025
|
+
is not in :samp:`[{self}.ARRAY_BOUNDS, {self}.NO_BOUNDS]`.
|
|
1026
|
+
"""
|
|
1027
|
+
if self.tile_bounds_policy not in self.valid_tile_bounds_policies:
|
|
1028
|
+
raise ValueError(
|
|
1029
|
+
"Got self.tile_bounds_policy=%s, which is not in %s."
|
|
1030
|
+
%
|
|
1031
|
+
(self.tile_bounds_policy, self.valid_tile_bounds_policies)
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
def check_consistent_parameter_dimensions(self):
|
|
1035
|
+
"""
|
|
1036
|
+
Ensure that all parameter dimensions are consistent with
|
|
1037
|
+
the :attr:`array_shape` dimension.
|
|
1038
|
+
|
|
1039
|
+
:raises ValueError: For inconsistent parameter dimensions.
|
|
1040
|
+
"""
|
|
1041
|
+
if self.indices_per_axis is not None:
|
|
1042
|
+
if len(self.indices_per_axis) > len(self.array_shape):
|
|
1043
|
+
raise ValueError(
|
|
1044
|
+
"Got len(self.indices_per_axis)=%s > len(self.array_shape)=%s, should be equal."
|
|
1045
|
+
%
|
|
1046
|
+
(len(self.indices_per_axis), len(self.array_shape))
|
|
1047
|
+
)
|
|
1048
|
+
if self.split_num_slices_per_axis is not None:
|
|
1049
|
+
if len(self.split_num_slices_per_axis) > len(self.array_shape):
|
|
1050
|
+
raise ValueError(
|
|
1051
|
+
(
|
|
1052
|
+
"Got len(self.split_num_slices_per_axis)=%s > len(self.array_shape)=%s,"
|
|
1053
|
+
+
|
|
1054
|
+
" should be equal."
|
|
1055
|
+
)
|
|
1056
|
+
%
|
|
1057
|
+
(len(self.split_num_slices_per_axis), len(self.array_shape))
|
|
1058
|
+
)
|
|
1059
|
+
if self.tile_shape is not None:
|
|
1060
|
+
if len(self.tile_shape) != len(self.array_shape):
|
|
1061
|
+
raise ValueError(
|
|
1062
|
+
"Got len(self.tile_shape)=%s > len(self.array_shape)=%s, should be equal."
|
|
1063
|
+
%
|
|
1064
|
+
(len(self.tile_shape), len(self.array_shape))
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
if self.sub_tile_shape is not None:
|
|
1068
|
+
if len(self.sub_tile_shape) != len(self.array_shape):
|
|
1069
|
+
raise ValueError(
|
|
1070
|
+
"Got len(self.sub_tile_shape)=%s > len(self.array_shape)=%s, should be equal."
|
|
1071
|
+
%
|
|
1072
|
+
(len(self.sub_tile_shape), len(self.array_shape))
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
if self.max_tile_shape is not None:
|
|
1076
|
+
if len(self.max_tile_shape) != len(self.array_shape):
|
|
1077
|
+
raise ValueError(
|
|
1078
|
+
"Got len(self.max_tile_shape)=%s > len(self.array_shape)=%s, should be equal."
|
|
1079
|
+
%
|
|
1080
|
+
(len(self.max_tile_shape), len(self.array_shape))
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
if self.array_start is not None:
|
|
1084
|
+
if len(self.array_start) != len(self.array_shape):
|
|
1085
|
+
raise ValueError(
|
|
1086
|
+
"Got len(self.array_start)=%s > len(self.array_shape)=%s, should be equal."
|
|
1087
|
+
%
|
|
1088
|
+
(len(self.array_start), len(self.array_shape))
|
|
1089
|
+
)
|
|
1090
|
+
|
|
1091
|
+
def check_consistent_parameter_grouping(self):
|
|
1092
|
+
"""
|
|
1093
|
+
Ensures this object does not have conflicting groups of parameters.
|
|
1094
|
+
|
|
1095
|
+
:raises ValueError: For conflicting or absent parameters.
|
|
1096
|
+
"""
|
|
1097
|
+
parameter_groups = {}
|
|
1098
|
+
if self.indices_per_axis is not None:
|
|
1099
|
+
parameter_groups["indices_per_axis"] = \
|
|
1100
|
+
{"self.indices_per_axis": self.indices_per_axis}
|
|
1101
|
+
if (self.split_size is not None) or (self.split_num_slices_per_axis is not None):
|
|
1102
|
+
parameter_groups["split_size"] = \
|
|
1103
|
+
{
|
|
1104
|
+
"self.split_size": self.split_size,
|
|
1105
|
+
"self.split_num_slices_per_axis": self.split_num_slices_per_axis,
|
|
1106
|
+
}
|
|
1107
|
+
if self.tile_shape is not None:
|
|
1108
|
+
parameter_groups["tile_shape"] = \
|
|
1109
|
+
{"self.tile_shape": self.tile_shape}
|
|
1110
|
+
if self.max_tile_bytes is not None:
|
|
1111
|
+
parameter_groups["max_tile_bytes"] = \
|
|
1112
|
+
{"self.max_tile_bytes": self.max_tile_bytes}
|
|
1113
|
+
if self.max_tile_shape is not None:
|
|
1114
|
+
if "max_tile_bytes" not in parameter_groups.keys():
|
|
1115
|
+
parameter_groups["max_tile_bytes"] = {}
|
|
1116
|
+
parameter_groups["max_tile_bytes"]["self.max_tile_shape"] = self.max_tile_shape
|
|
1117
|
+
if self.sub_tile_shape is not None:
|
|
1118
|
+
if "max_tile_bytes" not in parameter_groups.keys():
|
|
1119
|
+
parameter_groups["max_tile_bytes"] = {}
|
|
1120
|
+
parameter_groups["max_tile_bytes"]["self.sub_tile_shape"] = self.sub_tile_shape
|
|
1121
|
+
|
|
1122
|
+
self.logger.debug("parameter_groups=%s", parameter_groups)
|
|
1123
|
+
|
|
1124
|
+
if len(parameter_groups.keys()) > 1:
|
|
1125
|
+
group_keys = sorted(parameter_groups.keys())
|
|
1126
|
+
raise ValueError(
|
|
1127
|
+
"Got conflicting parameter groups specified, "
|
|
1128
|
+
+
|
|
1129
|
+
"should only specify one group to define the split:\n"
|
|
1130
|
+
+
|
|
1131
|
+
(
|
|
1132
|
+
"\n".join(
|
|
1133
|
+
[
|
|
1134
|
+
(
|
|
1135
|
+
("Group %18s: " % ("'%s'" % group_key))
|
|
1136
|
+
+
|
|
1137
|
+
str(parameter_groups[group_key])
|
|
1138
|
+
)
|
|
1139
|
+
for group_key in group_keys
|
|
1140
|
+
]
|
|
1141
|
+
)
|
|
1142
|
+
)
|
|
1143
|
+
)
|
|
1144
|
+
if len(parameter_groups.keys()) <= 0:
|
|
1145
|
+
raise ValueError(
|
|
1146
|
+
"No split parameters specified, need parameters from one of the groups: "
|
|
1147
|
+
+
|
|
1148
|
+
"'indices_per_axis', 'split_size', 'tile_shape' or 'max_tile_bytes'"
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
def check_split_parameters(self):
|
|
1152
|
+
"""
|
|
1153
|
+
Ensures this object has a state consistent with evaluating a split.
|
|
1154
|
+
|
|
1155
|
+
:raises ValueError: For conflicting or absent parameters.
|
|
1156
|
+
"""
|
|
1157
|
+
|
|
1158
|
+
self.check_tile_bounds_policy()
|
|
1159
|
+
self.check_consistent_parameter_dimensions()
|
|
1160
|
+
self.check_consistent_parameter_grouping()
|
|
1161
|
+
|
|
1162
|
+
def update_tile_extent_bounds(self):
|
|
1163
|
+
"""
|
|
1164
|
+
Updates the :attr:`tile_beg_min` and :attr:`tile_end_max`
|
|
1165
|
+
data members according to :attr:`tile_bounds_policy`.
|
|
1166
|
+
"""
|
|
1167
|
+
|
|
1168
|
+
if self.tile_bounds_policy == NO_BOUNDS:
|
|
1169
|
+
self.tile_beg_min = self.array_start - self.halo[:, 0]
|
|
1170
|
+
self.tile_end_max = self.array_start + self.array_shape + self.halo[:, 1]
|
|
1171
|
+
elif self.tile_bounds_policy == ARRAY_BOUNDS:
|
|
1172
|
+
self.tile_beg_min = self.array_start
|
|
1173
|
+
self.tile_end_max = self.array_start + self.array_shape
|
|
1174
|
+
|
|
1175
|
+
def set_split_extents_by_indices_per_axis(self):
|
|
1176
|
+
"""
|
|
1177
|
+
Sets split shape :attr:`split_shape` and
|
|
1178
|
+
split extents (:attr:`split_begs` and :attr:`split_ends`)
|
|
1179
|
+
from values in :attr:`indices_per_axis`.
|
|
1180
|
+
"""
|
|
1181
|
+
if self.indices_per_axis is None:
|
|
1182
|
+
raise ValueError("Got None for self.indices_per_axis")
|
|
1183
|
+
|
|
1184
|
+
self.logger.debug("self.array_shape=%s", self.array_shape)
|
|
1185
|
+
self.logger.debug("self.indices_per_axis=%s", self.indices_per_axis)
|
|
1186
|
+
self.indices_per_axis = \
|
|
1187
|
+
pad_with_none(self.indices_per_axis, len(self.array_shape))
|
|
1188
|
+
|
|
1189
|
+
# Define the start and stop indices (extents) for each axis slice
|
|
1190
|
+
self.split_shape = _np.ones(len(self.array_shape), dtype="int64")
|
|
1191
|
+
self.split_begs = [[], ] * len(self.array_shape)
|
|
1192
|
+
self.split_ends = [[], ] * len(self.array_shape)
|
|
1193
|
+
for i in range(len(self.indices_per_axis)):
|
|
1194
|
+
indices = self.indices_per_axis[i]
|
|
1195
|
+
if (indices is not None) and (len(indices) > 0):
|
|
1196
|
+
self.split_shape[i] = len(indices) + 1
|
|
1197
|
+
self.split_begs[i] = _np.zeros((len(indices) + 1,), dtype="int64")
|
|
1198
|
+
self.split_begs[i][1:] = indices
|
|
1199
|
+
self.split_ends[i] = _np.zeros((len(self.split_begs[i]),), dtype="int64")
|
|
1200
|
+
self.split_ends[i][0:-1] = self.split_begs[i][1:]
|
|
1201
|
+
self.split_ends[i][-1] = self.array_shape[i]
|
|
1202
|
+
else:
|
|
1203
|
+
# start and stop is the full width of the axis
|
|
1204
|
+
self.split_begs[i] = [0, ]
|
|
1205
|
+
self.split_ends[i] = [self.array_shape[i], ]
|
|
1206
|
+
|
|
1207
|
+
self.logger.debug("self.indices_per_axis=%s", self.indices_per_axis)
|
|
1208
|
+
|
|
1209
|
+
def calculate_split_from_extents(self):
|
|
1210
|
+
"""
|
|
1211
|
+
Returns split calculated using extents obtained
|
|
1212
|
+
from :attr:`split_begs` and :attr:`split_ends`.
|
|
1213
|
+
All calls to calculate the split end up here to produce
|
|
1214
|
+
the :mod:`numpy` `structured array <http://docs.scipy.org/doc/numpy/user/basics.rec.html>`_
|
|
1215
|
+
of :obj:`tuple`-of-:obj:`slice` elements.
|
|
1216
|
+
|
|
1217
|
+
:rtype: :obj:`numpy.ndarray`
|
|
1218
|
+
:return:
|
|
1219
|
+
A :mod:`numpy` `structured array <http://docs.scipy.org/doc/numpy/user/basics.rec.html>`_
|
|
1220
|
+
where each element is a :obj:`tuple` of :obj:`slice` objects.
|
|
1221
|
+
"""
|
|
1222
|
+
self.logger.debug("self.split_shape=%s", self.split_shape)
|
|
1223
|
+
self.logger.debug("self.split_begs=%s", self.split_begs)
|
|
1224
|
+
self.logger.debug("self.split_ends=%s", self.split_ends)
|
|
1225
|
+
|
|
1226
|
+
ret = \
|
|
1227
|
+
_np.array(
|
|
1228
|
+
[
|
|
1229
|
+
tuple(
|
|
1230
|
+
[
|
|
1231
|
+
slice(
|
|
1232
|
+
int(max([
|
|
1233
|
+
self.split_begs[d][idx[d]]
|
|
1234
|
+
+ self.array_start[d]
|
|
1235
|
+
- self.halo[d, 0]
|
|
1236
|
+
* (self.split_ends[d][idx[d]] > self.split_begs[d][idx[d]]),
|
|
1237
|
+
self.tile_beg_min[d]
|
|
1238
|
+
])),
|
|
1239
|
+
int(min([
|
|
1240
|
+
self.split_ends[d][idx[d]]
|
|
1241
|
+
+ self.array_start[d]
|
|
1242
|
+
+ self.halo[d, 1]
|
|
1243
|
+
* (self.split_ends[d][idx[d]] > self.split_begs[d][idx[d]]),
|
|
1244
|
+
self.tile_end_max[d]
|
|
1245
|
+
]))
|
|
1246
|
+
)
|
|
1247
|
+
for d in range(len(self.split_shape))
|
|
1248
|
+
]
|
|
1249
|
+
)
|
|
1250
|
+
for idx in
|
|
1251
|
+
_np.array(
|
|
1252
|
+
_np.unravel_index(
|
|
1253
|
+
_np.arange(0, _np.prod(self.split_shape)),
|
|
1254
|
+
self.split_shape if self.split_shape is not None else tuple()
|
|
1255
|
+
)
|
|
1256
|
+
).T
|
|
1257
|
+
],
|
|
1258
|
+
dtype=[("%d" % d, "object") for d in range(len(self.split_shape))]
|
|
1259
|
+
).reshape(self.split_shape)
|
|
1260
|
+
|
|
1261
|
+
return ret
|
|
1262
|
+
|
|
1263
|
+
def calculate_split_halos_from_extents(self):
|
|
1264
|
+
"""
|
|
1265
|
+
Returns :samp:`(self.ndim, 2)` shaped halo array elements indicating
|
|
1266
|
+
the halo for each split. Tiles on the boundary may have the halo trimmed
|
|
1267
|
+
to account for the :attr:`tile_bounds_policy`.
|
|
1268
|
+
|
|
1269
|
+
:rtype: :obj:`numpy.ndarray`
|
|
1270
|
+
:return:
|
|
1271
|
+
A :mod:`numpy` `structured array <http://docs.scipy.org/doc/numpy/user/basics.rec.html>`_
|
|
1272
|
+
where each element is a :samp:`(self.ndim, 2)` shaped :obj:`numpy.ndarray`
|
|
1273
|
+
indicating the per-axis and per-direction number of halo elements for each tile
|
|
1274
|
+
in the split.
|
|
1275
|
+
"""
|
|
1276
|
+
self.logger.debug("self.split_shape=%s", self.split_shape)
|
|
1277
|
+
self.logger.debug("self.split_begs=%s", self.split_begs)
|
|
1278
|
+
self.logger.debug("self.split_ends=%s", self.split_ends)
|
|
1279
|
+
|
|
1280
|
+
ret = \
|
|
1281
|
+
_np.array(
|
|
1282
|
+
[
|
|
1283
|
+
(
|
|
1284
|
+
tuple(
|
|
1285
|
+
(
|
|
1286
|
+
min([
|
|
1287
|
+
self.split_begs[d][idx[d]] - self.tile_beg_min[d],
|
|
1288
|
+
self.halo[d, 0]
|
|
1289
|
+
*
|
|
1290
|
+
(self.split_ends[d][idx[d]] > self.split_begs[d][idx[d]])
|
|
1291
|
+
]),
|
|
1292
|
+
min([
|
|
1293
|
+
self.tile_end_max[d] - self.split_ends[d][idx[d]],
|
|
1294
|
+
self.halo[d, 1]
|
|
1295
|
+
*
|
|
1296
|
+
(self.split_ends[d][idx[d]] > self.split_begs[d][idx[d]])
|
|
1297
|
+
])
|
|
1298
|
+
)
|
|
1299
|
+
for d in range(len(self.split_shape))
|
|
1300
|
+
)
|
|
1301
|
+
)
|
|
1302
|
+
for idx in
|
|
1303
|
+
_np.array(
|
|
1304
|
+
_np.unravel_index(
|
|
1305
|
+
_np.arange(0, _np.prod(self.split_shape)),
|
|
1306
|
+
self.split_shape if self.split_shape is not None else tuple()
|
|
1307
|
+
)
|
|
1308
|
+
).T
|
|
1309
|
+
],
|
|
1310
|
+
dtype=[("%d" % d, "2int64") for d in range(len(self.split_shape))]
|
|
1311
|
+
).reshape(self.split_shape)
|
|
1312
|
+
|
|
1313
|
+
return ret
|
|
1314
|
+
|
|
1315
|
+
def calculate_split_by_indices_per_axis(self):
|
|
1316
|
+
"""
|
|
1317
|
+
Returns split calculated using extents obtained
|
|
1318
|
+
from :attr:`indices_per_axis`.
|
|
1319
|
+
|
|
1320
|
+
:rtype: :obj:`numpy.ndarray`
|
|
1321
|
+
:return:
|
|
1322
|
+
A :mod:`numpy` `structured array <http://docs.scipy.org/doc/numpy/user/basics.rec.html>`_
|
|
1323
|
+
where each element is a :obj:`tuple` of :obj:`slice` objects.
|
|
1324
|
+
"""
|
|
1325
|
+
self.set_split_extents_by_indices_per_axis()
|
|
1326
|
+
return self.calculate_split_from_extents()
|
|
1327
|
+
|
|
1328
|
+
def calculate_axis_split_extents(self, num_sections, size):
|
|
1329
|
+
"""
|
|
1330
|
+
Divides :samp:`range(0, {size})` into (approximately) equal sized
|
|
1331
|
+
intervals. Returns :samp:`(begs, ends)` where :samp:`slice(begs[i], ends[i])`
|
|
1332
|
+
define the intervals for :samp:`i in range(0, {num_sections})`.
|
|
1333
|
+
|
|
1334
|
+
:type num_sections: :obj:`int`
|
|
1335
|
+
:param num_sections: Divide :samp:`range(0, {size})` into this
|
|
1336
|
+
many intervals (approximately) equal sized intervals.
|
|
1337
|
+
:type size: :obj:`int`
|
|
1338
|
+
:param size: Range for the subdivision.
|
|
1339
|
+
:rtype: :obj:`tuple`
|
|
1340
|
+
:return: Two element tuple :samp:`(begs, ends)`
|
|
1341
|
+
such that :samp:`slice(begs[i], ends[i])` define the
|
|
1342
|
+
intervals for :samp:`i in range(0, {num_sections})`.
|
|
1343
|
+
|
|
1344
|
+
"""
|
|
1345
|
+
section_size = size // num_sections
|
|
1346
|
+
if section_size >= 1:
|
|
1347
|
+
begs = _np.arange(0, section_size * num_sections, section_size)
|
|
1348
|
+
rem = size - section_size * num_sections
|
|
1349
|
+
if rem > 0:
|
|
1350
|
+
for i in range(rem):
|
|
1351
|
+
begs[i + 1:] += 1
|
|
1352
|
+
ends = _np.zeros_like(begs)
|
|
1353
|
+
ends[0:-1] = begs[1:]
|
|
1354
|
+
ends[-1] = size
|
|
1355
|
+
else:
|
|
1356
|
+
begs = _np.arange(0, num_sections)
|
|
1357
|
+
begs[size:] = size
|
|
1358
|
+
ends = begs.copy()
|
|
1359
|
+
ends[0:-1] = begs[1:]
|
|
1360
|
+
|
|
1361
|
+
return begs, ends
|
|
1362
|
+
|
|
1363
|
+
def set_split_extents_by_split_size(self):
|
|
1364
|
+
"""
|
|
1365
|
+
Sets split shape :attr:`split_shape` and
|
|
1366
|
+
split extents (:attr:`split_begs` and :attr:`split_ends`)
|
|
1367
|
+
from values in :attr:`split_size` and :attr:`split_num_slices_per_axis`.
|
|
1368
|
+
"""
|
|
1369
|
+
|
|
1370
|
+
if self.split_size is None:
|
|
1371
|
+
if (
|
|
1372
|
+
_np.all([s is not None for s in self.split_num_slices_per_axis])
|
|
1373
|
+
and
|
|
1374
|
+
_np.all([s > 0 for s in self.split_num_slices_per_axis])
|
|
1375
|
+
):
|
|
1376
|
+
self.split_size = _np.prod(self.split_num_slices_per_axis)
|
|
1377
|
+
else:
|
|
1378
|
+
raise ValueError(
|
|
1379
|
+
(
|
|
1380
|
+
"Got invalid self.split_num_slices_per_axis=%s, all elements "
|
|
1381
|
+
+
|
|
1382
|
+
"need to be integers greater than zero when self.split_size is None."
|
|
1383
|
+
)
|
|
1384
|
+
%
|
|
1385
|
+
self.split_num_slices_per_axis
|
|
1386
|
+
)
|
|
1387
|
+
self.logger.debug(
|
|
1388
|
+
"Pre cannonicalise: self.split_num_slices_per_axis=%s",
|
|
1389
|
+
self.split_num_slices_per_axis)
|
|
1390
|
+
self.split_num_slices_per_axis = \
|
|
1391
|
+
calculate_num_slices_per_axis(
|
|
1392
|
+
self.split_num_slices_per_axis,
|
|
1393
|
+
self.split_size,
|
|
1394
|
+
self.array_shape
|
|
1395
|
+
)
|
|
1396
|
+
self.logger.debug(
|
|
1397
|
+
"Post cannonicalise: self.split_num_slices_per_axis=%s",
|
|
1398
|
+
self.split_num_slices_per_axis)
|
|
1399
|
+
# Define the start and stop indices (extents) for each axis slice
|
|
1400
|
+
self.split_shape = self.split_num_slices_per_axis.copy()
|
|
1401
|
+
self.split_begs = [[], ] * len(self.array_shape)
|
|
1402
|
+
self.split_ends = [[], ] * len(self.array_shape)
|
|
1403
|
+
for i in range(len(self.array_shape)):
|
|
1404
|
+
self.split_begs[i], self.split_ends[i] = \
|
|
1405
|
+
self.calculate_axis_split_extents(
|
|
1406
|
+
self.split_shape[i],
|
|
1407
|
+
self.array_shape[i]
|
|
1408
|
+
)
|
|
1409
|
+
|
|
1410
|
+
def calculate_split_by_split_size(self):
|
|
1411
|
+
"""
|
|
1412
|
+
Returns split calculated using extents obtained
|
|
1413
|
+
from :attr:`split_size` and :attr:`split_num_slices_per_axis`.
|
|
1414
|
+
|
|
1415
|
+
:rtype: :obj:`numpy.ndarray`
|
|
1416
|
+
:return:
|
|
1417
|
+
A :mod:`numpy` `structured array <http://docs.scipy.org/doc/numpy/user/basics.rec.html>`_
|
|
1418
|
+
where each element is a :obj:`tuple` of :obj:`slice` objects.
|
|
1419
|
+
"""
|
|
1420
|
+
self.set_split_extents_by_split_size()
|
|
1421
|
+
return self.calculate_split_from_extents()
|
|
1422
|
+
|
|
1423
|
+
def set_split_extents_by_tile_shape(self):
|
|
1424
|
+
"""
|
|
1425
|
+
Sets split shape :attr:`split_shape` and
|
|
1426
|
+
split extents (:attr:`split_begs` and :attr:`split_ends`)
|
|
1427
|
+
from value of :attr:`tile_shape`.
|
|
1428
|
+
"""
|
|
1429
|
+
self.split_shape = ((self.array_shape - 1) // self.tile_shape) + 1
|
|
1430
|
+
self.split_begs = [[], ] * len(self.array_shape)
|
|
1431
|
+
self.split_ends = [[], ] * len(self.array_shape)
|
|
1432
|
+
for i in range(len(self.array_shape)):
|
|
1433
|
+
self.split_begs[i] = _np.arange(0, self.array_shape[i], self.tile_shape[i])
|
|
1434
|
+
self.split_ends[i] = _np.zeros_like(self.split_begs[i])
|
|
1435
|
+
self.split_ends[i][0:-1] = self.split_begs[i][1:]
|
|
1436
|
+
self.split_ends[i][-1] = self.array_shape[i]
|
|
1437
|
+
|
|
1438
|
+
def calculate_split_by_tile_shape(self):
|
|
1439
|
+
"""
|
|
1440
|
+
Returns split calculated using extents obtained
|
|
1441
|
+
from :attr:`tile_shape`.
|
|
1442
|
+
|
|
1443
|
+
:rtype: :obj:`numpy.ndarray`
|
|
1444
|
+
:return:
|
|
1445
|
+
A :mod:`numpy` `structured array <http://docs.scipy.org/doc/numpy/user/basics.rec.html>`_
|
|
1446
|
+
where each element is a :obj:`tuple` of :obj:`slice` objects.
|
|
1447
|
+
"""
|
|
1448
|
+
self.set_split_extents_by_tile_shape()
|
|
1449
|
+
return self.calculate_split_from_extents()
|
|
1450
|
+
|
|
1451
|
+
def set_split_extents_by_tile_max_bytes(self):
|
|
1452
|
+
"""
|
|
1453
|
+
Sets split extents (:attr:`split_begs`
|
|
1454
|
+
and :attr:`split_ends`) calculated using
|
|
1455
|
+
from :attr:`max_tile_bytes`
|
|
1456
|
+
(and :attr:`max_tile_shape`, :attr:`sub_tile_shape`, :attr:`halo`).
|
|
1457
|
+
|
|
1458
|
+
"""
|
|
1459
|
+
self.tile_shape = \
|
|
1460
|
+
calculate_tile_shape_for_max_bytes(
|
|
1461
|
+
array_shape=self.array_shape,
|
|
1462
|
+
array_itemsize=self.array_itemsize,
|
|
1463
|
+
max_tile_bytes=self.max_tile_bytes,
|
|
1464
|
+
max_tile_shape=self.max_tile_shape,
|
|
1465
|
+
sub_tile_shape=self.sub_tile_shape,
|
|
1466
|
+
halo=self.halo
|
|
1467
|
+
)
|
|
1468
|
+
self.set_split_extents_by_tile_shape()
|
|
1469
|
+
|
|
1470
|
+
def calculate_split_by_tile_max_bytes(self):
|
|
1471
|
+
"""
|
|
1472
|
+
Returns split calculated using extents obtained
|
|
1473
|
+
from :attr:`max_tile_bytes`
|
|
1474
|
+
(and :attr:`max_tile_shape`, :attr:`sub_tile_shape`, :attr:`halo`).
|
|
1475
|
+
|
|
1476
|
+
:rtype: :obj:`numpy.ndarray`
|
|
1477
|
+
:return:
|
|
1478
|
+
A :mod:`numpy` `structured array <http://docs.scipy.org/doc/numpy/user/basics.rec.html>`_
|
|
1479
|
+
where each element is a :obj:`tuple` of :obj:`slice` objects.
|
|
1480
|
+
"""
|
|
1481
|
+
|
|
1482
|
+
self.set_split_extents_by_tile_max_bytes()
|
|
1483
|
+
return self.calculate_split_from_extents()
|
|
1484
|
+
|
|
1485
|
+
def set_split_extents(self):
|
|
1486
|
+
"""
|
|
1487
|
+
Sets split extents (:attr:`split_begs`
|
|
1488
|
+
and :attr:`split_ends`) calculated using
|
|
1489
|
+
selected attributes set from :meth:`__init__`.
|
|
1490
|
+
"""
|
|
1491
|
+
|
|
1492
|
+
self.check_split_parameters()
|
|
1493
|
+
self.update_tile_extent_bounds()
|
|
1494
|
+
|
|
1495
|
+
if self.indices_per_axis is not None:
|
|
1496
|
+
self.set_split_extents_by_indices_per_axis()
|
|
1497
|
+
elif (self.split_size is not None) or (self.split_num_slices_per_axis is not None):
|
|
1498
|
+
self.set_split_extents_by_split_size()
|
|
1499
|
+
elif self.tile_shape is not None:
|
|
1500
|
+
self.set_split_extents_by_tile_shape()
|
|
1501
|
+
elif self.max_tile_bytes is not None:
|
|
1502
|
+
self.set_split_extents_by_tile_max_bytes()
|
|
1503
|
+
|
|
1504
|
+
def calculate_split(self):
|
|
1505
|
+
"""
|
|
1506
|
+
Computes the split.
|
|
1507
|
+
|
|
1508
|
+
:rtype: :obj:`numpy.ndarray`
|
|
1509
|
+
:return:
|
|
1510
|
+
A :mod:`numpy` `structured array <http://docs.scipy.org/doc/numpy/user/basics.rec.html>`_
|
|
1511
|
+
of dimension :samp:`len({self}.array_shape)`.
|
|
1512
|
+
Each element of the returned array is a :obj:`tuple`
|
|
1513
|
+
containing :samp:`len({self}.array_shape)` elements, with each element
|
|
1514
|
+
being a :obj:`slice` object. Each :obj:`tuple` defines a slice within
|
|
1515
|
+
the bounds :samp:`{self}.array_start - {self}.halo[:, 0]`
|
|
1516
|
+
to :samp:`{self}.array_start + {self}.array_shape + {self}.halo[:, 1]`.
|
|
1517
|
+
"""
|
|
1518
|
+
|
|
1519
|
+
self.set_split_extents()
|
|
1520
|
+
slyces = self.calculate_split_from_extents()
|
|
1521
|
+
self.logger.debug("type(slyces)=%s", type(slyces))
|
|
1522
|
+
self.logger.debug("slyces=%s", slyces)
|
|
1523
|
+
return slyces
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
ShapeSplitter([0, ]).__init__.__func__.__doc__ = \
|
|
1527
|
+
"""
|
|
1528
|
+
Initialises parameters which define a split.
|
|
1529
|
+
|
|
1530
|
+
|
|
1531
|
+
%s
|
|
1532
|
+
%s
|
|
1533
|
+
|
|
1534
|
+
.. seealso:: :ref:`array_split-examples`
|
|
1535
|
+
|
|
1536
|
+
""" % (
|
|
1537
|
+
_array_shape_param_doc,
|
|
1538
|
+
(
|
|
1539
|
+
_ShapeSplitter__init__params_doc
|
|
1540
|
+
%
|
|
1541
|
+
(
|
|
1542
|
+
_array_start_param_doc,
|
|
1543
|
+
"\n" + _array_itemsize_param_doc,
|
|
1544
|
+
_halo_param_doc,
|
|
1545
|
+
_array_tile_bounds_policy_param_doc,
|
|
1546
|
+
)
|
|
1547
|
+
)
|
|
1548
|
+
)
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
def shape_split(array_shape, *args, **kwargs):
|
|
1552
|
+
"To be replaced."
|
|
1553
|
+
return \
|
|
1554
|
+
ShapeSplitter(
|
|
1555
|
+
array_shape,
|
|
1556
|
+
*args,
|
|
1557
|
+
**kwargs
|
|
1558
|
+
).calculate_split()
|
|
1559
|
+
|
|
1560
|
+
|
|
1561
|
+
shape_split.__doc__ =\
|
|
1562
|
+
"""
|
|
1563
|
+
Splits specified :samp:`{array_shape}` in tiles, returns array of :obj:`slice` tuples.
|
|
1564
|
+
|
|
1565
|
+
%s
|
|
1566
|
+
%s
|
|
1567
|
+
:rtype: :obj:`numpy.ndarray`
|
|
1568
|
+
:return: Array of :obj:`tuple` objects. Each :obj:`tuple` element
|
|
1569
|
+
is a :obj:`slice` object so that each :obj:`tuple` defines
|
|
1570
|
+
a multi-dimensional slice of an array of shape :samp:`{array_shape}`.
|
|
1571
|
+
|
|
1572
|
+
.. seealso:: :func:`array_split.array_split`, :meth:`array_split.ShapeSplitter`,
|
|
1573
|
+
:ref:`array_split-examples`
|
|
1574
|
+
|
|
1575
|
+
|
|
1576
|
+
""" % (
|
|
1577
|
+
_array_shape_param_doc,
|
|
1578
|
+
(
|
|
1579
|
+
_ShapeSplitter__init__params_doc
|
|
1580
|
+
%
|
|
1581
|
+
(
|
|
1582
|
+
_array_start_param_doc,
|
|
1583
|
+
"\n" + _array_itemsize_param_doc,
|
|
1584
|
+
_halo_param_doc,
|
|
1585
|
+
_array_tile_bounds_policy_param_doc,
|
|
1586
|
+
)
|
|
1587
|
+
)
|
|
1588
|
+
)
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
def array_split(
|
|
1592
|
+
ary,
|
|
1593
|
+
indices_or_sections=None,
|
|
1594
|
+
axis=None,
|
|
1595
|
+
tile_shape=None,
|
|
1596
|
+
max_tile_bytes=None,
|
|
1597
|
+
max_tile_shape=None,
|
|
1598
|
+
sub_tile_shape=None,
|
|
1599
|
+
halo=None
|
|
1600
|
+
):
|
|
1601
|
+
"To be replaced."
|
|
1602
|
+
return list(
|
|
1603
|
+
ary[slyce]
|
|
1604
|
+
for slyce in
|
|
1605
|
+
shape_split(
|
|
1606
|
+
array_shape=ary.shape,
|
|
1607
|
+
indices_or_sections=indices_or_sections,
|
|
1608
|
+
axis=axis,
|
|
1609
|
+
array_start=None,
|
|
1610
|
+
array_itemsize=ary.itemsize,
|
|
1611
|
+
tile_shape=tile_shape,
|
|
1612
|
+
max_tile_bytes=max_tile_bytes,
|
|
1613
|
+
max_tile_shape=max_tile_shape,
|
|
1614
|
+
sub_tile_shape=sub_tile_shape,
|
|
1615
|
+
halo=halo,
|
|
1616
|
+
tile_bounds_policy=ARRAY_BOUNDS
|
|
1617
|
+
).flatten().tolist()
|
|
1618
|
+
)
|
|
1619
|
+
|
|
1620
|
+
|
|
1621
|
+
array_split.__doc__ =\
|
|
1622
|
+
"""
|
|
1623
|
+
Splits the specified array :samp:`{ary}` into sub-arrays, returns list of :obj:`numpy.ndarray`.
|
|
1624
|
+
|
|
1625
|
+
:type ary: :obj:`numpy.ndarray`
|
|
1626
|
+
:param ary: Array which is split into sub-arrays.
|
|
1627
|
+
%s
|
|
1628
|
+
:rtype: :obj:`list`
|
|
1629
|
+
:return: List of :obj:`numpy.ndarray` elements, where each element is
|
|
1630
|
+
a *slice* from :samp:`{ary}` (potentially an empty slice).
|
|
1631
|
+
|
|
1632
|
+
.. seealso:: :func:`array_split.shape_split`, :meth:`array_split.ShapeSplitter`,
|
|
1633
|
+
:ref:`array_split-examples`
|
|
1634
|
+
|
|
1635
|
+
|
|
1636
|
+
""" % (
|
|
1637
|
+
_ShapeSplitter__init__params_doc
|
|
1638
|
+
%
|
|
1639
|
+
(
|
|
1640
|
+
"",
|
|
1641
|
+
"",
|
|
1642
|
+
_halo_param_doc.replace("len({array_shape})", "len({ary}.shape)"),
|
|
1643
|
+
""
|
|
1644
|
+
)
|
|
1645
|
+
)
|
|
1646
|
+
|
|
1647
|
+
__all__ = [s for s in dir() if not s.startswith('_')]
|