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.
Files changed (339) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -25
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +1 -1
  5. data/NEWS.md +3 -0
  6. data/README.md +128 -44
  7. data/carray.gemspec +22 -24
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1208 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +125 -0
  15. data/ext/ca_binop_dispatch.h +159 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +241 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4321 -0
  26. data/ext/ca_kernel_iterator.h +2603 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +809 -0
  33. data/ext/ca_obj_binop.c +892 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +965 -0
  39. data/ext/ca_obj_face.c +670 -0
  40. data/ext/ca_obj_face.h +247 -0
  41. data/ext/ca_obj_fake.c +228 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +306 -0
  45. data/ext/ca_obj_grid.c +858 -440
  46. data/ext/ca_obj_meld.c +1034 -0
  47. data/ext/ca_obj_moncmp.c +569 -0
  48. data/ext/ca_obj_monop.c +1111 -0
  49. data/ext/ca_obj_object.c +774 -298
  50. data/ext/ca_obj_record.c +468 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +569 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +616 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1296 -0
  58. data/ext/ca_obj_shift.c +230 -792
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2501 -0
  62. data/ext/ca_obj_string.c +268 -0
  63. data/ext/ca_obj_tile.c +614 -0
  64. data/ext/ca_obj_time.c +546 -0
  65. data/ext/ca_obj_timedelta.c +435 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +746 -0
  68. data/ext/ca_obj_unbound_repeat.c +208 -241
  69. data/ext/ca_obj_window.c +1131 -563
  70. data/ext/ca_op_byte_swap.c +175 -0
  71. data/ext/ca_op_ipower.c +319 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +430 -0
  75. data/ext/ca_sweep_engine.h +157 -0
  76. data/ext/ca_transform_common.c +228 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +795 -402
  80. data/ext/carray_access.c +831 -711
  81. data/ext/carray_attribute.c +98 -330
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +283 -0
  84. data/ext/carray_call_cfunc.c +1360 -828
  85. data/ext/carray_call_cfunc.h +160 -0
  86. data/ext/carray_cast.c +1212 -301
  87. data/ext/carray_cast_func.rb +81 -40
  88. data/ext/carray_class.c +53 -63
  89. data/ext/carray_config.h +28 -0
  90. data/ext/carray_conversion.c +350 -346
  91. data/ext/carray_copy.c +156 -268
  92. data/ext/carray_core.c +1342 -199
  93. data/ext/carray_count.c +312 -0
  94. data/ext/carray_data_type.c +43 -19
  95. data/ext/carray_element.c +585 -213
  96. data/ext/carray_factorize.c +2542 -0
  97. data/ext/carray_generate.c +230 -559
  98. data/ext/carray_histogram.c +490 -0
  99. data/ext/carray_hold.c +228 -0
  100. data/ext/carray_index_classifier.c +1035 -0
  101. data/ext/carray_index_classifier.h +27 -0
  102. data/ext/carray_internal.h +120 -0
  103. data/ext/carray_kernels_bincmp.c +4445 -0
  104. data/ext/carray_kernels_binop.c +10979 -0
  105. data/ext/carray_kernels_init.c +36 -0
  106. data/ext/carray_kernels_map.c +3466 -0
  107. data/ext/carray_kernels_moncmp.c +2096 -0
  108. data/ext/carray_kernels_monop.c +18312 -0
  109. data/ext/carray_kernels_reduce_aggregate.c +25836 -0
  110. data/ext/carray_kernels_reduce_boolean.c +329 -0
  111. data/ext/carray_kernels_reduce_cumulative.c +14592 -0
  112. data/ext/carray_kernels_reduce_extreme.c +16947 -0
  113. data/ext/carray_kernels_reduce_variance.c +3909 -0
  114. data/ext/carray_kernels_scan.c +3692 -0
  115. data/ext/carray_kernels_search.c +32137 -0
  116. data/ext/carray_kernels_sort.c +10625 -0
  117. data/ext/carray_kernels_triop.c +1391 -0
  118. data/ext/carray_lazy.c +567 -0
  119. data/ext/carray_loop.c +88 -200
  120. data/ext/carray_mask.c +848 -154
  121. data/ext/carray_math_kernel.h +120 -0
  122. data/ext/carray_mathfunc.c +10 -241
  123. data/ext/carray_median_percentile.c +1257 -0
  124. data/ext/carray_memory_view.c +1625 -0
  125. data/ext/carray_operator.c +1526 -318
  126. data/ext/carray_order.c +664 -1394
  127. data/ext/carray_partition.c +416 -0
  128. data/ext/carray_random.c +518 -0
  129. data/ext/carray_scatter.c +357 -0
  130. data/ext/carray_slab.c +1219 -0
  131. data/ext/carray_slab.h +84 -0
  132. data/ext/carray_sort.c +829 -0
  133. data/ext/carray_sort_kernel.c +620 -0
  134. data/ext/carray_struct.c +695 -0
  135. data/ext/carray_test.c +343 -229
  136. data/ext/carray_undef.c +34 -17
  137. data/ext/carray_utils.c +175 -74
  138. data/ext/extconf.rb +216 -55
  139. data/ext/mk_call_cfunc.rb +480 -0
  140. data/ext/mkkernel.rb +8842 -0
  141. data/ext/ruby_carray.c +202 -101
  142. data/ext/version.h +4 -14
  143. data/ext/version.rb +5 -13
  144. data/lib/carray/arrow_tensor.rb +401 -0
  145. data/lib/carray/attribute.rb +166 -0
  146. data/lib/carray/autoload_carray.rb +220 -0
  147. data/lib/carray/autoload_method_extension.rb +44 -0
  148. data/lib/carray/axis_group.rb +711 -0
  149. data/lib/carray/basics.rb +481 -0
  150. data/lib/carray/bincount_nd.rb +358 -0
  151. data/lib/carray/block_iterator.rb +604 -0
  152. data/lib/carray/boolean_reduce.rb +109 -0
  153. data/lib/carray/categorical.rb +561 -0
  154. data/lib/carray/categorical_iterator.rb +1062 -0
  155. data/lib/carray/complex.rb +150 -0
  156. data/lib/carray/conditional.rb +216 -0
  157. data/lib/carray/const_string.rb +228 -0
  158. data/lib/carray/construct.rb +139 -328
  159. data/lib/carray/core_extensions.rb +240 -0
  160. data/lib/carray/data_type_extension.rb +233 -0
  161. data/lib/carray/fixlen_string.rb +95 -0
  162. data/lib/carray/frame/concat.rb +132 -0
  163. data/lib/carray/frame/convert.rb +95 -0
  164. data/lib/carray/frame/csv_parser.rb +211 -0
  165. data/lib/carray/frame/frame.rb +649 -0
  166. data/lib/carray/frame/group.rb +186 -0
  167. data/lib/carray/frame/io.rb +164 -0
  168. data/lib/carray/frame/join.rb +248 -0
  169. data/lib/carray/frame/records.rb +99 -0
  170. data/lib/carray/frame/sort.rb +113 -0
  171. data/lib/carray/frame/verbs.rb +299 -0
  172. data/lib/carray/frame.rb +16 -0
  173. data/lib/carray/histogram.rb +512 -0
  174. data/lib/carray/inspect.rb +37 -20
  175. data/lib/carray/iterator.rb +57 -349
  176. data/lib/carray/lazy.rb +889 -0
  177. data/lib/carray/mask_gap_fill.rb +200 -0
  178. data/lib/carray/math.rb +78 -342
  179. data/lib/carray/meld_reduce.rb +289 -0
  180. data/lib/carray/methods/align_addr.rb +116 -0
  181. data/lib/carray/methods/bin.rb +128 -0
  182. data/lib/carray/methods/bincount.rb +87 -0
  183. data/lib/carray/methods/bit_string.rb +92 -0
  184. data/lib/carray/methods/broadcast.rb +63 -0
  185. data/lib/carray/methods/choose.rb +39 -0
  186. data/lib/carray/methods/composition.rb +280 -0
  187. data/lib/carray/methods/gather_nd.rb +206 -0
  188. data/lib/carray/methods/index.rb +39 -0
  189. data/lib/carray/methods/insert_block.rb +99 -0
  190. data/lib/carray/methods/is_in.rb +141 -0
  191. data/lib/carray/methods/join.rb +90 -0
  192. data/lib/carray/methods/locate_addr.rb +47 -0
  193. data/lib/carray/methods/mask_duplicates.rb +41 -0
  194. data/lib/carray/methods/meshgrid.rb +91 -0
  195. data/lib/carray/methods/mode.rb +126 -0
  196. data/lib/carray/methods/nunique.rb +46 -0
  197. data/lib/carray/methods/resize.rb +56 -0
  198. data/lib/carray/methods/snap.rb +156 -0
  199. data/lib/carray/methods/string_format.rb +57 -0
  200. data/lib/carray/methods/unique.rb +47 -0
  201. data/lib/carray/methods/value_counts.rb +71 -0
  202. data/lib/carray/mkmf.rb +124 -101
  203. data/lib/carray/runtime.rb +108 -0
  204. data/lib/carray/serialize.rb +478 -167
  205. data/lib/carray/slab_iterator.rb +292 -0
  206. data/lib/carray/stack.rb +291 -0
  207. data/lib/carray/string.rb +56 -180
  208. data/lib/carray/string_operation_extension.rb +289 -0
  209. data/lib/carray/struct.rb +335 -323
  210. data/lib/carray/struct_builder.rb +697 -0
  211. data/lib/carray/table.rb +41 -2
  212. data/lib/carray/time.rb +2255 -38
  213. data/lib/carray/window_iterator.rb +655 -0
  214. data/lib/carray.rb +55 -57
  215. metadata +163 -130
  216. data/Rakefile +0 -51
  217. data/TODO.md +0 -18
  218. data/ext/ca_iter_block.c +0 -257
  219. data/ext/ca_iter_dimension.c +0 -299
  220. data/ext/ca_iter_window.c +0 -214
  221. data/ext/ca_obj_mapping.c +0 -644
  222. data/ext/carray_iterator.c +0 -641
  223. data/ext/carray_math.rb +0 -850
  224. data/ext/carray_numeric.c +0 -259
  225. data/ext/carray_sort_addr.c +0 -254
  226. data/ext/carray_stat.c +0 -2100
  227. data/ext/carray_stat_proc.rb +0 -1999
  228. data/ext/mkmath.rb +0 -741
  229. data/ext/ruby_ccomplex.c +0 -509
  230. data/ext/ruby_float_func.c +0 -86
  231. data/lib/carray/array.rb +0 -8
  232. data/lib/carray/autoload/autoload_base.rb +0 -19
  233. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  234. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  235. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  236. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  237. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  238. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  239. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  240. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  241. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  242. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  243. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  244. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  245. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  246. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  247. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  248. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  249. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  250. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  251. data/lib/carray/autoload.rb +0 -141
  252. data/lib/carray/basic.rb +0 -191
  253. data/lib/carray/broadcast.rb +0 -101
  254. data/lib/carray/compose.rb +0 -315
  255. data/lib/carray/convert.rb +0 -115
  256. data/lib/carray/info.rb +0 -110
  257. data/lib/carray/io/imagemagick.rb +0 -235
  258. data/lib/carray/mask.rb +0 -102
  259. data/lib/carray/math/histogram.rb +0 -177
  260. data/lib/carray/math/recurrence.rb +0 -93
  261. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  262. data/lib/carray/object/ca_obj_link.rb +0 -50
  263. data/lib/carray/object/ca_obj_pack.rb +0 -99
  264. data/lib/carray/obsolete.rb +0 -256
  265. data/lib/carray/ordering.rb +0 -181
  266. data/lib/carray/testing.rb +0 -51
  267. data/lib/carray/transform.rb +0 -109
  268. data/misc/Methods.ja.md +0 -182
  269. data/misc/NOTE +0 -51
  270. data/spec/Classes/CABitfield_spec.rb +0 -58
  271. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  272. data/spec/Classes/CABlock_spec.rb +0 -205
  273. data/spec/Classes/CAField_spec.rb +0 -39
  274. data/spec/Classes/CAGrid_spec.rb +0 -75
  275. data/spec/Classes/CAMap_spec.rb +0 -0
  276. data/spec/Classes/CAMapping_spec.rb +0 -105
  277. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  278. data/spec/Classes/CAObject_spec.rb +0 -33
  279. data/spec/Classes/CARefer_spec.rb +0 -93
  280. data/spec/Classes/CARepeat_spec.rb +0 -65
  281. data/spec/Classes/CASelect_spec.rb +0 -22
  282. data/spec/Classes/CAShift_spec.rb +0 -16
  283. data/spec/Classes/CAStruct_spec.rb +0 -71
  284. data/spec/Classes/CATranspose_spec.rb +0 -60
  285. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  286. data/spec/Classes/CAWindow_spec.rb +0 -54
  287. data/spec/Classes/CAWrap_spec.rb +0 -8
  288. data/spec/Classes/CArray_spec.rb +0 -184
  289. data/spec/Classes/CScalar_spec.rb +0 -55
  290. data/spec/Classes/ex1.rb +0 -46
  291. data/spec/Features/feature_130_spec.rb +0 -19
  292. data/spec/Features/feature_attributes_spec.rb +0 -280
  293. data/spec/Features/feature_boolean_spec.rb +0 -98
  294. data/spec/Features/feature_broadcast.rb +0 -116
  295. data/spec/Features/feature_cast_function.rb +0 -19
  296. data/spec/Features/feature_cast_spec.rb +0 -33
  297. data/spec/Features/feature_class_spec.rb +0 -84
  298. data/spec/Features/feature_complex_spec.rb +0 -42
  299. data/spec/Features/feature_composite_spec.rb +0 -124
  300. data/spec/Features/feature_convert_spec.rb +0 -46
  301. data/spec/Features/feature_copy_spec.rb +0 -123
  302. data/spec/Features/feature_creation_spec.rb +0 -84
  303. data/spec/Features/feature_element_spec.rb +0 -144
  304. data/spec/Features/feature_extream_spec.rb +0 -54
  305. data/spec/Features/feature_generate_spec.rb +0 -74
  306. data/spec/Features/feature_index_spec.rb +0 -69
  307. data/spec/Features/feature_mask_spec.rb +0 -580
  308. data/spec/Features/feature_math_spec.rb +0 -97
  309. data/spec/Features/feature_order_spec.rb +0 -146
  310. data/spec/Features/feature_ref_store_spec.rb +0 -209
  311. data/spec/Features/feature_serialization_spec.rb +0 -125
  312. data/spec/Features/feature_stat_spec.rb +0 -397
  313. data/spec/Features/feature_virtual_spec.rb +0 -48
  314. data/spec/Features/method_eq_spec.rb +0 -81
  315. data/spec/Features/method_is_nan_spec.rb +0 -12
  316. data/spec/Features/method_map_spec.rb +0 -54
  317. data/spec/Features/method_max_with.rb +0 -20
  318. data/spec/Features/method_min_with.rb +0 -19
  319. data/spec/Features/method_ne_spec.rb +0 -18
  320. data/spec/Features/method_project_spec.rb +0 -188
  321. data/spec/Features/method_ref_spec.rb +0 -27
  322. data/spec/Features/method_round_spec.rb +0 -11
  323. data/spec/Features/method_s_linspace_spec.rb +0 -48
  324. data/spec/Features/method_s_span_spec.rb +0 -14
  325. data/spec/Features/method_seq_spec.rb +0 -47
  326. data/spec/Features/method_sort_with.rb +0 -43
  327. data/spec/Features/method_sorted_with.rb +0 -29
  328. data/spec/Features/method_span_spec.rb +0 -42
  329. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  330. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  331. data/spec/spec_all.rb +0 -10
  332. data/utils/ca_ase.rb +0 -21
  333. data/utils/ca_methods.rb +0 -15
  334. data/utils/cast_checker.rb +0 -30
  335. data/utils/convert_test.rb +0 -73
  336. data/utils/extract_yard.rb +0 -22
  337. data/utils/guess_shape.rb +0 -76
  338. data/utils/monkey_patch_methods.rb +0 -62
  339. 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