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.
@@ -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('_')]