carray 2.0.1 → 3.0.1

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