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,292 @@
1
+ # CASlabIterator.
2
+ #
3
+ # Created by the C indexer dispatch when an index uses the `:>` slab-axis
4
+ # sigil (e.g. `ca[nil, :>, :>]`). It is a thin sugar over each_slab /
5
+ # map_slab / reduce_slab: `:>` axes become the slab handed to the block;
6
+ # the remaining (sliced) axes are the outer iteration space.
7
+ #
8
+ # ca[nil, :>].each { |row| ... } # = ca.each_slab(axis: 1)
9
+ # ca[2..5, :>, :>].map { |slab| ... } # = ca[2..5,nil,nil].map_slab(axis: [-2,-1])
10
+ #
11
+ # Surface is intentionally minimal (each / map / reduce); to_a / count /
12
+ # size and other Enumerable extras are deferred.
13
+ #
14
+ # Naming note: distinct from the C-defined `CArray::SlabIterator` engine
15
+ # in ext/carray_slab.c -- this is the Ruby-level sigil iterator that
16
+ # delegates to each_slab / map_slab / reduce_slab. Loaded lazily via
17
+ # autoload from lib/carray/autoload/autoload_base.rb on first
18
+ # `ca[..., :>]` evaluation.
19
+
20
+ class CASlabIterator < CAIterator
21
+ # Built from C via `CASlabIterator.new(reference, slab_axes)`: `reference`
22
+ # is the sliced base view (with `:>` axes as full range), `slab_axes` are
23
+ # the positions marked with `:>`.
24
+ def initialize (reference, slab_axes)
25
+ @reference = reference
26
+ @slab_axes = slab_axes
27
+ rdim = reference.shape
28
+ @outer_positions = (0...reference.ndim).reject { |k| slab_axes.include?(k) }.freeze
29
+ @shape = @outer_positions.map { |k| rdim[k] }
30
+ @ndim = @shape.size
31
+ self
32
+ end
33
+
34
+ # The array being iterated (slab exposes it as `reference`; block / window
35
+ # iterators expose theirs as `source`). The base has no common accessor.
36
+ attr_reader :reference, :slab_axes
37
+
38
+ # Slab view at outer index `idx` (Array, length = self.ndim). Returns
39
+ # `@reference[*full_idx]` where slab axes are nil (full range) and
40
+ # outer axes take their values from idx.
41
+ def kernel_at_index (idx)
42
+ full = Array.new(@reference.ndim) # nil at every position
43
+ @outer_positions.each_with_index { |k, i| full[k] = idx[i] }
44
+ @reference[*full]
45
+ end
46
+
47
+ # @overload each { |slab| ... }
48
+ # Yields each slab as an inner CArray. Without a block, returns
49
+ # an `Enumerator` from `each_slab`.
50
+ # @yieldparam slab [CArray]
51
+ # @return [Enumerator, self]
52
+ def each (&block)
53
+ return @reference.each_slab(axis: @slab_axes) unless block
54
+ @reference.each_slab(axis: @slab_axes, &block)
55
+ end
56
+
57
+ # @overload map { |slab| ... }
58
+ # Returns a new CArray built by applying the block to each slab,
59
+ # delegating to `map_slab`.
60
+ # @yieldparam slab [CArray]
61
+ # @yieldreturn [Object] per-slab result.
62
+ # @return [CArray]
63
+ def map (&block)
64
+ @reference.map_slab(axis: @slab_axes, &block)
65
+ end
66
+
67
+ # @overload reduce { |acc, slab| ... }
68
+ # Reduces the slabs with `reduce_slab`. The block-only form uses
69
+ # the first slab as the seed; the `init` form starts from `init`.
70
+ # @yieldparam acc [Object] running accumulator.
71
+ # @yieldparam slab [CArray] next slab.
72
+ # @yieldreturn [Object] updated accumulator.
73
+ # @return [Object]
74
+ # @overload reduce(init) { |acc, slab| ... }
75
+ # @param init [Object] initial accumulator value.
76
+ # @yieldparam acc [Object]
77
+ # @yieldparam slab [CArray]
78
+ # @yieldreturn [Object]
79
+ # @return [Object]
80
+ def reduce (*args, &block)
81
+ if args.empty?
82
+ @reference.reduce_slab(axis: @slab_axes, &block)
83
+ else
84
+ @reference.reduce_slab(axis: @slab_axes, init: args[0], &block)
85
+ end
86
+ end
87
+
88
+ # ---- named reductions -------------------------------------------------
89
+ #
90
+ # A per-slab reduction that folds each slab to one value is exactly the core
91
+ # per-axis reduction over the slab axes, so every reduction is a direct
92
+ # delegation to `reference.<op>(axis: slab_axes)`. This inherits the core
93
+ # dtype, mask, empty/all-masked (identity vs UNDEF) and epsilon-close
94
+ # contracts unchanged -- there is no separate slab reduction kernel. (Unlike
95
+ # the map / reduce block surface, these are mask-aware: they route through the
96
+ # core reduction, which handles masked sources.)
97
+
98
+ # @overload sum
99
+ # Per-slab sum, delegating to `reference.sum(axis: slab_axes)`.
100
+ # @return [CArray] one value per slab (shape = self.dim minus the slab axes)
101
+ # The rest are analogous: prod / mean / min / max, sample and population
102
+ # variance / stddev, all / any, fused minmax ([min, max] pair), the axis-local
103
+ # position min_index / max_index (index within the slab axes), and the flat
104
+ # source address min_addr / max_addr (which source cell holds the extremum).
105
+ [:sum, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
106
+ :variancep, :stddevp, :minmax, :min_index, :max_index,
107
+ :min_addr, :max_addr].each do |op|
108
+ define_method(op) { @reference.send(op, axis: @slab_axes) }
109
+ end
110
+
111
+ # @overload count(v = <none>)
112
+ # Per-slab count, delegating to `reference.count(..., axis: slab_axes)`.
113
+ # No argument counts present (non-masked) cells; `count(UNDEF)` counts
114
+ # masked cells; `count(v)` counts cells equal to `v`.
115
+ # @return [CArray] one count per slab
116
+ def count (*args)
117
+ return count_not_masked if args.empty?
118
+ # CABlock / CAWindow shadow #count with a geometry accessor, and @reference
119
+ # may be such a view, so dispatch CArray#count explicitly for count(v).
120
+ CArray.instance_method(:count).bind_call(@reference, *args, axis: @slab_axes)
121
+ end
122
+
123
+ # @overload count_not_masked
124
+ # Per-slab count of present (non-masked) cells.
125
+ # @return [CArray]
126
+ def count_not_masked
127
+ @reference.count_not_masked(axis: @slab_axes)
128
+ end
129
+
130
+ # @overload count_masked
131
+ # Per-slab count of masked cells.
132
+ # @return [CArray]
133
+ def count_masked
134
+ @reference.count_masked(axis: @slab_axes)
135
+ end
136
+
137
+ # @overload elements
138
+ # Per-slab cell count (structural, mask-independent). Every slab has the
139
+ # same shape, so this is a constant array shaped like the outer iteration
140
+ # space.
141
+ # @return [CArray]
142
+ def elements
143
+ sz = @slab_axes.inject(1) { |p, ax| p * @reference.shape[ax] }
144
+ # count_not_masked gives the correct output shape (and, unlike bare #count,
145
+ # is not shadowed by CABlock / CAWindow); overwrite with the constant size.
146
+ out = @reference.count_not_masked(axis: @slab_axes)
147
+ out[] = sz
148
+ out
149
+ end
150
+
151
+ # ---- order statistics (tier 3) ----------------------------------------
152
+ #
153
+ # Core order statistics take a single integer axis, so a single slab axis is
154
+ # delegated directly (fast C path) and a multi-axis slab is folded per slab
155
+ # with reduce_slab (each slab flattened). As with `CArray#median(axis:)`, a
156
+ # masked source raises (the known per-axis masked limitation); strip the mask
157
+ # with `ca.value` first if needed.
158
+
159
+ # @overload median
160
+ # Per-slab median.
161
+ # @return [CArray]
162
+ def median
163
+ if @slab_axes.size == 1
164
+ @reference.median(axis: @slab_axes[0])
165
+ else
166
+ @reference.reduce_slab(axis: @slab_axes) { |s| s.median }
167
+ end
168
+ end
169
+
170
+ # @overload percentile(*pers)
171
+ # Per-slab percentile(s). One argument returns one CArray; several return an
172
+ # array of CArrays (as `CArray#percentile`).
173
+ # @return [CArray, Array<CArray>]
174
+ def percentile (*pers)
175
+ if @slab_axes.size == 1
176
+ @reference.percentile(*pers, axis: @slab_axes[0])
177
+ else
178
+ rs = pers.map { |p| @reference.reduce_slab(axis: @slab_axes) { |s| s.percentile(p) } }
179
+ pers.size == 1 ? rs[0] : rs
180
+ end
181
+ end
182
+
183
+ # @overload quantile
184
+ # Per-slab five-number summary `[min, Q1, median, Q3, max]` (five CArrays),
185
+ # as `CArray#quantile`.
186
+ # @return [Array<CArray>]
187
+ def quantile
188
+ if @slab_axes.size == 1
189
+ @reference.quantile(axis: @slab_axes[0])
190
+ else
191
+ [0, 25, 50, 75, 100].map { |p|
192
+ @reference.reduce_slab(axis: @slab_axes) { |s| s.percentile(p) }
193
+ }
194
+ end
195
+ end
196
+
197
+ # ---- order surface ----------------------------------------------------
198
+ #
199
+ # sort_addr / sort_index lift the core order surface per slab. Core sort takes
200
+ # a single axis, so a one-axis slab delegates straight to the C path (unlike
201
+ # the reductions above, sort does not accept an axis array). A multi-axis slab
202
+ # has no single sort axis, so it raises; sort one axis at a time.
203
+
204
+ # @overload sort_addr
205
+ # Per-slab sort by flat source address. Delegates to
206
+ # `reference.sort_addr(axis: slab_axis)`: reference-shaped, each slab's cells
207
+ # carry the flat source addresses that sort that slab ascending.
208
+ # @return [CArray] reference-shaped int64
209
+ def sort_addr
210
+ @reference.sort_addr(axis: single_sort_axis(:sort_addr))
211
+ end
212
+
213
+ # @overload sort_index
214
+ # Per-slab sort by axis-local index (usable with take_along_axis).
215
+ # Delegates to `reference.sort_index(axis: slab_axis)`: reference-shaped, the
216
+ # axis-local rank order within the slab axis.
217
+ # @return [CArray] reference-shaped int64
218
+ def sort_index
219
+ @reference.sort_index(axis: single_sort_axis(:sort_index))
220
+ end
221
+
222
+ # ---- weighted (tier 2) ------------------------------------------------
223
+ #
224
+ # Delegated to the core weighted reduction over the slab axes; `weights` has
225
+ # the same shape as the reference (one weight per cell). Core wsum / wmean
226
+ # accept multi-axis, so this covers multi-axis slabs directly.
227
+
228
+ # @overload wsum(weights)
229
+ # Per-slab weighted sum, `weights` shaped like the reference.
230
+ # @return [CArray]
231
+ def wsum (weights)
232
+ @reference.wsum(weights, axis: @slab_axes)
233
+ end
234
+
235
+ # @overload wmean(weights)
236
+ # Per-slab weighted mean, `weights` shaped like the reference.
237
+ # @return [CArray]
238
+ def wmean (weights)
239
+ @reference.wmean(weights, axis: @slab_axes)
240
+ end
241
+
242
+ # ---- segment scan: within-slab running statistics ---------------------
243
+ #
244
+ # A per-slab running accumulation along the slab axis is exactly the core
245
+ # per-axis cumulative over that axis, so every scan delegates to
246
+ # `reference.<op>(axis: slab_axis)`, inheriting the core dtype / mask (masked
247
+ # cells hold the running total, output unmasked) contracts unchanged. Each
248
+ # cell is in exactly one slab (a partition), so the running value is
249
+ # well-defined; the surface is uniform with the family even though it
250
+ # coincides with a plain per-axis cumulative. Core scan takes a single axis (a
251
+ # multi-axis running accumulation is ambiguous), so a multi-axis slab raises --
252
+ # scan one axis at a time, like the order surface.
253
+
254
+ # @overload cumsum
255
+ # Per-slab inclusive running sum (float64), reference-shaped.
256
+ # @return [CArray]
257
+ # @overload cumprod
258
+ # Per-slab inclusive running product (float64), reference-shaped.
259
+ # @return [CArray]
260
+ # @overload cummax
261
+ # Per-slab inclusive running maximum (reference dtype), reference-shaped.
262
+ # @return [CArray]
263
+ # @overload cummin
264
+ # Per-slab inclusive running minimum (reference dtype), reference-shaped.
265
+ # @return [CArray]
266
+ # @overload cumcount
267
+ # Per-slab running count of present cells (int64), reference-shaped.
268
+ # @return [CArray]
269
+ [:cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |op|
270
+ define_method(op) { @reference.send(op, axis: single_scan_axis(op)) }
271
+ end
272
+
273
+ private
274
+
275
+ # The single slab axis for the order surface (core sort takes one integer
276
+ # axis). A multi-axis slab has no single sort axis, so raise.
277
+ def single_sort_axis (op)
278
+ return @slab_axes[0] if @slab_axes.size == 1
279
+ raise ArgumentError,
280
+ "#{op}: a multi-axis slab (axes #{@slab_axes.inspect}) has no single " \
281
+ "sort axis; sort one axis at a time"
282
+ end
283
+
284
+ # The single slab axis for a segment scan (core scan takes one integer axis).
285
+ # A multi-axis slab has no single scan axis, so raise.
286
+ def single_scan_axis (op)
287
+ return @slab_axes[0] if @slab_axes.size == 1
288
+ raise ArgumentError,
289
+ "#{op}: a multi-axis slab (axes #{@slab_axes.inspect}) has no single " \
290
+ "scan axis; scan one axis at a time"
291
+ end
292
+ end
@@ -0,0 +1,291 @@
1
+ # CAStack view-default composition surface
2
+ #
3
+ # This file holds the composition methods that build (directly or via
4
+ # reshape/transpose) a CAStack view from storage-uniform pieces:
5
+ #
6
+ # stack = K parents pushed onto a new K axis
7
+ # meld = melt + weld; pieces dissolve their boundaries along the
8
+ # named axis and are regarded as one (uniform)
9
+ # montage = uniform tiles arranged in a regular grid (ImageMagick)
10
+ #
11
+ # Their ragged eager counterparts (concatenate / mosaic / tabulate) live in
12
+ # carray/methods/composition.rb. See that file's header for the full
13
+ # composition cheat sheet.
14
+ #
15
+ # Examples (a, b, c are each shape [3, 4]):
16
+ #
17
+ # CArray.stack([a, b, c]) #=> view, shape (3, 3, 4) (K outermost)
18
+ # CArray.stack([a, b, c], axis: -1) #=> view, shape (3, 4, 3) (RGB pattern)
19
+ # CArray.meld([a, b, c]) #=> view, shape (9, 4)
20
+ # CArray.meld([a, b, c], axis: 1) #=> view, shape (3, 12)
21
+ # CArray.montage([a, b, c, d], [2, 2], axis: 0) #=> view, shape (6, 8) (2x2 image tile)
22
+ #
23
+ # Known limitation: scalar indexing on a CAStack-rooted view drops ndim
24
+ # and triggers a full
25
+ # materialise fallback. Use range indexing (e.g. `meld(...)[k..k, nil]`
26
+ # instead of `meld(...)[k, nil]`) for partial-use perf, or `.to_ca` upfront
27
+ # to materialise eagerly.
28
+
29
+ class CArray
30
+
31
+ # ---------------------------------------------------------------- view-default
32
+
33
+ # Stack `list` of CArrays along a new axis inserted at position
34
+ # `axis:` (default 0 = outermost). Returns a view
35
+ # (CAStack with k_axis = axis) when inputs are storage-uniform, or a
36
+ # Face-lifted view (= CATime, CATimedelta, ...) when inputs are
37
+ # homogeneous Face instances. Call `.to_ca` to materialise eagerly.
38
+ #
39
+ # `data_type:` kwarg (optional, primitive Symbol only) forces primitive
40
+ # promotion; cannot be used when the list contains Face elements. Class
41
+ # / Module targets are rejected (= `data_type: CATime` is invalid;
42
+ # use auto-detect for Face round-trip).
43
+ #
44
+ # 3.0 (post-K_AXIS, F.S1-stack landed): replaces `CArray.merge`. The
45
+ # low-level raw constructor is `CAStack.new(list, axis:)`; this method
46
+ # is the high-level surface that performs promote_list + CAStack.new +
47
+ # (face_lift when homogeneous Face).
48
+ # @overload stack(list, axis: 0, data_type: nil)
49
+ # Returns a view stacking uniform-shape arrays along a new K
50
+ # axis at position `axis`. Runs `promote_list` for a common
51
+ # `data_type` and re-wraps homogeneous Face inputs via
52
+ # `face_lift`. Output `ndim` is one greater than each piece.
53
+ # @param list [Array<CArray>] pieces to stack; must not be empty.
54
+ # @param axis [Integer] position of the new K axis.
55
+ # @param data_type [Symbol, Integer, nil] result `data_type`;
56
+ # inferred when `nil`.
57
+ # @return [CArray] CAStack view.
58
+ # @raise [ArgumentError] when `list` is empty.
59
+ def self.stack (list, axis: 0, data_type: nil)
60
+ raise ArgumentError, "stack: list must not be empty" if list.empty?
61
+ list = CArray.promote_list(list, data_type: data_type)
62
+ axis = CArray.normalize_axis(axis, list[0].ndim + 1, "stack")
63
+ CAStack.new(list, axis: axis) # CAStack.new does Face lift internally
64
+ end
65
+
66
+ # Returns a {CAMeld} view of the arrays welded along an existing axis.
67
+ # No data is copied; reads gather from parents on demand and writes flow
68
+ # back to them (chain composability preserved).
69
+ #
70
+ # Pieces must agree on `ndim`, `data_type`, byte width, and every axis
71
+ # length except `axis` (the "meld axis"). Mismatched `data_type` raises:
72
+ # cast the pieces yourself (`.to_type(:float64)`) or use
73
+ # {CArray.concatenate} (eager, auto-casts).
74
+ #
75
+ # "meld" = melt + weld — pieces dissolve their boundaries along the named
76
+ # axis and are regarded as one.
77
+ #
78
+ # @overload meld(*arrays, axis: 0)
79
+ # @overload meld(list, axis: 0)
80
+ # Convenience form: a single Array argument is treated as the list.
81
+ # @param arrays [Array<CArray>] pieces to weld. A single Array
82
+ # argument is accepted for compatibility with older callers.
83
+ # @param axis [Integer] existing axis to extend (normalises negative
84
+ # values against the reference ndim).
85
+ # @return [CAMeld] view over the welded pieces.
86
+ # @raise [ArgumentError] when the list is empty, ndim mismatch,
87
+ # data_type mismatch, or non-axis dim mismatch across pieces
88
+ # (surfaced by CAMeld.new).
89
+ def self.meld (*arrays, axis: 0)
90
+ if arrays.length == 1 && arrays[0].is_a?(Array)
91
+ arrays = arrays[0]
92
+ end
93
+ raise ArgumentError, "meld: list must not be empty" if arrays.empty?
94
+ first = arrays[0]
95
+ unless first.is_a?(CArray)
96
+ raise ArgumentError, "meld: entries must be CArray (got #{first.class})"
97
+ end
98
+ axis_norm = CArray.normalize_axis(axis, first.ndim, "meld")
99
+ # Flatten nested CAMeld inputs that share our meld axis: they already
100
+ # describe a segment sequence, so absorbing their parents keeps chain
101
+ # depth at 1 (avoids 2-level xfer_all / reduce chains through the
102
+ # intermediate CAMeld). A CAMeld with a different meld_axis is left
103
+ # intact — its segment structure is orthogonal.
104
+ if arrays.any? { |a| a.is_a?(CAMeld) && a.meld_axis == axis_norm }
105
+ arrays = arrays.flat_map { |a|
106
+ a.is_a?(CAMeld) && a.meld_axis == axis_norm ? a.parents : [a]
107
+ }
108
+ end
109
+ CAMeld.new(arrays, axis: axis_norm)
110
+ end
111
+ end
112
+
113
+ class CArray
114
+ # Returns a {CAMeld} view of `[self, *others]` welded along `axis`.
115
+ # Instance form of {CArray.meld}; non-destructive, see the class method
116
+ # for full semantics.
117
+ #
118
+ # @overload meld(*others, axis: 0)
119
+ # @param others [Array<CArray>] additional pieces.
120
+ # @param axis [Integer] existing axis to extend.
121
+ # @return [CAMeld]
122
+ # @raise [ArgumentError] when no `others` are given.
123
+ def meld (*others, axis: 0)
124
+ raise ArgumentError, "meld: at least one other array required" if others.empty?
125
+ CArray.meld(self, *others, axis: axis)
126
+ end
127
+
128
+ # Arrange `list` of uniform-shape pieces in a `tdim`-shape grid that
129
+ # extends parent axes `axis..axis+tdim.size-1` by the corresponding
130
+ # `tdim[i]` factor (= ImageMagick `montage` analog). Output ndim equals
131
+ # each piece's ndim; the tile axes occupy positions
132
+ # `axis..axis+tdim.size-1`. Returns a view; call `.to_ca` to materialise.
133
+ #
134
+ # `tdim.product` must equal `list.size`. For non-uniform pieces along
135
+ # tile axes, use `CArray.mosaic`.
136
+ #
137
+ # Example (parent shape (3, 4), 6-element list, tdim=[2, 3], axis: 0):
138
+ #
139
+ # CArray.montage([a, b, c, d, e, f], [2, 3], axis: 0)
140
+ # #=> shape (6, 12) -- 2 rows x 3 cols grid of (3, 4) blocks
141
+ # # +-----+-----+-----+
142
+ # # | a | b | c | rows 0..2
143
+ # # +-----+-----+-----+
144
+ # # | d | e | f | rows 3..5
145
+ # # +-----+-----+-----+
146
+ #
147
+ # 3.0 (post K_AXIS / promote_list / stack rename): renamed from `combine`
148
+ # (= 20-year vocabulary that didn't describe the action). Positional
149
+ # `at` replaced with `axis:` kwarg for consistency with bind / stack.
150
+ # Parameter order changed from `(tdim, list, at)` to `(list, tdim, axis:)`
151
+ # to align with bind / stack (`list` first).
152
+ # @overload montage(list, tdim, axis: 0, data_type: nil)
153
+ # Returns a view arranging uniform-shape pieces in a `tdim`-shape
154
+ # grid that extends parent axes `axis..axis+tdim.size-1` by the
155
+ # corresponding `tdim[i]` factors. Output `ndim` equals each
156
+ # piece's `ndim`. `tdim.product` must equal `list.size`. For
157
+ # non-uniform pieces along tile axes, use `CArray.mosaic`.
158
+ # @param list [Array<CArray>] pieces to arrange.
159
+ # @param tdim [Array<Integer>] tile grid shape.
160
+ # @param axis [Integer] first tile axis in the result.
161
+ # @param data_type [Symbol, Integer, nil] result `data_type`.
162
+ # @return [CArray] tiled view.
163
+ # @raise [ArgumentError] when `list` is empty or `tdim.product !=
164
+ # list.size`.
165
+ def self.montage (list, tdim, axis: 0, data_type: nil)
166
+ raise ArgumentError, "montage: list must not be empty" if list.empty?
167
+ unless tdim.is_a?(Array) && tdim.size > 0
168
+ raise ArgumentError, "montage: tdim must be a non-empty Array of Integer"
169
+ end
170
+ expected = tdim.inject(1) { |acc, n| acc * n }
171
+ unless expected == list.size
172
+ raise ArgumentError,
173
+ "montage: tdim product (#{expected}) must equal list size (#{list.size})"
174
+ end
175
+
176
+ list = CArray.promote_list(list, data_type: data_type)
177
+ parent_shape = list[0].shape
178
+ ntile = tdim.size
179
+ nparent = parent_shape.size
180
+ axis = CArray.normalize_axis(axis, nparent - ntile + 1, "montage")
181
+
182
+ s = CArray.stack(list).reshape(*tdim, *parent_shape) # (K, *) → (*tdim, *)
183
+
184
+ # Interleave: tile axis i (= s axis i, i ∈ [0, ntile)) is moved to
185
+ # just before parent axis (axis + i) in s coordinates (= s axis
186
+ # ntile + axis + i).
187
+ perm = []
188
+ nparent.times do |j|
189
+ if j.between?(axis, axis + ntile - 1)
190
+ perm << (j - axis) # tile axis
191
+ end
192
+ perm << ntile + j # parent axis
193
+ end
194
+ s = s.transpose(*perm)
195
+
196
+ # Merge each (tile[i], parent[axis+i]) pair via reshape.
197
+ new_shape = parent_shape.dup
198
+ ntile.times { |i| new_shape[axis + i] *= tdim[i] }
199
+ s.reshape(*new_shape)
200
+ end
201
+
202
+ # Instance-side stack: build a new K-stack from `[self] + others`
203
+ # along the new K axis at position `axis:`. Always treats `self` as a
204
+ # parent (= even when self is a CAStack, the resulting stack has self
205
+ # as one of its parents, NOT flat-appended into self's parents).
206
+ #
207
+ # For flat-appending into an existing CAStack (= same k_axis, parents
208
+ # extended), use `CAStack#append`.
209
+ #
210
+ # 3.0: high-level Face-aware surface, mirrors `CArray.stack(list, axis:)`.
211
+ # @overload stack(*others, axis: 0, data_type: nil)
212
+ # Returns a K-stack view built from `[self] + others` along a
213
+ # new K axis at position `axis`. Always treats `self` as one
214
+ # parent, even when `self` is a CAStack; use {CAStack#append} to
215
+ # flat-append into an existing CAStack.
216
+ # @param others [Array<CArray>] additional parents.
217
+ # @param axis [Integer] position of the new K axis.
218
+ # @param data_type [Symbol, Integer, nil] result `data_type`.
219
+ # @return [CArray] CAStack view.
220
+ # @raise [ArgumentError] when no `others` are given.
221
+ def stack (*others, axis: 0, data_type: nil)
222
+ raise ArgumentError, "stack: at least one other parent required" if others.empty?
223
+ CArray.stack([self] + others, axis: axis, data_type: data_type)
224
+ end
225
+
226
+ # Split self along a single axis into an Array of (ndim-1)-D slices, each
227
+ # a writable CABlock view. The exact inverse of CArray.stack -- split's
228
+ # slices are all the same shape, so they round-trip back through stack:
229
+ #
230
+ # CArray.stack(a.split(axis: k), axis: k) == a
231
+ #
232
+ # a = CA_INT([[1,2,3], [4,5,6]])
233
+ # a.split(axis: 0) #=> [ <[1,2,3]>, <[4,5,6]> ] (row views)
234
+ # a.split(axis: 1) #=> [ <[1,4]>, <[2,5]>, <[3,6]> ] (column views)
235
+ #
236
+ # 3.0 breaking:
237
+ # - returns a Ruby Array of views (was an object CArray), so it
238
+ # round-trips with CArray.stack (which takes an Array)
239
+ # - +axis:+ takes a single Integer (the multi-axis Array form, which
240
+ # returned an N-D object grid, is no longer accepted)
241
+ # - pieces are CABlock views, NOT copies; writing through a piece
242
+ # mutates +self+. Chain +.copy+ / +.to_ca+ for independent entities.
243
+ # @overload split(axis:)
244
+ # Returns an Array of `(ndim-1)`-D writable CABlock views obtained
245
+ # by splitting `self` along `axis`. Inverse of `CArray.stack`.
246
+ # Slices share storage with `self`; chain `.copy` for independent
247
+ # entities.
248
+ # @param axis [Integer] axis to split along.
249
+ # @return [Array<CArray>] one slice per index along `axis`.
250
+ # @raise [ArgumentError] when `axis` is not a single Integer.
251
+ def split (axis:)
252
+ if axis.is_a?(Array)
253
+ raise ArgumentError, "split: axis must be a single Integer"
254
+ end
255
+ k = normalize_axis(axis, "split")
256
+ (0...shape[k]).map do |i|
257
+ idx = [nil] * ndim
258
+ idx[k] = i
259
+ self[*idx]
260
+ end
261
+ end
262
+
263
+ end
264
+
265
+ # CAStack.new(list, axis: 0) is implemented in C (ext/ca_obj_stack.c
266
+ # rb_ca_stack_s_new): it overrides Class#new to inspect Face homogeneity
267
+ # and (when applicable) re-wrap the raw CAStack via ca_face_lift. The
268
+ # Ruby class only adds the #append instance method below.
269
+ class CAStack
270
+ # Flat-append `others` into self's parents, preserving the receiver's
271
+ # k_axis. Returns a new CAStack with the combined parents list.
272
+ #
273
+ # Distinct from `CArray#stack(other)` which always treats self as a
274
+ # single parent of a new K-stack -- here, the existing K-stack is
275
+ # extended in place.
276
+ #
277
+ # Example:
278
+ # s = CArray.stack([a, b], axis: 1) # 2-parent stack, k_axis=1
279
+ # s.append(c, d) # 4-parent stack, k_axis=1
280
+ # @overload append(*others)
281
+ # Returns a new CAStack whose parents are `self.parents + others`,
282
+ # preserving `self.k_axis`. Distinct from `CArray#stack`, which
283
+ # always treats `self` as a single parent of a new K-stack.
284
+ # @param others [Array<CArray>] extra parents to append.
285
+ # @return [CAStack]
286
+ # @raise [ArgumentError] when no `others` are given.
287
+ def append (*others)
288
+ raise ArgumentError, "append: at least one parent required" if others.empty?
289
+ CArray.stack(self.parents + others, axis: self.k_axis)
290
+ end
291
+ end