carray 2.0.0 → 3.0.0
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.
- checksums.yaml +4 -4
- data/.yardopts +5 -25
- data/CHANGELOG.md +16 -0
- data/LICENSE +1 -1
- data/NEWS.md +3 -0
- data/README.md +128 -44
- data/carray.gemspec +22 -24
- data/ext/ca_array_pool.c +91 -0
- data/ext/ca_axis_descriptor.h +186 -0
- data/ext/ca_axis_dispatch.c +924 -0
- data/ext/ca_axis_group.c +1208 -0
- data/ext/ca_bincmp_dispatch.c +76 -0
- data/ext/ca_bincmp_dispatch.h +85 -0
- data/ext/ca_binop_dispatch.c +125 -0
- data/ext/ca_binop_dispatch.h +159 -0
- data/ext/ca_categorical_iterator.c +1375 -0
- data/ext/ca_compare.c +94 -0
- data/ext/ca_compare.h +26 -0
- data/ext/ca_composite_dispatch.c +414 -0
- data/ext/ca_composite_dispatch.h +116 -0
- data/ext/ca_for_buffer.h +96 -0
- data/ext/ca_for_each_element.h +241 -0
- data/ext/ca_group_iter.c +304 -0
- data/ext/ca_iter_substrate.h +325 -0
- data/ext/ca_kernel_iterator.c +4321 -0
- data/ext/ca_kernel_iterator.h +2603 -0
- data/ext/ca_moncmp_dispatch.c +37 -0
- data/ext/ca_moncmp_dispatch.h +62 -0
- data/ext/ca_monop_dispatch.c +200 -0
- data/ext/ca_monop_dispatch.h +235 -0
- data/ext/ca_obj_array.c +355 -359
- data/ext/ca_obj_bincmp.c +809 -0
- data/ext/ca_obj_binop.c +892 -0
- data/ext/ca_obj_bitarray.c +369 -164
- data/ext/ca_obj_bitfield.c +294 -234
- data/ext/ca_obj_block.c +189 -711
- data/ext/ca_obj_byte_swap.c +766 -0
- data/ext/ca_obj_const_string.c +965 -0
- data/ext/ca_obj_face.c +670 -0
- data/ext/ca_obj_face.h +247 -0
- data/ext/ca_obj_fake.c +228 -100
- data/ext/ca_obj_farray.c +54 -441
- data/ext/ca_obj_field.c +82 -529
- data/ext/ca_obj_fixlen_string.c +306 -0
- data/ext/ca_obj_grid.c +858 -440
- data/ext/ca_obj_meld.c +1034 -0
- data/ext/ca_obj_moncmp.c +569 -0
- data/ext/ca_obj_monop.c +1111 -0
- data/ext/ca_obj_object.c +774 -298
- data/ext/ca_obj_record.c +468 -0
- data/ext/ca_obj_reduce.c +97 -82
- data/ext/ca_obj_refer.c +569 -459
- data/ext/ca_obj_remap.c +475 -0
- data/ext/ca_obj_repeat.c +92 -477
- data/ext/ca_obj_roll.c +616 -0
- data/ext/ca_obj_select.c +344 -296
- data/ext/ca_obj_select_axis.c +1296 -0
- data/ext/ca_obj_shift.c +230 -792
- data/ext/ca_obj_source.c +78 -0
- data/ext/ca_obj_stack.c +1173 -0
- data/ext/ca_obj_stride.c +2501 -0
- data/ext/ca_obj_string.c +268 -0
- data/ext/ca_obj_tile.c +614 -0
- data/ext/ca_obj_time.c +546 -0
- data/ext/ca_obj_timedelta.c +435 -0
- data/ext/ca_obj_transpose.c +62 -516
- data/ext/ca_obj_triop.c +746 -0
- data/ext/ca_obj_unbound_repeat.c +208 -241
- data/ext/ca_obj_window.c +1131 -563
- data/ext/ca_op_byte_swap.c +175 -0
- data/ext/ca_op_ipower.c +319 -0
- data/ext/ca_op_powi.h +88 -0
- data/ext/ca_sort_kernels.h +132 -0
- data/ext/ca_sweep_engine.c +430 -0
- data/ext/ca_sweep_engine.h +157 -0
- data/ext/ca_transform_common.c +228 -0
- data/ext/ca_triop_dispatch.c +55 -0
- data/ext/ca_triop_dispatch.h +62 -0
- data/ext/carray.h +795 -402
- data/ext/carray_access.c +831 -711
- data/ext/carray_attribute.c +98 -330
- data/ext/carray_bincount.c +255 -0
- data/ext/carray_broadcast.c +283 -0
- data/ext/carray_call_cfunc.c +1360 -828
- data/ext/carray_call_cfunc.h +160 -0
- data/ext/carray_cast.c +1212 -301
- data/ext/carray_cast_func.rb +81 -40
- data/ext/carray_class.c +53 -63
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +350 -346
- data/ext/carray_copy.c +156 -268
- data/ext/carray_core.c +1342 -199
- data/ext/carray_count.c +312 -0
- data/ext/carray_data_type.c +43 -19
- data/ext/carray_element.c +585 -213
- data/ext/carray_factorize.c +2542 -0
- data/ext/carray_generate.c +230 -559
- data/ext/carray_histogram.c +490 -0
- data/ext/carray_hold.c +228 -0
- data/ext/carray_index_classifier.c +1035 -0
- data/ext/carray_index_classifier.h +27 -0
- data/ext/carray_internal.h +120 -0
- data/ext/carray_kernels_bincmp.c +4445 -0
- data/ext/carray_kernels_binop.c +10979 -0
- data/ext/carray_kernels_init.c +36 -0
- data/ext/carray_kernels_map.c +3466 -0
- data/ext/carray_kernels_moncmp.c +2096 -0
- data/ext/carray_kernels_monop.c +18312 -0
- data/ext/carray_kernels_reduce_aggregate.c +25836 -0
- data/ext/carray_kernels_reduce_boolean.c +329 -0
- data/ext/carray_kernels_reduce_cumulative.c +14592 -0
- data/ext/carray_kernels_reduce_extreme.c +16947 -0
- data/ext/carray_kernels_reduce_variance.c +3909 -0
- data/ext/carray_kernels_scan.c +3692 -0
- data/ext/carray_kernels_search.c +32137 -0
- data/ext/carray_kernels_sort.c +10625 -0
- data/ext/carray_kernels_triop.c +1391 -0
- data/ext/carray_lazy.c +567 -0
- data/ext/carray_loop.c +88 -200
- data/ext/carray_mask.c +848 -154
- data/ext/carray_math_kernel.h +120 -0
- data/ext/carray_mathfunc.c +10 -241
- data/ext/carray_median_percentile.c +1257 -0
- data/ext/carray_memory_view.c +1625 -0
- data/ext/carray_operator.c +1526 -318
- data/ext/carray_order.c +664 -1394
- data/ext/carray_partition.c +416 -0
- data/ext/carray_random.c +518 -0
- data/ext/carray_scatter.c +357 -0
- data/ext/carray_slab.c +1219 -0
- data/ext/carray_slab.h +84 -0
- data/ext/carray_sort.c +829 -0
- data/ext/carray_sort_kernel.c +620 -0
- data/ext/carray_struct.c +695 -0
- data/ext/carray_test.c +343 -229
- data/ext/carray_undef.c +34 -17
- data/ext/carray_utils.c +175 -74
- data/ext/extconf.rb +216 -55
- data/ext/mk_call_cfunc.rb +480 -0
- data/ext/mkkernel.rb +8842 -0
- data/ext/ruby_carray.c +202 -101
- data/ext/version.h +4 -14
- data/ext/version.rb +5 -13
- data/lib/carray/arrow_tensor.rb +401 -0
- data/lib/carray/attribute.rb +166 -0
- data/lib/carray/autoload_carray.rb +220 -0
- data/lib/carray/autoload_method_extension.rb +44 -0
- data/lib/carray/axis_group.rb +711 -0
- data/lib/carray/basics.rb +481 -0
- data/lib/carray/bincount_nd.rb +358 -0
- data/lib/carray/block_iterator.rb +604 -0
- data/lib/carray/boolean_reduce.rb +109 -0
- data/lib/carray/categorical.rb +561 -0
- data/lib/carray/categorical_iterator.rb +1062 -0
- data/lib/carray/complex.rb +150 -0
- data/lib/carray/conditional.rb +216 -0
- data/lib/carray/const_string.rb +228 -0
- data/lib/carray/construct.rb +139 -328
- data/lib/carray/core_extensions.rb +240 -0
- data/lib/carray/data_type_extension.rb +233 -0
- data/lib/carray/fixlen_string.rb +95 -0
- data/lib/carray/frame/concat.rb +132 -0
- data/lib/carray/frame/convert.rb +95 -0
- data/lib/carray/frame/csv_parser.rb +211 -0
- data/lib/carray/frame/frame.rb +649 -0
- data/lib/carray/frame/group.rb +186 -0
- data/lib/carray/frame/io.rb +164 -0
- data/lib/carray/frame/join.rb +248 -0
- data/lib/carray/frame/records.rb +99 -0
- data/lib/carray/frame/sort.rb +113 -0
- data/lib/carray/frame/verbs.rb +299 -0
- data/lib/carray/frame.rb +16 -0
- data/lib/carray/histogram.rb +512 -0
- data/lib/carray/inspect.rb +37 -20
- data/lib/carray/iterator.rb +57 -349
- data/lib/carray/lazy.rb +889 -0
- data/lib/carray/mask_gap_fill.rb +200 -0
- data/lib/carray/math.rb +78 -342
- data/lib/carray/meld_reduce.rb +289 -0
- data/lib/carray/methods/align_addr.rb +116 -0
- data/lib/carray/methods/bin.rb +128 -0
- data/lib/carray/methods/bincount.rb +87 -0
- data/lib/carray/methods/bit_string.rb +92 -0
- data/lib/carray/methods/broadcast.rb +63 -0
- data/lib/carray/methods/choose.rb +39 -0
- data/lib/carray/methods/composition.rb +280 -0
- data/lib/carray/methods/gather_nd.rb +206 -0
- data/lib/carray/methods/index.rb +39 -0
- data/lib/carray/methods/insert_block.rb +99 -0
- data/lib/carray/methods/is_in.rb +141 -0
- data/lib/carray/methods/join.rb +90 -0
- data/lib/carray/methods/locate_addr.rb +47 -0
- data/lib/carray/methods/mask_duplicates.rb +41 -0
- data/lib/carray/methods/meshgrid.rb +91 -0
- data/lib/carray/methods/mode.rb +126 -0
- data/lib/carray/methods/nunique.rb +46 -0
- data/lib/carray/methods/resize.rb +56 -0
- data/lib/carray/methods/snap.rb +156 -0
- data/lib/carray/methods/string_format.rb +57 -0
- data/lib/carray/methods/unique.rb +47 -0
- data/lib/carray/methods/value_counts.rb +71 -0
- data/lib/carray/mkmf.rb +124 -101
- data/lib/carray/runtime.rb +108 -0
- data/lib/carray/serialize.rb +478 -167
- data/lib/carray/slab_iterator.rb +292 -0
- data/lib/carray/stack.rb +291 -0
- data/lib/carray/string.rb +56 -180
- data/lib/carray/string_operation_extension.rb +289 -0
- data/lib/carray/struct.rb +335 -323
- data/lib/carray/struct_builder.rb +697 -0
- data/lib/carray/table.rb +41 -2
- data/lib/carray/time.rb +2255 -38
- data/lib/carray/window_iterator.rb +655 -0
- data/lib/carray.rb +55 -57
- metadata +163 -130
- data/Rakefile +0 -51
- data/TODO.md +0 -18
- data/ext/ca_iter_block.c +0 -257
- data/ext/ca_iter_dimension.c +0 -299
- data/ext/ca_iter_window.c +0 -214
- data/ext/ca_obj_mapping.c +0 -644
- data/ext/carray_iterator.c +0 -641
- data/ext/carray_math.rb +0 -850
- data/ext/carray_numeric.c +0 -259
- data/ext/carray_sort_addr.c +0 -254
- data/ext/carray_stat.c +0 -2100
- data/ext/carray_stat_proc.rb +0 -1999
- data/ext/mkmath.rb +0 -741
- data/ext/ruby_ccomplex.c +0 -509
- data/ext/ruby_float_func.c +0 -86
- data/lib/carray/array.rb +0 -8
- data/lib/carray/autoload/autoload_base.rb +0 -19
- data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
- data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
- data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
- data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
- data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
- data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
- data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
- data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
- data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
- data/lib/carray/autoload/autoload_gem_random.rb +0 -8
- data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
- data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
- data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
- data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
- data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
- data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
- data/lib/carray/autoload/autoload_object_link.rb +0 -1
- data/lib/carray/autoload/autoload_object_pack.rb +0 -2
- data/lib/carray/autoload.rb +0 -141
- data/lib/carray/basic.rb +0 -191
- data/lib/carray/broadcast.rb +0 -101
- data/lib/carray/compose.rb +0 -315
- data/lib/carray/convert.rb +0 -115
- data/lib/carray/info.rb +0 -110
- data/lib/carray/io/imagemagick.rb +0 -235
- data/lib/carray/mask.rb +0 -102
- data/lib/carray/math/histogram.rb +0 -177
- data/lib/carray/math/recurrence.rb +0 -93
- data/lib/carray/object/ca_obj_iterator.rb +0 -50
- data/lib/carray/object/ca_obj_link.rb +0 -50
- data/lib/carray/object/ca_obj_pack.rb +0 -99
- data/lib/carray/obsolete.rb +0 -256
- data/lib/carray/ordering.rb +0 -181
- data/lib/carray/testing.rb +0 -51
- data/lib/carray/transform.rb +0 -109
- data/misc/Methods.ja.md +0 -182
- data/misc/NOTE +0 -51
- data/spec/Classes/CABitfield_spec.rb +0 -58
- data/spec/Classes/CABlockIterator_spec.rb +0 -114
- data/spec/Classes/CABlock_spec.rb +0 -205
- data/spec/Classes/CAField_spec.rb +0 -39
- data/spec/Classes/CAGrid_spec.rb +0 -75
- data/spec/Classes/CAMap_spec.rb +0 -0
- data/spec/Classes/CAMapping_spec.rb +0 -105
- data/spec/Classes/CAObject_attribute_spec.rb +0 -33
- data/spec/Classes/CAObject_spec.rb +0 -33
- data/spec/Classes/CARefer_spec.rb +0 -93
- data/spec/Classes/CARepeat_spec.rb +0 -65
- data/spec/Classes/CASelect_spec.rb +0 -22
- data/spec/Classes/CAShift_spec.rb +0 -16
- data/spec/Classes/CAStruct_spec.rb +0 -71
- data/spec/Classes/CATranspose_spec.rb +0 -60
- data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
- data/spec/Classes/CAWindow_spec.rb +0 -54
- data/spec/Classes/CAWrap_spec.rb +0 -8
- data/spec/Classes/CArray_spec.rb +0 -184
- data/spec/Classes/CScalar_spec.rb +0 -55
- data/spec/Classes/ex1.rb +0 -46
- data/spec/Features/feature_130_spec.rb +0 -19
- data/spec/Features/feature_attributes_spec.rb +0 -280
- data/spec/Features/feature_boolean_spec.rb +0 -98
- data/spec/Features/feature_broadcast.rb +0 -116
- data/spec/Features/feature_cast_function.rb +0 -19
- data/spec/Features/feature_cast_spec.rb +0 -33
- data/spec/Features/feature_class_spec.rb +0 -84
- data/spec/Features/feature_complex_spec.rb +0 -42
- data/spec/Features/feature_composite_spec.rb +0 -124
- data/spec/Features/feature_convert_spec.rb +0 -46
- data/spec/Features/feature_copy_spec.rb +0 -123
- data/spec/Features/feature_creation_spec.rb +0 -84
- data/spec/Features/feature_element_spec.rb +0 -144
- data/spec/Features/feature_extream_spec.rb +0 -54
- data/spec/Features/feature_generate_spec.rb +0 -74
- data/spec/Features/feature_index_spec.rb +0 -69
- data/spec/Features/feature_mask_spec.rb +0 -580
- data/spec/Features/feature_math_spec.rb +0 -97
- data/spec/Features/feature_order_spec.rb +0 -146
- data/spec/Features/feature_ref_store_spec.rb +0 -209
- data/spec/Features/feature_serialization_spec.rb +0 -125
- data/spec/Features/feature_stat_spec.rb +0 -397
- data/spec/Features/feature_virtual_spec.rb +0 -48
- data/spec/Features/method_eq_spec.rb +0 -81
- data/spec/Features/method_is_nan_spec.rb +0 -12
- data/spec/Features/method_map_spec.rb +0 -54
- data/spec/Features/method_max_with.rb +0 -20
- data/spec/Features/method_min_with.rb +0 -19
- data/spec/Features/method_ne_spec.rb +0 -18
- data/spec/Features/method_project_spec.rb +0 -188
- data/spec/Features/method_ref_spec.rb +0 -27
- data/spec/Features/method_round_spec.rb +0 -11
- data/spec/Features/method_s_linspace_spec.rb +0 -48
- data/spec/Features/method_s_span_spec.rb +0 -14
- data/spec/Features/method_seq_spec.rb +0 -47
- data/spec/Features/method_sort_with.rb +0 -43
- data/spec/Features/method_sorted_with.rb +0 -29
- data/spec/Features/method_span_spec.rb +0 -42
- data/spec/Features/method_wrap_readonly_spec.rb +0 -43
- data/spec/UnitTest/test_CAVirtual.rb +0 -214
- data/spec/spec_all.rb +0 -10
- data/utils/ca_ase.rb +0 -21
- data/utils/ca_methods.rb +0 -15
- data/utils/cast_checker.rb +0 -30
- data/utils/convert_test.rb +0 -73
- data/utils/extract_yard.rb +0 -22
- data/utils/guess_shape.rb +0 -76
- data/utils/monkey_patch_methods.rb +0 -62
- data/utils/remove_resource_fork.sh +0 -5
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
# ----------------------------------------------------------------------------
|
|
2
|
+
#
|
|
3
|
+
# carray/block_iterator.rb
|
|
4
|
+
#
|
|
5
|
+
# CABlockIterator — a non-overlapping tile reduction dispatcher, the Block
|
|
6
|
+
# member of the 3.0 iterator family (sibling of CASlabIterator /
|
|
7
|
+
# CAWindowIterator / CACategoricalIterator). Where a window iterator folds an
|
|
8
|
+
# overlapping window per anchor, a block iterator folds each non-overlapping
|
|
9
|
+
# tile of a fixed per-axis size, so the output is a tile grid (pooling /
|
|
10
|
+
# block statistics / downsampling).
|
|
11
|
+
#
|
|
12
|
+
# bi = a.blocks(3, 3) # 3x3 non-overlapping tiles
|
|
13
|
+
# bi.mean # per-tile mean (average pooling)
|
|
14
|
+
# bi.max # per-tile max (max pooling)
|
|
15
|
+
#
|
|
16
|
+
# Engine: block_view decomposition. The source splits into an interior region
|
|
17
|
+
# (all axes on a size-divisible extent) plus 2^m - 1 boundary regions (m = the
|
|
18
|
+
# number of axes whose length is not a multiple of the tile size). Each region
|
|
19
|
+
# is a `block_view` (a CAStride: zero-copy over the source via compose-fold),
|
|
20
|
+
# reduced over the trailing tile axes by a core reduction and scattered into the
|
|
21
|
+
# ceil-shaped tile grid. One core reduction per region; the named reductions
|
|
22
|
+
# delegate straight to the core, so their dtype / mask / empty (ERI) / epsilon
|
|
23
|
+
# contracts are the core's, unchanged. Because tiles do not overlap, no
|
|
24
|
+
# padded entity is built (unlike CAWindowIterator) and the interior stays on
|
|
25
|
+
# the source buffer.
|
|
26
|
+
#
|
|
27
|
+
# The remainder is not the user's concern. There is no boundary-policy knob:
|
|
28
|
+
# the sole behaviour is full coverage — every cell belongs to a tile, the
|
|
29
|
+
# edge tiles simply fold whatever cells are present (present-only), and the
|
|
30
|
+
# output is the ceil tile grid. A partial edge tile carries fewer real cells,
|
|
31
|
+
# so a core `min_count:` naturally marks it UNDEF when it is not full. "Valid"
|
|
32
|
+
# pooling (drop the remainder, floor grid) is expressed explicitly by slicing
|
|
33
|
+
# first: `a[0...q*b].blocks(b)`.
|
|
34
|
+
#
|
|
35
|
+
# The class name is kept from 2.0 (the concept — a tile — is stable);
|
|
36
|
+
# this is the Ruby family member that supersedes the retired C engine
|
|
37
|
+
# (ext/ca_iter_block.c). Loaded lazily via
|
|
38
|
+
# autoload from lib/carray/autoload_carray.rb the first time `a.blocks(...)`
|
|
39
|
+
# is used.
|
|
40
|
+
#
|
|
41
|
+
# ----------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
require "carray"
|
|
44
|
+
|
|
45
|
+
# Non-overlapping tile reduction dispatcher — the Block member of the
|
|
46
|
+
# iterator family (sibling of `CASlabIterator` / {CAWindowIterator} /
|
|
47
|
+
# `CACategoricalIterator`). Where a window iterator folds an overlapping
|
|
48
|
+
# window per anchor, a block iterator folds each non-overlapping tile of a
|
|
49
|
+
# fixed per-axis size, so the result is a tile grid: pooling, downsampling,
|
|
50
|
+
# block statistics.
|
|
51
|
+
#
|
|
52
|
+
# Obtained from `CArray#blocks`, not constructed directly.
|
|
53
|
+
#
|
|
54
|
+
# @example
|
|
55
|
+
# bi = a.blocks(3, 3) # 3x3 non-overlapping tiles
|
|
56
|
+
# bi.mean # per-tile mean (average pooling)
|
|
57
|
+
# bi.max # per-tile max (max pooling)
|
|
58
|
+
class CABlockIterator < CAIterator
|
|
59
|
+
|
|
60
|
+
# @overload initialize(source, *blocks)
|
|
61
|
+
# Builds a block iterator tiling `source` with a per-axis tile size. Each
|
|
62
|
+
# `blocks[i]` is either an Integer tile size (offset 0) or a `lo..hi` range
|
|
63
|
+
# whose length is the tile size and whose start is a leading offset
|
|
64
|
+
# (backward compatible with the 2.0 `a.blocks(2..4)` form). A single Array
|
|
65
|
+
# argument is taken as the per-axis size list.
|
|
66
|
+
#
|
|
67
|
+
# @param source [CArray] the array to tile.
|
|
68
|
+
# @param blocks [Array<Integer, Range>] per-axis tile sizes (or ranges).
|
|
69
|
+
def initialize (source, *blocks)
|
|
70
|
+
blocks = blocks[0] if blocks.size == 1 && blocks[0].is_a?(Array)
|
|
71
|
+
|
|
72
|
+
unless blocks.size == source.ndim
|
|
73
|
+
raise ArgumentError,
|
|
74
|
+
"blocks: expected #{source.ndim} tile sizes (one per axis), " \
|
|
75
|
+
"got #{blocks.size}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
@sndim = source.ndim
|
|
79
|
+
offsets = Array.new(@sndim, 0)
|
|
80
|
+
@sizes = Array.new(@sndim)
|
|
81
|
+
blocks.each_with_index do |b, i|
|
|
82
|
+
if b.is_a?(Range)
|
|
83
|
+
offsets[i] = b.begin
|
|
84
|
+
@sizes[i] = b.end - b.begin + (b.exclude_end? ? 0 : 1)
|
|
85
|
+
else
|
|
86
|
+
@sizes[i] = Integer(b)
|
|
87
|
+
end
|
|
88
|
+
if @sizes[i] < 1
|
|
89
|
+
raise ArgumentError, "blocks: tile size on axis #{i} must be >= 1"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Absorb a leading offset with a zero-copy pre-slice, so the tile geometry
|
|
94
|
+
# below always starts at index 0.
|
|
95
|
+
@source =
|
|
96
|
+
if offsets.all?(&:zero?)
|
|
97
|
+
source
|
|
98
|
+
else
|
|
99
|
+
source[*offsets.map { |o| o..-1 }]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
n = @source.shape
|
|
103
|
+
@q = @sndim.times.map { |i| n[i] / @sizes[i] } # full tiles per axis
|
|
104
|
+
@r = @sndim.times.map { |i| n[i] % @sizes[i] } # remainder per axis
|
|
105
|
+
|
|
106
|
+
# Ceil tile grid: a partial edge tile adds one grid cell on that axis.
|
|
107
|
+
@shape = @sndim.times.map { |i| @q[i] + (@r[i] > 0 ? 1 : 0) }
|
|
108
|
+
@ndim = @shape.size
|
|
109
|
+
|
|
110
|
+
# Trailing tile axes of a block_view: [ndim .. 2*ndim-1].
|
|
111
|
+
@tile_axes = (@sndim...(2 * @sndim)).to_a
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @overload source
|
|
116
|
+
# Returns the array being tiled (with any leading offset already applied).
|
|
117
|
+
# @return [CArray]
|
|
118
|
+
attr_reader :source
|
|
119
|
+
|
|
120
|
+
# ---- region decomposition ---------------------------------------------
|
|
121
|
+
#
|
|
122
|
+
# The tile grid splits into regions by whether each axis sits on its interior
|
|
123
|
+
# (full tiles, extent q_i) or its boundary (the one partial edge tile). A
|
|
124
|
+
# region is the Cartesian product of these per-axis choices; the all-interior
|
|
125
|
+
# choice is the interior region, the rest are the 2^m - 1 boundary regions.
|
|
126
|
+
# Each region is a plain block_view whose tile size on an axis is the full
|
|
127
|
+
# size (interior) or the remainder (boundary) — always dividing the region's
|
|
128
|
+
# extent, so block_view never rejects.
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
# Yield each region as (strip_ranges, tiles, out_ranges): the source slice to
|
|
133
|
+
# tile, the per-axis tile sizes, and the sub-block of the output grid it fills.
|
|
134
|
+
def each_region
|
|
135
|
+
axis_choices = (0...@sndim).map do |i|
|
|
136
|
+
ch = []
|
|
137
|
+
ch << :interior if @q[i] > 0
|
|
138
|
+
ch << :boundary if @r[i] > 0
|
|
139
|
+
ch
|
|
140
|
+
end
|
|
141
|
+
combos = axis_choices[0].product(*axis_choices[1..-1])
|
|
142
|
+
combos.each do |choice|
|
|
143
|
+
strip_ranges = []
|
|
144
|
+
tiles = []
|
|
145
|
+
out_ranges = []
|
|
146
|
+
(0...@sndim).each do |i|
|
|
147
|
+
base = @q[i] * @sizes[i]
|
|
148
|
+
if choice[i] == :interior
|
|
149
|
+
strip_ranges << (0...base)
|
|
150
|
+
tiles << @sizes[i]
|
|
151
|
+
out_ranges << (0...@q[i])
|
|
152
|
+
else
|
|
153
|
+
strip_ranges << (base...(base + @r[i]))
|
|
154
|
+
tiles << @r[i]
|
|
155
|
+
out_ranges << (@q[i]...(@q[i] + 1))
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
yield strip_ranges, tiles, out_ranges
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Reduce every region with the yielded block (a block_view over the region
|
|
163
|
+
# and its tile sizes) and scatter the region results into the ceil tile grid.
|
|
164
|
+
# The block returns one CArray, or an Array of CArrays (minmax / percentile /
|
|
165
|
+
# quantile) which are assembled into that many grids. Boundary regions use a
|
|
166
|
+
# smaller (unmasked, ragged) block_view whose present cell count is naturally
|
|
167
|
+
# below a full tile, so `min_count:` marks them UNDEF with no masking. The
|
|
168
|
+
# output dtype is seeded from the first region (all regions share it).
|
|
169
|
+
def assemble
|
|
170
|
+
outs = nil
|
|
171
|
+
each_region do |strip_ranges, tiles, out_ranges|
|
|
172
|
+
view = @source[*strip_ranges].block_view(*tiles)
|
|
173
|
+
res = yield(view, tiles)
|
|
174
|
+
if res.is_a?(Array)
|
|
175
|
+
outs ||= res.map { |r| CArray.new(r.data_type, @shape) }
|
|
176
|
+
res.each_index { |k| outs[k][*out_ranges] = res[k] }
|
|
177
|
+
else
|
|
178
|
+
outs ||= CArray.new(res.data_type, @shape)
|
|
179
|
+
outs[*out_ranges] = res
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
outs
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Fold every region with a single core reduction `op` (the tier-1 shape).
|
|
186
|
+
def fold (op, **kw)
|
|
187
|
+
assemble { |view, _tiles| view.send(op, axis: @tile_axes, **kw) }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
public
|
|
191
|
+
|
|
192
|
+
# ---- named reductions (core delegation, drift zero) -------------------
|
|
193
|
+
#
|
|
194
|
+
# A per-tile fold to one value over the trailing tile axes is exactly a core
|
|
195
|
+
# per-axis reduction over those axes, so every reduction delegates to
|
|
196
|
+
# `block_view.<op>(axis: tile_axes, ...)` per region. This inherits the core
|
|
197
|
+
# dtype, mask, empty / all-masked (identity vs UNDEF) and epsilon-close
|
|
198
|
+
# contracts unchanged. `min_count:` / `fill_value:` pass straight to the core.
|
|
199
|
+
|
|
200
|
+
# @overload sum(min_count: nil, fill_value: nil)
|
|
201
|
+
# Per-tile sum. @return [CArray] tile-grid shaped
|
|
202
|
+
# The rest are analogous: prod / mean / min / max, sample and population
|
|
203
|
+
# variance / stddev, all / any.
|
|
204
|
+
[:sum, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
|
|
205
|
+
:variancep, :stddevp].each do |op|
|
|
206
|
+
define_method(op) do |min_count: nil, fill_value: nil|
|
|
207
|
+
kw = {}
|
|
208
|
+
kw[:min_count] = min_count unless min_count.nil?
|
|
209
|
+
kw[:fill_value] = fill_value unless fill_value.nil?
|
|
210
|
+
fold(op, **kw)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# ---- count family and elements ----------------------------------------
|
|
215
|
+
|
|
216
|
+
# @overload count(v = <none>)
|
|
217
|
+
# Per-tile count. No argument counts present (non-masked) cells (fewer at a
|
|
218
|
+
# partial edge tile); `count(UNDEF)` counts masked cells; `count(v)` counts
|
|
219
|
+
# cells equal to `v`.
|
|
220
|
+
# @return [CArray] tile-grid shaped
|
|
221
|
+
def count (*args)
|
|
222
|
+
return count_not_masked if args.empty?
|
|
223
|
+
out = nil
|
|
224
|
+
each_region do |strip_ranges, tiles, out_ranges|
|
|
225
|
+
view = @source[*strip_ranges].block_view(*tiles)
|
|
226
|
+
# block_view is a CAStride, so #count is not shadowed; dispatch
|
|
227
|
+
# CArray#count explicitly anyway, matching the family regularity.
|
|
228
|
+
red = CArray.instance_method(:count).bind_call(view, *args, axis: @tile_axes)
|
|
229
|
+
out ||= CArray.new(red.data_type, @shape)
|
|
230
|
+
out[*out_ranges] = red
|
|
231
|
+
end
|
|
232
|
+
out
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# @overload count_not_masked
|
|
236
|
+
# Per-tile count of present (non-masked) cells. For a partial edge tile
|
|
237
|
+
# this is the real cell count (the OOB cells are not present).
|
|
238
|
+
# @return [CArray]
|
|
239
|
+
def count_not_masked
|
|
240
|
+
fold(:count_not_masked)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# @overload count_masked
|
|
244
|
+
# Per-tile count of masked cells. Equals `elements - count_not_masked`;
|
|
245
|
+
# the OOB cells of a partial edge tile count here.
|
|
246
|
+
# @return [CArray]
|
|
247
|
+
def count_masked
|
|
248
|
+
elements - count_not_masked
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# @overload elements
|
|
252
|
+
# Tile cell count (structural, mask-independent): the constant tile size
|
|
253
|
+
# `Π b_i`, shaped like the tile grid. Every tile — including a partial
|
|
254
|
+
# edge tile — reports the full size; how many of those cells are real is
|
|
255
|
+
# `count_not_masked`, and `elements = count_not_masked + count_masked`.
|
|
256
|
+
# @return [CArray]
|
|
257
|
+
def elements
|
|
258
|
+
sz = @sizes.inject(1) { |p, b| p * b }
|
|
259
|
+
out = CArray.int64(*@shape)
|
|
260
|
+
out[] = sz
|
|
261
|
+
out
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# ---- tier 2: minmax, position, weighted -------------------------------
|
|
265
|
+
|
|
266
|
+
# @overload minmax(min_count: nil, fill_value: nil)
|
|
267
|
+
# Per-tile `[min, max]` (two tile-grid-shaped CArrays, a single fused pass).
|
|
268
|
+
# @return [Array<CArray>]
|
|
269
|
+
def minmax (min_count: nil, fill_value: nil)
|
|
270
|
+
kw = {}
|
|
271
|
+
kw[:min_count] = min_count unless min_count.nil?
|
|
272
|
+
kw[:fill_value] = fill_value unless fill_value.nil?
|
|
273
|
+
assemble { |view, _| view.minmax(axis: @tile_axes, **kw) }
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# @overload min_index
|
|
277
|
+
# Per-tile position of the minimum, as a flat index within the tile (a
|
|
278
|
+
# partial edge tile indexes within its own present cells).
|
|
279
|
+
# @return [CArray] tile-grid shaped
|
|
280
|
+
# @overload max_index
|
|
281
|
+
# Per-tile position of the maximum (tile-local flat index).
|
|
282
|
+
# @return [CArray]
|
|
283
|
+
[:min_index, :max_index].each do |op|
|
|
284
|
+
define_method(op) { assemble { |view, _| view.send(op, axis: @tile_axes) } }
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# @overload min_addr
|
|
288
|
+
# Per-tile flat SOURCE address of the minimum — which cell of the source
|
|
289
|
+
# holds it, so `source.reshape(source.elements)[bi.min_addr]` are the tile
|
|
290
|
+
# minima. Unlike `min_index` (the tile-local position) this indexes back
|
|
291
|
+
# into the original array. An all-masked tile is a masked cell.
|
|
292
|
+
# @return [CArray] tile-grid shaped int64
|
|
293
|
+
def min_addr; winner_addr(:min_index); end
|
|
294
|
+
|
|
295
|
+
# @overload max_addr
|
|
296
|
+
# Per-tile flat source address of the maximum. See {#min_addr}.
|
|
297
|
+
# @return [CArray] tile-grid shaped int64
|
|
298
|
+
def max_addr; winner_addr(:max_index); end
|
|
299
|
+
|
|
300
|
+
private
|
|
301
|
+
|
|
302
|
+
# Per-tile source address of the winner. The tile-local flat index (min_index /
|
|
303
|
+
# max_index) is looked up in a source-address companion tiled the same way, so
|
|
304
|
+
# the tile geometry is reused rather than recomputed. An all-masked tile yields
|
|
305
|
+
# a masked index; it is gathered at 0 and re-masked in the result.
|
|
306
|
+
def winner_addr (idx_op)
|
|
307
|
+
saddr = CArray.int64(*@source.shape).seq!
|
|
308
|
+
out = nil
|
|
309
|
+
each_region do |strip_ranges, tiles, out_ranges|
|
|
310
|
+
vview = @source[*strip_ranges].block_view(*tiles)
|
|
311
|
+
sview = saddr[*strip_ranges].block_view(*tiles)
|
|
312
|
+
mi = vview.send(idx_op, axis: @tile_axes) # tile-local flat index
|
|
313
|
+
grid = (0...@sndim).map { |i| vview.shape[i] }
|
|
314
|
+
cells = tiles.inject(1) { |p, t| p * t }
|
|
315
|
+
safe = mi.copy
|
|
316
|
+
safe[mi.is_masked.eq(1)] = 0 if mi.has_mask? # placeholder for the gather
|
|
317
|
+
picked = sview.reshape(*(grid + [cells]))
|
|
318
|
+
.take_along_axis(safe.reshape(*(grid + [1])), axis: @sndim)
|
|
319
|
+
.reshape(*grid)
|
|
320
|
+
picked[mi.is_masked.eq(1)] = UNDEF if mi.has_mask? # re-mask the empty tiles
|
|
321
|
+
out ||= CArray.int64(*@shape)
|
|
322
|
+
out[*out_ranges] = picked
|
|
323
|
+
end
|
|
324
|
+
out
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
public
|
|
328
|
+
|
|
329
|
+
# @overload sort_addr
|
|
330
|
+
# Per-tile sort by flat SOURCE address, source-shaped: each tile's cells hold
|
|
331
|
+
# that tile's source addresses in ascending-value order (reading the tile
|
|
332
|
+
# row-major gives the sorted addresses), so
|
|
333
|
+
# `source.reshape(source.elements)[bi.sort_addr]` is the source sorted within
|
|
334
|
+
# each tile. Multi-axis tiles are flattened (as for the order statistics). A
|
|
335
|
+
# partial edge tile sorts only its present cells. Masked values sort to the
|
|
336
|
+
# tail of their tile (as `CArray#sort`).
|
|
337
|
+
# @return [CArray] source-shaped int64
|
|
338
|
+
def sort_addr
|
|
339
|
+
saddr = CArray.int64(*@source.shape).seq!
|
|
340
|
+
out = CArray.int64(*@source.shape)
|
|
341
|
+
out[] = UNDEF
|
|
342
|
+
each_region do |strip_ranges, tiles, _out_ranges|
|
|
343
|
+
vview = @source[*strip_ranges].block_view(*tiles)
|
|
344
|
+
sview = saddr[*strip_ranges].block_view(*tiles)
|
|
345
|
+
grid = (0...@sndim).map { |i| vview.shape[i] }
|
|
346
|
+
cells = tiles.inject(1) { |p, t| p * t }
|
|
347
|
+
order = vview.copy.reshape(*(grid + [cells])).sort_addr(axis: @sndim)
|
|
348
|
+
src_sorted = sview.reshape(*(grid + [cells])).take_along_axis(order, axis: @sndim)
|
|
349
|
+
out[*strip_ranges].block_view(*tiles)[] = src_sorted.reshape(*(grid + tiles))
|
|
350
|
+
end
|
|
351
|
+
out
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# @overload wsum(weights)
|
|
355
|
+
# Per-tile weighted sum; `weights` is shaped like one full tile (`Π b_i`).
|
|
356
|
+
# At a partial edge tile the weight kernel is sliced to the present cells.
|
|
357
|
+
# @return [CArray]
|
|
358
|
+
def wsum (weights)
|
|
359
|
+
weighted(weights) { |view, w| view.wsum(w, axis: @tile_axes) }
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# @overload wmean(weights)
|
|
363
|
+
# Per-tile weighted mean; `weights` shaped like one full tile.
|
|
364
|
+
# @return [CArray]
|
|
365
|
+
def wmean (weights)
|
|
366
|
+
weighted(weights) { |view, w| view.wmean(w, axis: @tile_axes) }
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
private
|
|
370
|
+
|
|
371
|
+
# Broadcast a full-tile weight kernel over each region (sliced to the region's
|
|
372
|
+
# possibly-ragged tile) and yield (view, broadcast weights) for the reduction.
|
|
373
|
+
def weighted (weights)
|
|
374
|
+
unless weights.shape == @sizes
|
|
375
|
+
raise ArgumentError,
|
|
376
|
+
"wsum/wmean: weights shape #{weights.shape.inspect} != " \
|
|
377
|
+
"tile shape #{@sizes.inspect}"
|
|
378
|
+
end
|
|
379
|
+
assemble do |view, tiles|
|
|
380
|
+
wt = tiles == @sizes ? weights : weights[*tiles.map { |t| 0...t }]
|
|
381
|
+
wshape = ([1] * @sndim) + tiles
|
|
382
|
+
# Pass the weights through unchanged, like the window iterator: core wsum /
|
|
383
|
+
# wmean own weight/dtype coercion, so do not pre-coerce the weights here.
|
|
384
|
+
wfull = wt.reshape(*wshape).broadcast_to(*view.shape)
|
|
385
|
+
yield view, wfull
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
public
|
|
390
|
+
|
|
391
|
+
# ---- tier 3: order statistics (median / percentile / quantile) --------
|
|
392
|
+
#
|
|
393
|
+
# Core per-axis order statistics take a single integer axis, so a 1-D tile
|
|
394
|
+
# delegates straight to the C path; a multi-axis tile is materialized per
|
|
395
|
+
# region and its tile axes flattened into one before the single-axis core
|
|
396
|
+
# order-stat. A masked source raises (the known per-axis masked limitation);
|
|
397
|
+
# strip the mask with `ca.value` first if needed.
|
|
398
|
+
|
|
399
|
+
# @overload median
|
|
400
|
+
# Per-tile median. @return [CArray]
|
|
401
|
+
def median
|
|
402
|
+
order_stat { |v, axis| v.median(axis: axis) }
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# @overload percentile(*pers)
|
|
406
|
+
# Per-tile percentile(s). One argument returns one CArray, several an array
|
|
407
|
+
# of CArrays (as `CArray#percentile`).
|
|
408
|
+
# @return [CArray, Array<CArray>]
|
|
409
|
+
def percentile (*pers)
|
|
410
|
+
order_stat { |v, axis| v.percentile(*pers, axis: axis) }
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# @overload quantile
|
|
414
|
+
# Per-tile five-number summary `[min, Q1, median, Q3, max]` (five CArrays).
|
|
415
|
+
# @return [Array<CArray>]
|
|
416
|
+
def quantile
|
|
417
|
+
order_stat { |v, axis| v.quantile(axis: axis) }
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
private
|
|
421
|
+
|
|
422
|
+
# Drive an order statistic (yielded as `block.call(view, axis)`) per region:
|
|
423
|
+
# a 1-D tile passes the tile axis straight through; a multi-axis tile is
|
|
424
|
+
# materialized (peak O(region cells)) and its tile axes flattened into one.
|
|
425
|
+
def order_stat (&op)
|
|
426
|
+
assemble do |view, tiles|
|
|
427
|
+
if @sndim == 1
|
|
428
|
+
op.call(view, @tile_axes[0])
|
|
429
|
+
else
|
|
430
|
+
grid = (0...@sndim).map { |i| view.shape[i] }
|
|
431
|
+
cells = tiles.inject(1) { |p, t| p * t }
|
|
432
|
+
op.call(view.copy.reshape(*(grid + [cells])), @sndim)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
public
|
|
438
|
+
|
|
439
|
+
# ---- generic iteration (escape hatch, slow) ---------------------------
|
|
440
|
+
#
|
|
441
|
+
# `each` / `reduce` yield each tile as a uniform `Π b_i`-shaped CArray whose
|
|
442
|
+
# out-of-bounds cells (at a partial edge tile) are masked, so the user never
|
|
443
|
+
# sees a ragged shape — the remainder is absorbed into the mask. This uses a
|
|
444
|
+
# padded (masked-margin) entity, materialised once, so it is the slow path;
|
|
445
|
+
# named reductions above stay on the zero-copy region engine. `each` is the
|
|
446
|
+
# receptacle for statistics not in the named surface.
|
|
447
|
+
|
|
448
|
+
# @overload each { |tile| ... }
|
|
449
|
+
# Yields each tile as a uniform `Π b_i`-shaped CArray (partial edge tiles
|
|
450
|
+
# have their out-of-bounds cells masked). Without a block, returns an
|
|
451
|
+
# Enumerator. Per-tile materialize, slow; use a named reduction for speed.
|
|
452
|
+
# @yieldparam tile [CArray]
|
|
453
|
+
# @return [Enumerator, self]
|
|
454
|
+
def each
|
|
455
|
+
return to_enum(:each) unless block_given?
|
|
456
|
+
tgv = tile_grid_view
|
|
457
|
+
nils = Array.new(@sndim, nil)
|
|
458
|
+
CArray.each_index(*@shape) { |*g| yield tgv[*g, *nils] }
|
|
459
|
+
self
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# @overload reduce { |tile| ... }
|
|
463
|
+
# Custom per-tile reduction: the block receives each tile (a CArray, masked
|
|
464
|
+
# at partial edges) and returns one value per tile. The escape hatch for
|
|
465
|
+
# statistics not in the named surface.
|
|
466
|
+
# @yieldparam tile [CArray]
|
|
467
|
+
# @return [CArray] tile-grid shaped
|
|
468
|
+
# @overload reduce(init) { |acc, elem| ... }
|
|
469
|
+
# Per-tile fiber fold: each tile's cells are folded element by element from
|
|
470
|
+
# `init` (masked cells are skipped by CArray#each).
|
|
471
|
+
# @param init [Object] initial accumulator.
|
|
472
|
+
# @return [CArray]
|
|
473
|
+
def reduce (*args, data_type: nil, &blk)
|
|
474
|
+
raise LocalJumpError, "no block given (yield)" unless blk
|
|
475
|
+
out = CArray.new(data_type || CA_OBJECT, @shape)
|
|
476
|
+
tgv = tile_grid_view
|
|
477
|
+
nils = Array.new(@sndim, nil)
|
|
478
|
+
if args.empty?
|
|
479
|
+
CArray.each_index(*@shape) { |*g| out[*g] = blk.call(tgv[*g, *nils]) }
|
|
480
|
+
else
|
|
481
|
+
init = args[0]
|
|
482
|
+
CArray.each_index(*@shape) do |*g|
|
|
483
|
+
acc = init
|
|
484
|
+
tgv[*g, *nils].each { |e| acc = blk.call(acc, e) }
|
|
485
|
+
out[*g] = acc
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
out
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# @overload map { |tile| ... }
|
|
492
|
+
# Per-tile element-wise transform: the block receives each tile (a uniform
|
|
493
|
+
# `Π b_i`-shaped CArray, masked at partial edges) and returns a same-shaped
|
|
494
|
+
# tile (or a scalar to broadcast). The transformed tiles are scattered back
|
|
495
|
+
# into a source-shaped CArray (the out-of-bounds cells of a partial edge
|
|
496
|
+
# tile are dropped). Well-defined because tiles do not overlap; the source
|
|
497
|
+
# is not modified (a new array is returned). Without a block, an Enumerator.
|
|
498
|
+
# @yieldparam tile [CArray]
|
|
499
|
+
# @return [CArray, Enumerator] source-shaped
|
|
500
|
+
def map
|
|
501
|
+
return to_enum(:map) unless block_given?
|
|
502
|
+
pout = CArray.new(padded_source.data_type, padded_source.shape)
|
|
503
|
+
pgv = pout.block_view(*@sizes)
|
|
504
|
+
tgv = tile_grid_view
|
|
505
|
+
nils = Array.new(@sndim, nil)
|
|
506
|
+
CArray.each_index(*@shape) { |*g| pgv[*g, *nils] = yield(tgv[*g, *nils]) }
|
|
507
|
+
pout[*@sndim.times.map { |i| 0...@source.shape[i] }].copy
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# ---- segment scan: within-tile running statistics ---------------------
|
|
511
|
+
#
|
|
512
|
+
# A tile is a partition (each cell is in exactly one tile), so a per-cell
|
|
513
|
+
# running statistic is well-defined: each tile accumulates in its internal
|
|
514
|
+
# row-major order. Each tile is flattened (row-major), scanned by the core
|
|
515
|
+
# value scan, reshaped back, and scattered into a source-shaped result --
|
|
516
|
+
# reusing the same padded (masked-margin) entity as the iterate escape
|
|
517
|
+
# hatches, so this is the slow path (a per-tile materialize), and the
|
|
518
|
+
# out-of-bounds cells of a partial edge tile are dropped from the result.
|
|
519
|
+
# cumsum / cumprod -> float64, cummax / cummin preserve the value dtype,
|
|
520
|
+
# cumcount -> int64 running count of present cells; the output dtype is seeded
|
|
521
|
+
# from the first tile's scan.
|
|
522
|
+
|
|
523
|
+
# @overload cumsum
|
|
524
|
+
# Per-tile inclusive running sum (float64), source-shaped.
|
|
525
|
+
# @return [CArray]
|
|
526
|
+
# @overload cumprod
|
|
527
|
+
# Per-tile inclusive running product (float64), source-shaped.
|
|
528
|
+
# @return [CArray]
|
|
529
|
+
# @overload cummax
|
|
530
|
+
# Per-tile inclusive running maximum (value dtype), source-shaped.
|
|
531
|
+
# @return [CArray]
|
|
532
|
+
# @overload cummin
|
|
533
|
+
# Per-tile inclusive running minimum (value dtype), source-shaped.
|
|
534
|
+
# @return [CArray]
|
|
535
|
+
# @overload cumcount
|
|
536
|
+
# Per-tile running count of present cells (int64), source-shaped.
|
|
537
|
+
# @return [CArray]
|
|
538
|
+
[:cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |op|
|
|
539
|
+
define_method(op) { block_scan(op) }
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
private
|
|
543
|
+
|
|
544
|
+
# Drive a within-tile segment scan: scan each tile flattened row-major and
|
|
545
|
+
# scatter back to a source-shaped result. The output dtype is taken from the
|
|
546
|
+
# first tile's scan (all tiles share it); the padded margin is UNDEF and the
|
|
547
|
+
# OOB cells of a partial edge tile are cropped from the result.
|
|
548
|
+
def block_scan (op)
|
|
549
|
+
tgv = tile_grid_view
|
|
550
|
+
nils = Array.new(@sndim, nil)
|
|
551
|
+
pout = nil
|
|
552
|
+
pgv = nil
|
|
553
|
+
CArray.each_index(*@shape) do |*g|
|
|
554
|
+
tile = tgv[*g, *nils]
|
|
555
|
+
scanned = tile.copy.reshape(tile.elements).send(op).reshape(*@sizes)
|
|
556
|
+
unless pout
|
|
557
|
+
pout = CArray.new(scanned.data_type, padded_source.shape)
|
|
558
|
+
pout[] = UNDEF
|
|
559
|
+
pgv = pout.block_view(*@sizes)
|
|
560
|
+
end
|
|
561
|
+
pgv[*g, *nils] = scanned
|
|
562
|
+
end
|
|
563
|
+
pout[*@sndim.times.map { |i| 0...@source.shape[i] }].copy
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
# A padded entity whose interior holds the source and whose partial-edge
|
|
567
|
+
# margin is masked (UNDEF), sized to exactly cover the ceil tile grid. When
|
|
568
|
+
# the source already divides evenly this is the source itself (no copy), so
|
|
569
|
+
# the tiles alias it. Memoised; feeds the iterate escape hatches.
|
|
570
|
+
def padded_source
|
|
571
|
+
@padded_source ||= begin
|
|
572
|
+
pshape = @sndim.times.map { |i| @shape[i] * @sizes[i] }
|
|
573
|
+
if pshape == @source.shape.to_a
|
|
574
|
+
@source
|
|
575
|
+
else
|
|
576
|
+
pad = CArray.new(@source.data_type, pshape)
|
|
577
|
+
pad[] = UNDEF
|
|
578
|
+
pad[*@sndim.times.map { |i| 0...@source.shape[i] }] = @source
|
|
579
|
+
pad
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# block_view of the padded entity: shape [g_0..g_{n-1}, b_0..b_{n-1}], so
|
|
585
|
+
# `view[*grid_index, *nils]` is one uniform tile.
|
|
586
|
+
def tile_grid_view
|
|
587
|
+
@tile_grid_view ||= padded_source.block_view(*@sizes)
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
class CArray
|
|
593
|
+
# @overload blocks(*blocks)
|
|
594
|
+
# Returns a {CABlockIterator} tiling `self` with non-overlapping tiles of a
|
|
595
|
+
# per-axis size. Each argument is an Integer tile size (offset 0) or a
|
|
596
|
+
# `lo..hi` range (length = tile size, start = leading offset). The
|
|
597
|
+
# remainder is covered by present-only edge tiles (ceil tile grid); slice
|
|
598
|
+
# first for "valid" tiling.
|
|
599
|
+
# @param blocks [Array<Integer, Range>] per-axis tile sizes (or ranges).
|
|
600
|
+
# @return [CABlockIterator]
|
|
601
|
+
def blocks (*blocks)
|
|
602
|
+
CABlockIterator.new(self, *blocks)
|
|
603
|
+
end
|
|
604
|
+
end
|