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,655 @@
|
|
|
1
|
+
# ----------------------------------------------------------------------------
|
|
2
|
+
#
|
|
3
|
+
# carray/window_iterator.rb
|
|
4
|
+
#
|
|
5
|
+
# CAWindowIterator — a rolling (sliding-window) reduction dispatcher, the
|
|
6
|
+
# Window member of the 3.0 iterator family (sibling of CASlabIterator /
|
|
7
|
+
# CACategoricalIterator). Where a slab iterator folds each non-overlapping
|
|
8
|
+
# slab, a window iterator folds an OVERLAPPING window centred on every anchor
|
|
9
|
+
# cell, so the output is shaped like the source (a rolling result) rather
|
|
10
|
+
# than an outer shape.
|
|
11
|
+
#
|
|
12
|
+
# sw = a.windows(-1..1) # width-3 window per anchor
|
|
13
|
+
# sw.mean # rolling mean, shaped like a
|
|
14
|
+
# sw.correlate(kernel) # bounded cross-correlation
|
|
15
|
+
# sw.convolve(kernel) # bounded convolution (kernel flipped)
|
|
16
|
+
#
|
|
17
|
+
# Engine: build a padded entity once (source copied into the interior, the
|
|
18
|
+
# margins filled by the boundary policy), take its `sliding_windows` view
|
|
19
|
+
# (a pure strided view over the padded buffer), and run a core reduction over
|
|
20
|
+
# the trailing window axes. One vectorized pass; the named reductions
|
|
21
|
+
# delegate straight to the core reduction, so their dtype / mask / empty
|
|
22
|
+
# (ERI) / epsilon contracts are the core's, unchanged. This replaces the 2.0
|
|
23
|
+
# per-anchor C engine (ext/ca_iter_window.c, retired).
|
|
24
|
+
#
|
|
25
|
+
# Boundary policy is chosen at construction with `bounds:`:
|
|
26
|
+
#
|
|
27
|
+
# :skip (default) UNDEF margin; a window near the edge folds only its
|
|
28
|
+
# in-bounds cells (masked pad cells are skipped by the core
|
|
29
|
+
# reduction). Output is reference-shaped.
|
|
30
|
+
# :nearest edge-replicated margin (the nearest source cell extends
|
|
31
|
+
# outward). Output is reference-shaped, margin cells are real.
|
|
32
|
+
# :truncate no pad; only fully in-bounds anchors are produced, so the
|
|
33
|
+
# output shrinks to `N_i - w_i + 1` per axis. Zero-copy (the
|
|
34
|
+
# source's own sliding_windows view), the valid-convolution
|
|
35
|
+
# mode.
|
|
36
|
+
#
|
|
37
|
+
# How the boundary spectrum lands on the core reduction: with :skip the
|
|
38
|
+
# margin is UNDEF, so `min_count:` (require this many present cells) and
|
|
39
|
+
# `fill_value:` (replace an UNDEF result) — both passed straight through to
|
|
40
|
+
# the core reduction — express the full spectrum from "fold whatever is
|
|
41
|
+
# present" to "full windows only, edges filled". There is no window-specific
|
|
42
|
+
# strictness knob.
|
|
43
|
+
#
|
|
44
|
+
# The class name is kept from 2.0 (the concept — a window — is stable);
|
|
45
|
+
# this is the Ruby family member that supersedes the C engine. Loaded
|
|
46
|
+
# lazily via autoload from
|
|
47
|
+
# lib/carray/autoload_carray.rb the first time `a.windows(...)` is used.
|
|
48
|
+
#
|
|
49
|
+
# ----------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
require "carray"
|
|
52
|
+
|
|
53
|
+
# Rolling (sliding-window) reduction dispatcher — the Window member of the
|
|
54
|
+
# iterator family (sibling of `CASlabIterator` / {CABlockIterator} /
|
|
55
|
+
# `CACategoricalIterator`). It folds an overlapping window centred on every
|
|
56
|
+
# anchor cell, so the result is shaped like the source rather than reduced.
|
|
57
|
+
#
|
|
58
|
+
# Obtained from `CArray#windows`, not constructed directly.
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# sw = a.windows(-1..1) # width-3 window per anchor
|
|
62
|
+
# sw.mean # rolling mean, shaped like a
|
|
63
|
+
# sw.correlate(kernel) # bounded cross-correlation
|
|
64
|
+
class CAWindowIterator < CAIterator
|
|
65
|
+
|
|
66
|
+
# @overload initialize(source, ranges, bounds: :skip, fill_value: nil)
|
|
67
|
+
# Builds a window iterator over `source` with a per-axis offset range.
|
|
68
|
+
# Each `ranges[i]` is a `lo..hi` giving the window's offset span around
|
|
69
|
+
# an anchor (`a.windows(-1..1)` is a centred width-3 window; `0..2` is
|
|
70
|
+
# forward-looking). `bounds:` selects the margin policy (`:skip` /
|
|
71
|
+
# `:nearest` / `:truncate`). `fill_value:` is a constant margin value
|
|
72
|
+
# (an escape for `:constant` padding); when given it overrides `:skip`.
|
|
73
|
+
#
|
|
74
|
+
# For backward compatibility `initialize(window_view)` accepts a CAWindow
|
|
75
|
+
# view (the old `CAWindowIterator.new(a.window(...))` form): the geometry
|
|
76
|
+
# (offset ranges, bounds, fill value) is read back from the view.
|
|
77
|
+
#
|
|
78
|
+
# @param source [CArray, CAWindow] the array to roll over, or a CAWindow
|
|
79
|
+
# view to read the geometry from.
|
|
80
|
+
# @param ranges [Array<Range>] per-axis offset ranges.
|
|
81
|
+
# @param bounds [Symbol] `:skip` / `:nearest` / `:truncate`.
|
|
82
|
+
# @param fill_value [Object, nil] constant margin value, overriding :skip.
|
|
83
|
+
def initialize (source, *ranges, bounds: :skip, fill_value: nil)
|
|
84
|
+
if source.is_a?(CArray) && source.obj_type == CA_OBJ_WINDOW
|
|
85
|
+
# Backward-compat: read geometry from a CAWindow view built by #window.
|
|
86
|
+
# start[i] = lo, dim[i] (window width) = w, so hi = lo + w - 1.
|
|
87
|
+
win = source
|
|
88
|
+
@source = win.parent
|
|
89
|
+
widths = win.count
|
|
90
|
+
@ranges = win.start.each_with_index.map { |lo, i| lo..(lo + widths[i] - 1) }
|
|
91
|
+
# The legacy #window default is FILL (constant), whose value is the
|
|
92
|
+
# view's fill_value; map that to a :constant margin.
|
|
93
|
+
@bounds = :constant
|
|
94
|
+
@fill_value = win.fill_value
|
|
95
|
+
else
|
|
96
|
+
@source = source
|
|
97
|
+
@ranges = ranges.flatten(0)
|
|
98
|
+
@bounds = bounds
|
|
99
|
+
@fill_value = fill_value
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
unless @ranges.size == @source.ndim
|
|
103
|
+
raise ArgumentError,
|
|
104
|
+
"windows: expected #{@source.ndim} ranges (one per axis), " \
|
|
105
|
+
"got #{@ranges.size}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
@sndim = @source.ndim
|
|
109
|
+
@widths = @ranges.map { |r| r.end - r.begin + 1 }
|
|
110
|
+
@lefts = @ranges.map { |r| [0, -r.begin].max } # left margin per axis
|
|
111
|
+
@rights = @ranges.map { |r| [0, r.end ].max } # right margin per axis
|
|
112
|
+
|
|
113
|
+
# A constant fill_value: overrides :skip (constant margin escape hatch).
|
|
114
|
+
@bounds = :constant if @fill_value != nil && @bounds == :skip
|
|
115
|
+
|
|
116
|
+
# Trailing window axes of the sliding_windows view: [ndim .. 2*ndim-1].
|
|
117
|
+
@window_axes = (@sndim...(2 * @sndim)).to_a
|
|
118
|
+
|
|
119
|
+
# Output iteration space (reference-shaped, except :truncate which shrinks).
|
|
120
|
+
rshape = @source.shape
|
|
121
|
+
if @bounds == :truncate
|
|
122
|
+
@shape = @sndim.times.map { |i| rshape[i] - @widths[i] + 1 }
|
|
123
|
+
else
|
|
124
|
+
@shape = rshape.dup
|
|
125
|
+
end
|
|
126
|
+
@ndim = @shape.size
|
|
127
|
+
self
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @overload source
|
|
131
|
+
# Returns the array being rolled over.
|
|
132
|
+
# @return [CArray]
|
|
133
|
+
attr_reader :source
|
|
134
|
+
|
|
135
|
+
# @overload bounds
|
|
136
|
+
# Returns the boundary policy symbol.
|
|
137
|
+
# @return [Symbol]
|
|
138
|
+
attr_reader :bounds
|
|
139
|
+
|
|
140
|
+
# ---- padded entity + sliding_windows view -----------------------------
|
|
141
|
+
#
|
|
142
|
+
# The engine: pad the source once (:skip -> UNDEF, :nearest -> edge, :constant
|
|
143
|
+
# -> value; :truncate does not pad), then take a sliding_windows view. Both
|
|
144
|
+
# are memoised: every named reduction reuses the one pad + one view.
|
|
145
|
+
|
|
146
|
+
# @overload sliding_view
|
|
147
|
+
# Returns the sliding_windows view feeding the reductions. For :truncate
|
|
148
|
+
# this is the source's own view (zero-copy); otherwise it is the view over
|
|
149
|
+
# the padded entity.
|
|
150
|
+
# @return [CArray]
|
|
151
|
+
def sliding_view
|
|
152
|
+
@sliding_view ||= padded_entity.sliding_windows(*@widths)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
# Build the padded entity (or, for :truncate, the source itself). Memoised.
|
|
158
|
+
def padded_entity
|
|
159
|
+
@padded_entity ||=
|
|
160
|
+
if @bounds == :truncate
|
|
161
|
+
@source
|
|
162
|
+
else
|
|
163
|
+
pad_source(@source, @lefts, @rights, pad_mode, @fill_value)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def pad_mode
|
|
168
|
+
case @bounds
|
|
169
|
+
when :skip then :masked
|
|
170
|
+
when :nearest then :edge
|
|
171
|
+
when :constant then :constant
|
|
172
|
+
else
|
|
173
|
+
raise ArgumentError,
|
|
174
|
+
"windows: unknown bounds #{@bounds.inspect} " \
|
|
175
|
+
"(expected :skip, :nearest, :truncate)"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Private pad helper: build a padded entity from `src` with per-axis
|
|
180
|
+
# (left, right) margins and one of three fill modes. Not a public
|
|
181
|
+
# CArray#pad -- window construction and convolution use it internally, the
|
|
182
|
+
# escape hatches yield from the padded entity, so users never call it. A
|
|
183
|
+
# standalone public CArray#pad is a possible future promotion.
|
|
184
|
+
#
|
|
185
|
+
# :constant margin cells set to `value` (0 when value is nil).
|
|
186
|
+
# :edge margin cells replicate the nearest source edge cell.
|
|
187
|
+
# :masked margin cells are UNDEF (masked); reductions skip them.
|
|
188
|
+
#
|
|
189
|
+
# One allocation, the source copied into the interior region, then the
|
|
190
|
+
# margins filled per mode.
|
|
191
|
+
def pad_source (src, lefts, rights, mode, value)
|
|
192
|
+
nd = src.ndim
|
|
193
|
+
shape = src.shape
|
|
194
|
+
pshape = nd.times.map { |i| shape[i] + lefts[i] + rights[i] }
|
|
195
|
+
pad = CArray.new(src.data_type, pshape)
|
|
196
|
+
|
|
197
|
+
# Fill the whole buffer first, then overwrite the interior with the source.
|
|
198
|
+
case mode
|
|
199
|
+
when :constant
|
|
200
|
+
pad[] = (value.nil? ? 0 : value)
|
|
201
|
+
when :masked
|
|
202
|
+
pad[] = UNDEF
|
|
203
|
+
when :edge
|
|
204
|
+
# Provisional fill; the edge margins are written below from the source.
|
|
205
|
+
pad[] = 0
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
interior = nd.times.map { |i| lefts[i]...(lefts[i] + shape[i]) }
|
|
209
|
+
pad[*interior] = src
|
|
210
|
+
|
|
211
|
+
replicate_edges(pad, lefts, rights, shape) if mode == :edge
|
|
212
|
+
|
|
213
|
+
pad
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Edge-replicate the margins of a padded buffer whose interior already holds
|
|
217
|
+
# the source. Per axis, the left margin rows copy the first interior row and
|
|
218
|
+
# the right margin rows copy the last; done axis by axis over the whole
|
|
219
|
+
# buffer (including corners, which pick up the replicated neighbours from an
|
|
220
|
+
# earlier axis, matching the standard edge-pad of corners).
|
|
221
|
+
def replicate_edges (pad, lefts, rights, shape)
|
|
222
|
+
pad.ndim.times do |ax|
|
|
223
|
+
lo = lefts[ax]
|
|
224
|
+
hi = lefts[ax] + shape[ax] - 1 # last interior index on this axis
|
|
225
|
+
if lefts[ax] > 0
|
|
226
|
+
src_sel = axis_selector(pad.ndim, ax, lo) # first interior row
|
|
227
|
+
lefts[ax].times do |m|
|
|
228
|
+
pad[*axis_selector(pad.ndim, ax, m)] = pad[*src_sel]
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
if rights[ax] > 0
|
|
232
|
+
src_sel = axis_selector(pad.ndim, ax, hi) # last interior row
|
|
233
|
+
(hi + 1...pad.shape[ax]).each do |m|
|
|
234
|
+
pad[*axis_selector(pad.ndim, ax, m)] = pad[*src_sel]
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# An index list of length `nd` that is `nil` (full range) on every axis
|
|
241
|
+
# except `ax`, which is pinned to `k`.
|
|
242
|
+
def axis_selector (nd, ax, k)
|
|
243
|
+
sel = Array.new(nd)
|
|
244
|
+
sel[ax] = k
|
|
245
|
+
sel
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
public
|
|
249
|
+
|
|
250
|
+
# ---- named reductions (core delegation, drift zero) -------------------
|
|
251
|
+
#
|
|
252
|
+
# A per-window fold to one value over the trailing window axes is exactly a
|
|
253
|
+
# core per-axis reduction over those axes, so every reduction delegates to
|
|
254
|
+
# `sliding_view.<op>(axis: window_axes, ...)`. This inherits the core dtype,
|
|
255
|
+
# mask, empty / all-masked (identity vs UNDEF) and epsilon-close contracts
|
|
256
|
+
# unchanged. `min_count:` / `fill_value:` pass straight to the core (the
|
|
257
|
+
# boundary strictness + result fill knobs).
|
|
258
|
+
|
|
259
|
+
# @overload sum(min_count: nil, fill_value: nil)
|
|
260
|
+
# Rolling sum, delegating to `sliding_view.sum(axis: window_axes)`.
|
|
261
|
+
# @return [CArray] reference-shaped (or shrunk, for :truncate)
|
|
262
|
+
# The rest are analogous: prod / mean / min / max, sample and population
|
|
263
|
+
# variance / stddev, all / any, fused minmax, and the window-local position
|
|
264
|
+
# min_index / max_index (index within the window axes).
|
|
265
|
+
[:sum, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
|
|
266
|
+
:variancep, :stddevp, :minmax, :min_index, :max_index].each do |op|
|
|
267
|
+
define_method(op) do |min_count: nil, fill_value: nil|
|
|
268
|
+
kw = {}
|
|
269
|
+
kw[:min_count] = min_count unless min_count.nil?
|
|
270
|
+
kw[:fill_value] = fill_value unless fill_value.nil?
|
|
271
|
+
sliding_view.send(op, axis: @window_axes, **kw)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# @overload min_addr
|
|
276
|
+
# Rolling flat SOURCE address of the window minimum — which source cell holds
|
|
277
|
+
# it, so `source.reshape(source.elements)[sw.min_addr]` are the window minima.
|
|
278
|
+
# Unlike `min_index` (the position within the window) this indexes back into
|
|
279
|
+
# the original array. The winner's source cell is the anchor plus its window
|
|
280
|
+
# offset; with `bounds: :nearest` a winning margin cell resolves to the edge
|
|
281
|
+
# source cell it replicates, and with `bounds: :constant` (or `fill_value:`)
|
|
282
|
+
# a winning margin cell has no source address and is a masked result.
|
|
283
|
+
# @return [CArray] reference-shaped (or shrunk, for :truncate)
|
|
284
|
+
def min_addr; window_winner_addr(:min_index); end
|
|
285
|
+
|
|
286
|
+
# @overload max_addr
|
|
287
|
+
# Rolling flat source address of the window maximum. See {#min_addr}.
|
|
288
|
+
# @return [CArray] reference-shaped (or shrunk, for :truncate)
|
|
289
|
+
def max_addr; window_winner_addr(:max_index); end
|
|
290
|
+
|
|
291
|
+
private
|
|
292
|
+
|
|
293
|
+
# Source address of the per-anchor winner. The window-local flat index
|
|
294
|
+
# (min_index / max_index) decomposes into per-axis window coordinates; the
|
|
295
|
+
# source coordinate on each axis is anchor + offset + window-coordinate (the
|
|
296
|
+
# offset is the range's begin for a padded margin, 0 for :truncate). A margin
|
|
297
|
+
# winner (coordinate out of bounds) resolves per the boundary policy:
|
|
298
|
+
# :nearest clamps to the edge source cell, :constant / :skip mask the result.
|
|
299
|
+
def window_winner_addr (idx_op)
|
|
300
|
+
mi = send(idx_op) # window-local flat index
|
|
301
|
+
n = @source.shape
|
|
302
|
+
wstride = Array.new(@sndim); acc = 1
|
|
303
|
+
(@sndim - 1).downto(0) { |i| wstride[i] = acc; acc *= @widths[i] }
|
|
304
|
+
sstride = Array.new(@sndim); acc = 1
|
|
305
|
+
(@sndim - 1).downto(0) { |i| sstride[i] = acc; acc *= n[i] }
|
|
306
|
+
lo = (@bounds == :truncate) ? Array.new(@sndim, 0) : @ranges.map(&:begin)
|
|
307
|
+
addr = CArray.int64(*@shape); addr[] = 0
|
|
308
|
+
oob = CArray.boolean(*@shape); oob[] = 0
|
|
309
|
+
(0...@sndim).each do |i|
|
|
310
|
+
w_i = (mi.int64 / wstride[i]) % @widths[i] # window coordinate on axis i
|
|
311
|
+
tshape = Array.new(@sndim, 1); tshape[i] = @shape[i]
|
|
312
|
+
o_i = CArray.int64(@shape[i]).seq!.reshape(*tshape) # anchor ramp (broadcasts)
|
|
313
|
+
coord = o_i + lo[i] + w_i
|
|
314
|
+
if @bounds == :nearest
|
|
315
|
+
coord[coord < 0] = 0
|
|
316
|
+
coord[coord >= n[i]] = n[i] - 1 # replicate the nearest edge cell
|
|
317
|
+
else
|
|
318
|
+
oob = oob | (coord < 0) | (coord >= n[i]) # margin winner -> no source cell
|
|
319
|
+
end
|
|
320
|
+
addr = addr + coord * sstride[i]
|
|
321
|
+
end
|
|
322
|
+
addr[oob] = UNDEF unless @bounds == :nearest
|
|
323
|
+
addr[mi.is_masked.eq(1)] = UNDEF if mi.has_mask?
|
|
324
|
+
addr
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
public
|
|
328
|
+
|
|
329
|
+
# @overload count(v = <none>)
|
|
330
|
+
# Rolling count over the window. No argument counts present (non-masked)
|
|
331
|
+
# cells (the effective tap count, which drops near a :skip edge);
|
|
332
|
+
# `count(UNDEF)` counts masked cells; `count(v)` counts cells equal to `v`.
|
|
333
|
+
# @return [CArray]
|
|
334
|
+
def count (*args)
|
|
335
|
+
return count_not_masked if args.empty?
|
|
336
|
+
# The sliding_windows view is a CAStride, so its #count is not shadowed;
|
|
337
|
+
# dispatch CArray#count explicitly anyway, matching the family regularity.
|
|
338
|
+
CArray.instance_method(:count).bind_call(sliding_view, *args, axis: @window_axes)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# @overload count_not_masked
|
|
342
|
+
# Rolling count of present (non-masked) cells -- the denominator of a
|
|
343
|
+
# renormalizing convolution.
|
|
344
|
+
# @return [CArray]
|
|
345
|
+
def count_not_masked
|
|
346
|
+
sliding_view.count_not_masked(axis: @window_axes)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# @overload count_masked
|
|
350
|
+
# Rolling count of masked cells.
|
|
351
|
+
# @return [CArray]
|
|
352
|
+
def count_masked
|
|
353
|
+
sliding_view.count_masked(axis: @window_axes)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# @overload elements
|
|
357
|
+
# Window cell count (structural, mask-independent): the constant window
|
|
358
|
+
# size `Π w_i`, shaped like the output.
|
|
359
|
+
# @return [CArray]
|
|
360
|
+
def elements
|
|
361
|
+
sz = @widths.inject(1) { |p, w| p * w }
|
|
362
|
+
# count_not_masked gives the correct output shape (and is not shadowed);
|
|
363
|
+
# overwrite with the constant window size.
|
|
364
|
+
out = sliding_view.count_not_masked(axis: @window_axes)
|
|
365
|
+
out[] = sz
|
|
366
|
+
out
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# ---- correlate / convolve ---------------------------------------------
|
|
370
|
+
#
|
|
371
|
+
# A windowed weighted sum: `out[i] = Σ_j window[i][j] · kernel[j]`. The
|
|
372
|
+
# engine computes cross-correlation (kernel not flipped); convolution flips
|
|
373
|
+
# the kernel (one line). Both are exposed under their literal names because
|
|
374
|
+
# the flip convention splits by domain (signal processing flips, image / DL
|
|
375
|
+
# does not). For these, the constant margin default is 0.0 (the value a tap
|
|
376
|
+
# reaching outside the source contributes); override with a :constant fill.
|
|
377
|
+
|
|
378
|
+
# @overload correlate(kernel, min_count: nil, fill_value: nil)
|
|
379
|
+
# Rolling cross-correlation `out[i] = Σ_j a[i+j]·k[j]` (kernel not
|
|
380
|
+
# flipped). `kernel` has the shape of one window (`w_1 × ... × w_n`).
|
|
381
|
+
# @param kernel [CArray] weights shaped like a single window.
|
|
382
|
+
# @return [CArray]
|
|
383
|
+
def correlate (kernel, min_count: nil, fill_value: nil)
|
|
384
|
+
unless kernel.shape == @widths
|
|
385
|
+
raise ArgumentError,
|
|
386
|
+
"correlate: kernel shape #{kernel.shape.inspect} != " \
|
|
387
|
+
"window shape #{@widths.inspect}"
|
|
388
|
+
end
|
|
389
|
+
sv = sliding_view
|
|
390
|
+
# Explicit broadcast of the kernel over the anchor axes: reshape to
|
|
391
|
+
# 1 on every anchor axis, kernel width on every window axis (CArray forbids
|
|
392
|
+
# implicit cross-ndim broadcast, so the shape is made explicit).
|
|
393
|
+
kshape = ([1] * @sndim) + @widths
|
|
394
|
+
# The product routes operand promotion through the single-source binop
|
|
395
|
+
# coercion (result_type), so a float kernel over an int source promotes to
|
|
396
|
+
# float instead of truncating the weights. Do not coerce the kernel here.
|
|
397
|
+
prod = sv * kernel.reshape(*kshape)
|
|
398
|
+
kw = {}
|
|
399
|
+
kw[:min_count] = min_count unless min_count.nil?
|
|
400
|
+
kw[:fill_value] = fill_value unless fill_value.nil?
|
|
401
|
+
prod.sum(axis: @window_axes, **kw)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# @overload convolve(kernel, min_count: nil, fill_value: nil)
|
|
405
|
+
# Rolling convolution `out[i] = Σ_j a[i-j]·k[j]` (true convolution: the
|
|
406
|
+
# kernel is flipped on every window axis). Equals {#correlate} for a
|
|
407
|
+
# symmetric kernel.
|
|
408
|
+
# @param kernel [CArray] weights shaped like a single window.
|
|
409
|
+
# @return [CArray]
|
|
410
|
+
def convolve (kernel, min_count: nil, fill_value: nil)
|
|
411
|
+
correlate(reverse_all_axes(kernel), min_count: min_count, fill_value: fill_value)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
private
|
|
415
|
+
|
|
416
|
+
# Reverse a kernel on every axis (`CArray#reverse` flips all axes at once).
|
|
417
|
+
def reverse_all_axes (kernel)
|
|
418
|
+
kernel.reverse
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
public
|
|
422
|
+
|
|
423
|
+
# ---- order statistics (median / percentile / quantile) ----------------
|
|
424
|
+
#
|
|
425
|
+
# Core per-axis order statistics take a single axis and do not accept a
|
|
426
|
+
# masked input, so the window mode is dispatched:
|
|
427
|
+
#
|
|
428
|
+
# single window axis + unmasked margin -> `sliding_view.op(axis: window_axis)`
|
|
429
|
+
# multi window axes + unmasked margin -> materialize the windows, flatten
|
|
430
|
+
# the window axes into one, single-
|
|
431
|
+
# axis core order-stat
|
|
432
|
+
# :skip (UNDEF margin) -> raise (core has no masked per-axis
|
|
433
|
+
# order-stat); guide to :nearest /
|
|
434
|
+
# :truncate
|
|
435
|
+
#
|
|
436
|
+
# When core gains masked per-axis order statistics (a tracked refactor), the
|
|
437
|
+
# :skip guard can be dropped and :skip served directly.
|
|
438
|
+
|
|
439
|
+
# @overload median
|
|
440
|
+
# Rolling median. Requires an unmasked margin (`bounds: :nearest` or
|
|
441
|
+
# `:truncate`); with the default `:skip` it raises.
|
|
442
|
+
# @return [CArray]
|
|
443
|
+
def median
|
|
444
|
+
order_stat { |view, axis| view.median(axis: axis) }
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# @overload percentile(*pers)
|
|
448
|
+
# Rolling percentile(s). One argument returns one CArray, several return
|
|
449
|
+
# an array of CArrays (as `CArray#percentile`). Requires an unmasked
|
|
450
|
+
# margin.
|
|
451
|
+
# @return [CArray, Array<CArray>]
|
|
452
|
+
def percentile (*pers)
|
|
453
|
+
order_stat { |view, axis| view.percentile(*pers, axis: axis) }
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# @overload quantile
|
|
457
|
+
# Rolling five-number summary `[min, Q1, median, Q3, max]` (five CArrays),
|
|
458
|
+
# as `CArray#quantile`. Requires an unmasked margin.
|
|
459
|
+
# @return [Array<CArray>]
|
|
460
|
+
def quantile
|
|
461
|
+
order_stat { |view, axis| view.quantile(axis: axis) }
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
private
|
|
465
|
+
|
|
466
|
+
# Drive an order statistic (yielded as `block.call(view, axis)`) through the
|
|
467
|
+
# single-axis / multi-axis / :skip-reject dispatch above.
|
|
468
|
+
def order_stat
|
|
469
|
+
if @bounds == :skip
|
|
470
|
+
raise ArgumentError,
|
|
471
|
+
"windowed order statistics need an unmasked margin; " \
|
|
472
|
+
"use bounds: :nearest (edge-extend) or bounds: :truncate (valid). " \
|
|
473
|
+
"For an UNDEF-margin median use reduce { |w| w.median } (slower)."
|
|
474
|
+
end
|
|
475
|
+
sv = sliding_view
|
|
476
|
+
if @window_axes.size == 1
|
|
477
|
+
yield sv, @window_axes[0]
|
|
478
|
+
else
|
|
479
|
+
# Materialize the overlapping windows, flatten the window axes into one,
|
|
480
|
+
# and run a single-axis core order-stat (vectorized; peak O(N·Πw)).
|
|
481
|
+
mat = sv.copy
|
|
482
|
+
wsize = @widths.inject(1) { |p, w| p * w }
|
|
483
|
+
flat = mat.reshape(*(@shape + [wsize]))
|
|
484
|
+
yield flat, @sndim # the flattened window axis
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
public
|
|
489
|
+
|
|
490
|
+
# ---- weighted (wsum / wmean) ------------------------------------------
|
|
491
|
+
|
|
492
|
+
# @overload wsum(weights)
|
|
493
|
+
# Rolling weighted sum, `weights` shaped like a single window.
|
|
494
|
+
# @return [CArray]
|
|
495
|
+
def wsum (weights)
|
|
496
|
+
weighted(weights) { |sv, w, axis| sv.wsum(w, axis: axis) }
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# @overload wmean(weights)
|
|
500
|
+
# Rolling weighted mean, `weights` shaped like a single window.
|
|
501
|
+
# @return [CArray]
|
|
502
|
+
def wmean (weights)
|
|
503
|
+
weighted(weights) { |sv, w, axis| sv.wmean(w, axis: axis) }
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
private
|
|
507
|
+
|
|
508
|
+
def weighted (weights)
|
|
509
|
+
unless weights.shape == @widths
|
|
510
|
+
raise ArgumentError,
|
|
511
|
+
"wsum/wmean: weights shape #{weights.shape.inspect} != " \
|
|
512
|
+
"window shape #{@widths.inspect}"
|
|
513
|
+
end
|
|
514
|
+
sv = sliding_view
|
|
515
|
+
# Explicit broadcast of the per-window weights over the anchor axes, then
|
|
516
|
+
# grow to the full view shape (core wsum / wmean take a per-cell weight
|
|
517
|
+
# array shaped like the source, not the reduced-axis vector).
|
|
518
|
+
wshape = ([1] * @sndim) + @widths
|
|
519
|
+
wfull = weights.reshape(*wshape).broadcast_to(*sv.shape)
|
|
520
|
+
yield sv, wfull, @window_axes
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
public
|
|
524
|
+
|
|
525
|
+
# ---- generic iteration (escape hatch, slow) ---------------------------
|
|
526
|
+
#
|
|
527
|
+
# `each` yields every window (a per-window materialize -- slow, but the
|
|
528
|
+
# receptacle for statistics not in the named surface). `reduce` folds each
|
|
529
|
+
# window to one value (a custom rolling reduction), producing a reference-
|
|
530
|
+
# shaped output. `map` is defined only to raise NotImplementedError with an
|
|
531
|
+
# explanation: overlapping windows make an element-wise scatter-back
|
|
532
|
+
# ill-defined.
|
|
533
|
+
|
|
534
|
+
# @overload each { |window| ... }
|
|
535
|
+
# Yields each anchor's window as a CArray. Without a block, returns an
|
|
536
|
+
# Enumerator. Per-window materialize, slow; use a named reduction or
|
|
537
|
+
# {#convolve} for speed.
|
|
538
|
+
# @yieldparam window [CArray]
|
|
539
|
+
# @return [Enumerator, self]
|
|
540
|
+
def each
|
|
541
|
+
return to_enum(:each) unless block_given?
|
|
542
|
+
sv = sliding_view
|
|
543
|
+
nils = Array.new(@sndim, nil) # full window on the trailing axes
|
|
544
|
+
each_anchor_index { |idx| yield sv[*idx, *nils] }
|
|
545
|
+
self
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
# @overload reduce { |window| ... }
|
|
549
|
+
# Custom rolling reduction: the block receives each window (a CArray) and
|
|
550
|
+
# returns one value per anchor. The escape hatch for statistics not in the
|
|
551
|
+
# named surface.
|
|
552
|
+
# @yieldparam window [CArray]
|
|
553
|
+
# @return [CArray] reference-shaped (or shrunk, for :truncate)
|
|
554
|
+
# @overload reduce(init) { |acc, elem| ... }
|
|
555
|
+
# Per-window fiber fold: each window's cells are folded element by element
|
|
556
|
+
# starting from `init`.
|
|
557
|
+
# @param init [Object] initial accumulator.
|
|
558
|
+
# @return [CArray]
|
|
559
|
+
def reduce (*args, data_type: nil, &blk)
|
|
560
|
+
raise LocalJumpError, "no block given (yield)" unless blk
|
|
561
|
+
dt = data_type || CA_OBJECT
|
|
562
|
+
out = CArray.new(dt, @shape)
|
|
563
|
+
sv = sliding_view
|
|
564
|
+
nils = Array.new(@sndim, nil) # full window on the trailing axes
|
|
565
|
+
if args.empty?
|
|
566
|
+
each_anchor_index { |idx| out[*idx] = blk.call(sv[*idx, *nils]) }
|
|
567
|
+
else
|
|
568
|
+
init = args[0]
|
|
569
|
+
each_anchor_index do |idx|
|
|
570
|
+
acc = init
|
|
571
|
+
sv[*idx, *nils].each { |e| acc = blk.call(acc, e) }
|
|
572
|
+
out[*idx] = acc
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
out
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# @overload map
|
|
579
|
+
# Not supported for a window iterator: overlapping windows share cells, so
|
|
580
|
+
# an element-wise transform has no well-defined scatter-back. Raises
|
|
581
|
+
# NotImplementedError; use {#reduce} for a custom per-window fold.
|
|
582
|
+
# @raise [NotImplementedError]
|
|
583
|
+
def map (*)
|
|
584
|
+
raise NotImplementedError,
|
|
585
|
+
"#{self.class} has no map: overlapping windows share cells, so an " \
|
|
586
|
+
"element-wise scatter-back is ill-defined; use reduce for a custom " \
|
|
587
|
+
"per-window fold."
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# @overload sort_addr
|
|
591
|
+
# Not supported for a window iterator: a window's boundary cells are padding
|
|
592
|
+
# with no source address, and overlapping windows share cells, so a
|
|
593
|
+
# per-window sort returning source flat addresses is ill-defined. Raises
|
|
594
|
+
# NotImplementedError. (min_addr / max_addr are fine: the single winning
|
|
595
|
+
# cell of a window is a real source cell.)
|
|
596
|
+
# @raise [NotImplementedError]
|
|
597
|
+
def sort_addr (*)
|
|
598
|
+
raise NotImplementedError,
|
|
599
|
+
"#{self.class} has no sort_addr: padded boundary cells have no " \
|
|
600
|
+
"source address and overlapping windows share cells, so a per-window " \
|
|
601
|
+
"sort of source addresses is ill-defined."
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
# @overload cumsum
|
|
605
|
+
# @overload cumprod
|
|
606
|
+
# @overload cummax
|
|
607
|
+
# @overload cummin
|
|
608
|
+
# @overload cumcount
|
|
609
|
+
# Not supported for a window iterator: a segment scan writes a per-cell
|
|
610
|
+
# running statistic, which is single-valued only when each cell belongs to
|
|
611
|
+
# exactly one piece. Overlapping windows put a cell in many windows, so
|
|
612
|
+
# there is no single running value. Raises NotImplementedError, exactly as
|
|
613
|
+
# {#map} / {#sort_addr} do (min / max reductions stay available: a single
|
|
614
|
+
# winner is well-defined).
|
|
615
|
+
# @raise [NotImplementedError]
|
|
616
|
+
[:cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |op|
|
|
617
|
+
define_method(op) do |*, **|
|
|
618
|
+
raise NotImplementedError,
|
|
619
|
+
"#{self.class} has no #{op}: a segment scan needs each cell to " \
|
|
620
|
+
"belong to exactly one piece, but overlapping windows share cells, " \
|
|
621
|
+
"so a per-cell running value is ill-defined; use reduce for a " \
|
|
622
|
+
"custom per-window fold."
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
private
|
|
627
|
+
|
|
628
|
+
# Yield every anchor index (the output iteration space) as an index Array of
|
|
629
|
+
# length `@ndim` (the anchor axes only; callers append the window nils).
|
|
630
|
+
def each_anchor_index
|
|
631
|
+
CArray.each_index(*@shape) do |*idx|
|
|
632
|
+
yield idx
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
class CArray
|
|
639
|
+
# @overload windows(*ranges, bounds: :skip, fill_value: nil)
|
|
640
|
+
# Returns a {CAWindowIterator} rolling a per-axis offset window over
|
|
641
|
+
# `self`. Each `ranges[i]` is a `lo..hi` offset span (`a.windows(-1..1)`
|
|
642
|
+
# is a centred width-3 window); `bounds:` selects the margin policy. With
|
|
643
|
+
# no ranges (`a.windows(a.window(...))` passing a CAWindow view) the
|
|
644
|
+
# geometry is read from the view for backward compatibility.
|
|
645
|
+
# @param ranges [Array<Range>] per-axis offset ranges.
|
|
646
|
+
# @param bounds [Symbol] `:skip` / `:nearest` / `:truncate`.
|
|
647
|
+
# @param fill_value [Object, nil] constant margin value.
|
|
648
|
+
# @return [CAWindowIterator]
|
|
649
|
+
def windows (*ranges, bounds: :skip, fill_value: nil)
|
|
650
|
+
if ranges.size == 1 && ranges[0].is_a?(CArray) && ranges[0].obj_type == CA_OBJ_WINDOW
|
|
651
|
+
return CAWindowIterator.new(ranges[0])
|
|
652
|
+
end
|
|
653
|
+
CAWindowIterator.new(self, *ranges, bounds: bounds, fill_value: fill_value)
|
|
654
|
+
end
|
|
655
|
+
end
|