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,39 @@
1
+ class CArray
2
+
3
+ # @overload index(axis: 0)
4
+ # Returns a writable int32 CArray holding the coordinate ramp
5
+ # `[0, 1, ..., shape[axis] - 1]` along `axis`, in an open
6
+ # broadcast shape: size `shape[axis]` on that axis and 1 on
7
+ # every other axis (e.g. for `(d0, d1, d2)`, `index(axis: 1)`
8
+ # returns `(1, d1, 1)`).
9
+ #
10
+ # The result broadcasts against `self` in element-wise ops
11
+ # without materialising the full shape. For the dense
12
+ # full-shape grid use
13
+ # `index(axis: k).broadcast_to(*shape)` or `CArray.meshgrid`.
14
+ #
15
+ # @param axis [Integer] axis to vary (negative counts from the
16
+ # end).
17
+ # @return [CArray] writable int32 CArray; size 1 on every axis
18
+ # except `axis`, which has size `shape[axis]`.
19
+ def index (axis: 0)
20
+ k = normalize_axis(axis, "index")
21
+ oshape = Array.new(ndim, 1)
22
+ oshape[k] = shape[k]
23
+ CArray.int32(*oshape).seq!
24
+ end
25
+
26
+ # @overload indices
27
+ # Returns an Array of +ndim+ coordinate ramps, one per axis, each in
28
+ # the open broadcast shape of {#index}.
29
+ # @return [Array<CArray>] one open coordinate ramp per axis.
30
+ # @overload indices { |*ramps| ... }
31
+ # Yields the +ndim+ open coordinate ramps as splat arguments.
32
+ # @yield [*ramps] the per-axis open coordinate ramps.
33
+ # @return [Object] the block's return value.
34
+ def indices
35
+ list = (0...ndim).map { |k| index(axis: k) }
36
+ block_given? ? yield(*list) : list
37
+ end
38
+
39
+ end
@@ -0,0 +1,99 @@
1
+ # Block insert / delete pair (insert_block / delete_block): grow or shrink
2
+ # an array by inserting or removing a block region along each axis.
3
+
4
+ class CArray
5
+
6
+ # @overload insert_block(offset, bsize, &block)
7
+ # Returns a new CArray obtained by inserting a block of size
8
+ # `bsize` (per axis) at `offset`, growing the array.
9
+ #
10
+ # Per axis, `offset` accepts `0..shape[i]` (equal to `shape[i]`
11
+ # appends at the end) and a negative value counts from the end;
12
+ # `bsize[i]` must be non-negative. Inserted cells are filled by
13
+ # the block's return value, or left at the type's default when
14
+ # no block is given. Preserves fixlen bytes and Face identity
15
+ # (built on the storage layout, re-wrapped via `face_lift`).
16
+ # The `offset` array is not mutated.
17
+ #
18
+ # @param offset [Array<Integer>] insertion offset per axis.
19
+ # @param bsize [Array<Integer>] block size per axis.
20
+ # @yieldreturn [Object] fill value for the inserted cells.
21
+ # @return [CArray] grown copy.
22
+ # @raise [ArgumentError] on `ndim` mismatch or out-of-range
23
+ # offset / size.
24
+ def insert_block (offset, bsize, &block)
25
+ if offset.size != ndim or bsize.size != ndim
26
+ raise ArgumentError, "ndim mismatch"
27
+ end
28
+ offset = offset.dup # normalize without mutating the caller's array
29
+ newdim = shape
30
+ grids = shape.map{|d| CArray.int32(d) }
31
+ ndim.times do |i|
32
+ offset[i] += shape[i] if offset[i] < 0
33
+ if offset[i] < 0 or offset[i] > shape[i] or bsize[i] < 0
34
+ raise ArgumentError, "invalid offset or size at axis #{i}"
35
+ end
36
+ if bsize[i] > 0
37
+ newdim[i] += bsize[i]
38
+ end
39
+ grids[i][0...offset[i]].seq! if offset[i] > 0
40
+ # offset == dim (append) leaves nothing on the upper side to shift.
41
+ grids[i][offset[i]..-1].seq!(offset[i]+bsize[i]) if offset[i] < shape[i]
42
+ end
43
+ # Build at the storage layout (preserving bytes for fixlen / Face),
44
+ # then re-wrap as the same Face.
45
+ face_parent = self.face? ? self : nil
46
+ src = self
47
+ src = src.parent while src.face?
48
+ dt = src.data_type
49
+ bytes = (dt == :fixlen) ? src.bytes : nil
50
+ out = CArray.new(dt, newdim, bytes: bytes)
51
+ if block_given?
52
+ sel = out.true
53
+ sel[*grids] = 0
54
+ out[sel] = block.call
55
+ end
56
+ out[*grids] = src
57
+ out = out.face_lift(face_parent) if face_parent
58
+ return out
59
+ end
60
+
61
+ # @overload delete_block(offset, bsize)
62
+ # Returns a new CArray obtained by deleting a block of `bsize`
63
+ # cells (per axis) starting at `offset`, shrinking the array.
64
+ #
65
+ # Per axis, `offset` accepts `0..shape[i]-1` (negative counts
66
+ # from the end) and `bsize[i]` must be non-negative with
67
+ # `offset[i] + bsize[i] <= shape[i]`. Bytes and Face identity
68
+ # are preserved naturally because the result is built by
69
+ # fancy-index copy of `self`. The `offset` array is not mutated.
70
+ #
71
+ # @param offset [Array<Integer>] start of the block per axis.
72
+ # @param bsize [Array<Integer>] block size per axis.
73
+ # @return [CArray] shrunk copy.
74
+ # @raise [ArgumentError] on `ndim` mismatch or out-of-range
75
+ # offset / size.
76
+ def delete_block (offset, bsize)
77
+ if offset.size != ndim or bsize.size != ndim
78
+ raise ArgumentError, "ndim mismatch"
79
+ end
80
+ offset = offset.dup # normalize without mutating the caller's array
81
+ newdim = shape
82
+ grids = []
83
+ ndim.times do |i|
84
+ offset[i] += shape[i] if offset[i] < 0
85
+ if bsize[i] < 0 or offset[i] < 0 or offset[i] >= shape[i] or
86
+ offset[i] + bsize[i] > shape[i]
87
+ raise ArgumentError, "invalid offset or size at axis #{i}"
88
+ end
89
+ newdim[i] -= bsize[i]
90
+ grids[i] = CArray.int32(newdim[i])
91
+ grids[i][0...offset[i]].seq! if offset[i] > 0
92
+ if offset[i] + bsize[i] < shape[i]
93
+ grids[i][offset[i]..-1].seq!(offset[i]+bsize[i])
94
+ end
95
+ end
96
+ return self[*grids].copy
97
+ end
98
+
99
+ end
@@ -0,0 +1,141 @@
1
+ class CArray
2
+
3
+ # @overload is_in(values)
4
+ # Returns a boolean CArray of the same shape as `self`, `true` at
5
+ # each cell whose value appears in the set `values`.
6
+ #
7
+ # `values` is treated as a set, not as an operand to broadcast: it
8
+ # may be any shape (or an Array / Range) and is flattened to a
9
+ # single seen-set, so its shape need not match `self`. Only one
10
+ # argument is accepted; to test a few immediate values pass an
11
+ # Array (`a.is_in([0, -1])`).
12
+ #
13
+ # When `self` and `values` have different numeric dtypes they are
14
+ # promoted to a common type first (the same promotion binops use,
15
+ # {CArray.result_type}), so membership is value-correct across
16
+ # dtypes (e.g. an int cell equals a float set element of the same
17
+ # value, and a fractional set element never truncates onto an int
18
+ # cell). Genuinely incompatible dtypes (e.g. numeric vs fixlen)
19
+ # raise.
20
+ #
21
+ # Membership is value-based and shares the distinctness of the
22
+ # value-hash discovery family ({#unique} / {#value_counts}):
23
+ # numeric follows `==` with all NaN collapsed to one value and
24
+ # -0.0 == +0.0; `CA_OBJECT` follows Ruby `hash` / `eql?` with Float
25
+ # NaN collapsed; `CA_FIXLEN` follows byte equality.
26
+ #
27
+ # Masked cells of `values` do not enter the set. Masked cells of
28
+ # `self` stay masked in the result (membership is unknown), so
29
+ # `is_in` propagates `self`'s mask like an element-wise comparison.
30
+ #
31
+ # For a per-fiber "does this fiber contain any of these values"
32
+ # reduction, compose with {#any}: `a.is_in(values).any(axis: k)`.
33
+ #
34
+ # @param values [CArray, Array, Range] the set to test membership
35
+ # against. Promoted with `self` to a common dtype.
36
+ # Between two time arrays the question is about instants, not ticks:
37
+ # `values` is reconciled into `self`'s unit first, so a `:D` array and an
38
+ # `:h` array match on the instants they share. The same holds for the set
39
+ # operations below, whose results come back as `self`'s own type.
40
+ #
41
+ # @return [CArray] boolean CArray of the same shape as `self`.
42
+ def is_in (values)
43
+ a, b = promote_value_set(values)
44
+ a.__send__(:__is_in__, b)
45
+ end
46
+
47
+ # @overload intersection(other, sort: false)
48
+ # Returns a 1-D CArray of the distinct values appearing in both
49
+ # `self` and `other`, in `self`'s first-appearance order.
50
+ #
51
+ # Value-based, sharing the distinctness of the discovery family
52
+ # (see {#is_in}); `self` and `other` are promoted to a common dtype.
53
+ # Masked cells of either array do not participate. The result is
54
+ # always flat, like {#unique}, because the distinct values of a
55
+ # fiber vary in number.
56
+ #
57
+ # @param other [CArray, Array, Range] promoted with `self`.
58
+ # @param sort [Boolean] when true, return the values sorted
59
+ # ascending instead of in first-appearance order.
60
+ # @return [CArray] 1-D CArray of the common distinct values.
61
+ def intersection (other, sort: false)
62
+ a, b = promote_value_set(other)
63
+ r = a.__send__(:__intersection__, b)
64
+ sort ? r.sort : r
65
+ end
66
+
67
+ # @overload difference(other, sort: false)
68
+ # Returns a 1-D CArray of the distinct values in `self` that are
69
+ # absent from `other`, in `self`'s first-appearance order.
70
+ # See {#intersection} for the shared semantics and options.
71
+ #
72
+ # @param other [CArray, Array, Range] promoted with `self`.
73
+ # @param sort [Boolean] when true, return the values sorted ascending.
74
+ # @return [CArray] 1-D CArray of the self-only distinct values.
75
+ def difference (other, sort: false)
76
+ a, b = promote_value_set(other)
77
+ r = a.__send__(:__difference__, b)
78
+ sort ? r.sort : r
79
+ end
80
+
81
+ # @overload union(other, sort: false)
82
+ # Returns a 1-D CArray of the distinct values appearing in either
83
+ # `self` or `other`, in self-then-other first-appearance order.
84
+ # See {#intersection} for the shared semantics and options.
85
+ #
86
+ # @param other [CArray, Array, Range] promoted with `self`.
87
+ # @param sort [Boolean] when true, return the values sorted ascending
88
+ # (a merged, ordered set — e.g. a common time axis).
89
+ # @return [CArray] 1-D CArray of the combined distinct values.
90
+ def union (other, sort: false)
91
+ a, b = promote_value_set(other)
92
+ r = a.__send__(:__union__, b)
93
+ sort ? r.sort : r
94
+ end
95
+
96
+ private
97
+
98
+ # Reconcile a set-valued argument with self to a common data_type via
99
+ # CArray.result_type (the single-source promotion rule the eager binop and
100
+ # lazy CABinOp share), returning [self', set']. Only dtypes are reconciled,
101
+ # never shapes: unlike the binop coercion (cast_self_or_other) the set's
102
+ # shape never broadcasts against self's, so a size-1 self keeps its shape.
103
+ # result_type raises for genuinely incompatible dtypes (numeric vs fixlen).
104
+ #
105
+ # A bare Array / Range has no intrinsic dtype, so the common type is inferred
106
+ # from self and the individual elements (result_type classifies each scalar).
107
+ # This keeps a fractional literal from truncating onto an int self, and an
108
+ # int literal from boxing a float self into the object lane (where Float 2.0
109
+ # is not eql? Integer 2). A CArray argument uses its own dtype; any other
110
+ # operand (Numo, a MemoryView producer, ...) comes in through wrap_readonly,
111
+ # the canonical type-coercion entry, so its format's dtype drives the promote.
112
+ def promote_value_set (values)
113
+ case values
114
+ when Array then return promote_elements(values)
115
+ when Range then return promote_elements(values.to_a)
116
+ end
117
+ set = values.is_a?(CArray) ? values : CArray.wrap_readonly(values)
118
+ return [self, set] if set.data_type == data_type # common fast path
119
+ t = CArray.result_type(self, set)
120
+ [coerce_self(t), set.to_type(t)]
121
+ end
122
+
123
+ # Bare Array / Range against numeric self: infer the common numeric type
124
+ # from self and the elements (each classified by result_type), so a
125
+ # fractional literal promotes self to float instead of truncating. Against
126
+ # object / fixlen self the elements are values, not dtype specifiers (a
127
+ # String is a value, not a type name), so build the set in self's dtype.
128
+ def promote_elements (elems)
129
+ if data_type == CA_OBJECT || data_type == CA_FIXLEN
130
+ [self, elems.to_ca.to_type(data_type)]
131
+ else
132
+ t = CArray.result_type(self, *elems)
133
+ [coerce_self(t), elems.to_ca.to_type(t)]
134
+ end
135
+ end
136
+
137
+ def coerce_self (t)
138
+ CArray.result_type(self) == t ? self : to_type(t)
139
+ end
140
+
141
+ end
@@ -0,0 +1,90 @@
1
+ class CArray
2
+
3
+ # @overload join(sep = nil)
4
+ # Flat form. Stringifies and concatenates every element of `self`
5
+ # (as if `to_a.flatten.join(sep)`). Returns a String.
6
+ #
7
+ # @overload join(sep = "", axis:, keep_axis: false)
8
+ # Per-axis form. Reduces `axis` into strings, one per fiber along
9
+ # that axis, and returns the result as a CArray with `axis` removed
10
+ # (or set to 1 when `keep_axis: true`). Composable — call `join`
11
+ # again on the result to collapse another axis or fold to a String.
12
+ #
13
+ # For a 1-D `self`, the axis form fully reduces and returns the
14
+ # String directly (matching the flat form and the reduction
15
+ # convention).
16
+ #
17
+ # @param sep [String] separator between elements along the axis.
18
+ # @param axis [Integer] axis to reduce (negative allowed).
19
+ # @param keep_axis [Boolean] keep the reduced axis as length 1.
20
+ # @return [CArray, String] a CArray of strings, or a String when
21
+ # `self` is 1-D.
22
+ #
23
+ # @example Flat form
24
+ # a = CArray.object(3, 3).seq("a", :succ)
25
+ # a.join # => "abcdefghi"
26
+ # a.join(",") # => "a,b,c,d,e,f,g,h,i"
27
+ #
28
+ # @example Per-axis form
29
+ # a = CArray.int32(3, 3).seq
30
+ # a.join(" ", axis: 1) # → CArray["0 1 2", "3 4 5", "6 7 8"]
31
+ # a.join(" ", axis: 1).join("\n") # => "0 1 2\n3 4 5\n6 7 8"
32
+ #
33
+ # @note The 2.x multi-separator form `a.join("\n", ",")` was removed
34
+ # in 3.0; use the axis form and chain, e.g.
35
+ # `a.join(",", axis: 1).join("\n")`.
36
+ def join (*argv, axis: nil, keep_axis: false)
37
+ if argv.size > 1
38
+ raise ArgumentError,
39
+ "join accepts at most one positional separator " \
40
+ "(the 2.x multi-separator form was removed in 3.0; " \
41
+ "use axis: for per-axis join and chain)"
42
+ end
43
+ sep = argv.first # nil or String
44
+
45
+ if axis.nil?
46
+ return sep.nil? ? to_a.join : to_a.join(sep)
47
+ end
48
+
49
+ ax = Integer(axis)
50
+ ax += ndim if ax < 0
51
+ if ax < 0 || ax >= ndim
52
+ raise ArgumentError,
53
+ "axis #{axis.inspect} out of range for ndim=#{ndim}"
54
+ end
55
+
56
+ sep_str = sep || ""
57
+
58
+ # Bring `ax` to the innermost position so we can iterate fibers as
59
+ # rows of a 2-D reshape. transpose returns a view; reshape may
60
+ # materialize on non-contig, which is fine for this formatting op.
61
+ if ax == ndim - 1
62
+ t = self
63
+ else
64
+ order = (0...ndim).to_a
65
+ order << order.delete_at(ax)
66
+ t = transpose(*order)
67
+ end
68
+ inner = t.shape[-1]
69
+ outer_n = t.elements / inner # 1 when ndim == 1
70
+ flat = t.reshape(outer_n, inner)
71
+
72
+ strings = Array.new(outer_n) { |i| flat[i, nil].to_a.join(sep_str) }
73
+
74
+ if ndim == 1
75
+ # Full reduction: return the String directly, or a length-1
76
+ # CArray when keep_axis was requested.
77
+ return keep_axis ? CA_OBJECT([strings.first]) : strings.first
78
+ end
79
+
80
+ result_shape = shape.dup
81
+ if keep_axis
82
+ result_shape[ax] = 1
83
+ else
84
+ result_shape.delete_at(ax)
85
+ end
86
+
87
+ CA_OBJECT(strings).reshape(*result_shape)
88
+ end
89
+
90
+ end
@@ -0,0 +1,47 @@
1
+ class CArray
2
+
3
+ # User-facing YARD docs for #locate_addr and #locate_nearest_addr live
4
+ # in yard-stubs/carray_order.rb (grouped with the search family).
5
+
6
+ def locate_addr (ref)
7
+ ref = ref.to_ca unless ref.is_a?(CArray)
8
+ # Put self and ref in a common lane via the single-source promotion rule
9
+ # (CArray.result_type), so a fractional query against an int ref is compared
10
+ # at the promoted type instead of truncating (1.5 no longer matches 1).
11
+ # to_type is elementwise and order-preserving, so the addresses stay valid
12
+ # indices into ref. result_type raises for cross-family input.
13
+ t = CArray.result_type(self, ref)
14
+ q = (data_type == t) ? self : to_type(t)
15
+ r = (ref.data_type == t) ? ref : ref.to_type(t)
16
+ q.send(:__locate_addr__, r)
17
+ end
18
+
19
+ def locate_nearest_addr (ref, direction: :round, tolerance: nil)
20
+ unless [:round, :floor, :ceil].include?(direction)
21
+ raise ArgumentError,
22
+ "locate_nearest_addr: direction must be :round / :floor / " \
23
+ ":ceil (got #{direction.inspect})"
24
+ end
25
+ ri = ref.sort_addr
26
+ rs = ref[ri]
27
+ sec = rs.linear_section(self)
28
+ unless sec.is_a?(CArray)
29
+ # A single-element (scalar-like) self makes linear_section collapse to
30
+ # its scalar-query path, which returns a bare Float (or nil when out of
31
+ # range) instead of a CArray. Rebuild a self-shaped float64 CArray so
32
+ # the mask_invalid -> direction -> project pipeline stays array-valued
33
+ # and the returned addr array matches self's shape.
34
+ fill = CArray.float64(*shape)
35
+ fill[] = sec.nil? ? UNDEF : sec
36
+ sec = fill
37
+ end
38
+ si = sec.mask_invalid.send(direction).int64
39
+ idx = ri.project(si)
40
+ if tolerance
41
+ dist = (ref.project(idx) - self).abs
42
+ idx[dist > tolerance] = UNDEF
43
+ end
44
+ idx
45
+ end
46
+
47
+ end
@@ -0,0 +1,41 @@
1
+ class CArray
2
+
3
+ # @overload mask_duplicates(axis: nil)
4
+ # Returns a shape-preserving copy of `self` with the mask set at
5
+ # every cell whose value duplicates an earlier-seen one; the
6
+ # first occurrence is kept.
7
+ #
8
+ # With `axis: nil` duplicates are detected in flatten (row-major)
9
+ # order across the whole array. With `axis: k` duplicates are
10
+ # detected per-fiber along axis `k`, independently for each
11
+ # fiber. Marking duplicates (not compressing) is what makes the
12
+ # per-axis form expressible: fibers may hold different numbers of
13
+ # distinct values, so a compressed result would be ragged.
14
+ #
15
+ # Distinctness matches the value-hash discovery family. Numeric:
16
+ # `==` with all NaN collapsed to one value (so the second and
17
+ # later NaN are duplicates) and -0.0 == +0.0. `CA_OBJECT` /
18
+ # `CA_FIXLEN`: Ruby `eql?` / `hash` (distinct NaN objects stay
19
+ # distinct). Masked input cells stay masked and do not participate
20
+ # in duplicate judging. Both `axis: nil` and `axis: k` work.
21
+ #
22
+ # @param axis [Integer, nil] axis to detect duplicates along;
23
+ # `nil` uses flatten order.
24
+ # @return [CArray] shape-preserving copy of `self` with
25
+ # duplicates masked.
26
+ def mask_duplicates (axis: nil)
27
+ dup =
28
+ if axis.nil?
29
+ # One seen-set over the flattened array, then restore shape.
30
+ flatten.send(:__mask_duplicates__, 0).reshape(*shape)
31
+ else
32
+ # Per-fiber single-pass seen-set hash (C __mask_duplicates__): one lane
33
+ # per dtype family (integer widen, float bitwise key with NaN collapse,
34
+ # object rb_hash + rb_eql, fixlen byte-hash + memcmp, boolean via the
35
+ # uint8 lane). O(distinct) memory, no sort/gather/scatter buffers.
36
+ __mask_duplicates__(normalize_axis(axis, "mask_duplicates"))
37
+ end
38
+ mask_where(dup)
39
+ end
40
+
41
+ end
@@ -0,0 +1,91 @@
1
+ class CArray
2
+
3
+ # @overload meshgrid(*axes, indexing: "xy", copy: true, sparse: false)
4
+ # Returns coordinate matrices built from 1-D coordinate vectors.
5
+ #
6
+ # Given N 1-D vectors, produces N arrays each broadcasting one
7
+ # input axis across the others. Useful for evaluating a function
8
+ # on a grid.
9
+ #
10
+ # With `indexing: "xy"` (default) the first two axes are swapped
11
+ # in the output shape (matrix-style convention): `meshgrid(x, y)`
12
+ # gives outputs of shape `[y.elements, x.elements]`. With
13
+ # `indexing: "ij"` input order is preserved:
14
+ # `meshgrid(x, y, indexing: "ij")` gives outputs of shape
15
+ # `[x.elements, y.elements]`. For more than two axes only the
16
+ # first two are swapped under `"xy"`; the remaining axes follow
17
+ # input order in both modes.
18
+ #
19
+ # When `copy` is true (default) each output is a materialised
20
+ # CArray; when false, view chains (CARepeat / CAUnboundRepeat)
21
+ # are returned. When `sparse` is true the outputs are
22
+ # CAUnboundRepeat views that broadcast on demand, saving memory
23
+ # for large grids.
24
+ #
25
+ # If a block is given, yields the resulting arrays as splat
26
+ # arguments and returns the block's value.
27
+ #
28
+ # Each axis goes through {CArray.wrap_readonly}, so a coordinate
29
+ # vector may be given as anything that entry point accepts (a
30
+ # CArray, an Array, a Range, a Numeric, a MemoryView producer, an
31
+ # object answering `ca` / `to_ca`); its own data type is kept.
32
+ #
33
+ # @param axes [Array<CArray, Array, Object>] 1-D coordinate vectors.
34
+ # @param indexing [String] `"xy"` or `"ij"`.
35
+ # @param copy [Boolean] materialise each output when true.
36
+ # @param sparse [Boolean] return broadcast-on-demand views when true.
37
+ # @yieldparam grids [Array<CArray>] the resulting coordinate arrays.
38
+ # @return [Array<CArray>] the coordinate arrays, or the block's
39
+ # return value.
40
+ # @raise [ArgumentError] when `indexing` is neither `"xy"` nor `"ij"`,
41
+ # or when a coordinate vector is not 1-D.
42
+ # @example
43
+ # x = CA_FLOAT64([1.0, 2.0, 3.0])
44
+ # y = CA_FLOAT64([10.0, 20.0])
45
+ # xx, yy = CArray.meshgrid(x, y)
46
+ # xx.shape # => [2, 3]
47
+ # yy.to_a # => [[10.0, 10.0, 10.0], [20.0, 20.0, 20.0]]
48
+ def self.meshgrid (*axes, indexing: "xy", copy: true, sparse: false, &block)
49
+ unless %w[xy ij].include?(indexing)
50
+ raise ArgumentError, %{indexing option should be one of "xy" and "ij"}
51
+ end
52
+
53
+ # Each axis is negotiable, so no target type is imposed here; a
54
+ # CArray comes back as itself and anything else is brought in with
55
+ # its own data type.
56
+ axes = axes.map.with_index do |axis, k|
57
+ a = CArray.wrap_readonly(axis)
58
+ unless a.ndim == 1
59
+ raise ArgumentError,
60
+ "coordinate vector #{k} should be 1-D (got #{a.ndim}-D)"
61
+ end
62
+ a
63
+ end
64
+
65
+ ndim = axes.size
66
+
67
+ # dest[k] = output axis position that input axis k populates.
68
+ # "xy" swaps the first two; everything else is in input order.
69
+ dest = (0...ndim).to_a
70
+ dest[0], dest[1] = 1, 0 if indexing == "xy" && ndim >= 2
71
+
72
+ # Output shape: each output axis i takes its size from the input
73
+ # axis that maps there.
74
+ out_shape = Array.new(ndim)
75
+ axes.each_with_index { |a, k| out_shape[dest[k]] = a.size }
76
+
77
+ list = axes.map.with_index do |axis, k|
78
+ d = dest[k]
79
+ idx = if sparse
80
+ Array.new(ndim) { |i| i == d ? nil : :* }
81
+ else
82
+ out_shape.dup.tap { |s| s[d] = :% }
83
+ end
84
+ view = axis[*idx]
85
+ copy ? view.copy : view
86
+ end
87
+
88
+ block ? block.call(*list) : list
89
+ end
90
+
91
+ end
@@ -0,0 +1,126 @@
1
+ class CArray
2
+
3
+ # @overload is_mode(axis: nil)
4
+ # Returns a shape-preserving boolean CArray, true at every cell
5
+ # that holds a modal value — a value whose occurrence count equals
6
+ # the maximum count. This is the first-class primitive of the mode
7
+ # family: rather than returning the mode *value* (whose count is
8
+ # data-dependent when there are ties), it marks every occurrence of
9
+ # every most-frequent value, so ties are never silently broken.
10
+ #
11
+ # ```
12
+ # CA_INT32([1, 1, 2, 2, 3]).is_mode # => [1, 1, 1, 1, 0] (1 and 2 tie)
13
+ # ```
14
+ #
15
+ # With `axis: nil` the frequency is over the whole array; with
16
+ # `axis: k` it is per fiber along axis `k`, independently. The
17
+ # result is always the input shape, so — unlike returning the mode
18
+ # value — the per-axis form has no ragged-length problem. Select the
19
+ # modal cells with `a[a.is_mode]` / `a[a.is_mode(axis: k)]` (the
20
+ # {#mask_duplicates} idiom); reduce further with `.min` / `.unique`.
21
+ #
22
+ # Mode is only meaningful for discrete or binned data: raw float is
23
+ # almost all unique, so `is_mode` would mark just the single lowest
24
+ # cell (count 1). Bin first (`bin` / `histogram` / `categorize`).
25
+ #
26
+ # Masked cells do not participate and are marked false. An empty or
27
+ # all-masked fiber marks every cell false (mode has no identity; it
28
+ # never raises). Numeric distinctness follows the discovery family
29
+ # (all NaN collapse to one value, -0.0 == +0.0); `CA_OBJECT` /
30
+ # `CA_FIXLEN` follow Ruby `eql?` / `hash`.
31
+ #
32
+ # @param axis [Integer, nil] axis to take the mode along; `nil` uses
33
+ # the whole array.
34
+ # @return [CArray] boolean CArray of `self.shape`.
35
+ def is_mode (axis: nil)
36
+ # Per-fiber two-pass frequency table (C __is_mode__), one lane per dtype
37
+ # family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen
38
+ # byte-hash + memcmp). Ties are all marked; masked cells stay false.
39
+ if axis.nil?
40
+ flatten.send(:__is_mode__, 0).reshape(*shape)
41
+ else
42
+ __is_mode__(normalize_axis(axis, "is_mode"))
43
+ end
44
+ end
45
+
46
+ # @overload mode(axis: nil)
47
+ # Returns the distinct modal values — the most frequent value(s),
48
+ # ascending. All values that tie for the highest count are returned
49
+ # (matching pandas `Series.mode`), because {#is_mode} does not break
50
+ # ties; `mode` is the value-form consumer of that primitive, read
51
+ # straight from the frequency table.
52
+ #
53
+ # With `axis: nil` the result is a 1-D CArray of the distinct modal
54
+ # values over the whole array (empty when all-masked).
55
+ #
56
+ # With `axis: k` the per-fiber mode counts are ragged, so — like
57
+ # per-axis `quantile` — the result is an `Array` of reduced CArrays.
58
+ # Element `j` holds each fiber's `j`-th smallest modal value, masked
59
+ # where a fiber has fewer than `j + 1` modes; the Array length is the
60
+ # widest fiber's mode count. So `mode(axis: k)[0]` is the smallest
61
+ # mode of each fiber (a plain reduced CArray). To get the rectangular
62
+ # mask-padded form, stack them: `CArray.stack(a.mode(axis: k), axis: k)`.
63
+ # An all-masked array yields an empty Array.
64
+ #
65
+ # Only meaningful for discrete / binned data: raw float is almost all
66
+ # unique, so every value is modal and the per-axis Array grows to the
67
+ # fiber length. Bin first. Same NaN / mask semantics as {#is_mode}.
68
+ #
69
+ # @param axis [Integer, nil] axis to take the mode along; `nil` uses
70
+ # the whole array.
71
+ # @return [CArray, Array<CArray>] 1-D CArray (flat) or an Array of
72
+ # reduced CArrays, one per mode rank (per-axis).
73
+ def mode (axis: nil)
74
+ return __mode_flat if axis.nil?
75
+ k = normalize_axis(axis, "mode")
76
+
77
+ # Numeric: the C frequency-table kernel emits the ragged Array<CArray>
78
+ # directly (reduced CArrays, self.shape with axis k dropped). A 1-D input
79
+ # reduces to length-1 CArrays, unwrapped to scalars like flat quantile.
80
+ unless data_type == CA_OBJECT || data_type == CA_FIXLEN
81
+ cols = __mode_axis__(k)
82
+ return ndim == 1 ? cols.map { |col| col[0] } : cols
83
+ end
84
+
85
+ # Object / fixlen (rare): per-fiber Ruby path, reusing the flat mode as the
86
+ # single source of what counts as a mode. Move axis k to the innermost
87
+ # position and fold the rest to one outer axis, so each row is a fiber.
88
+ perm = (0...ndim).to_a
89
+ perm.delete(k)
90
+ perm << k
91
+ a2 = (ndim == 1) ? self : transpose(*perm).copy # (outer..., L)
92
+ outer = a2.shape[0...-1]
93
+ m = outer.empty? ? 1 : outer.inject(:*)
94
+ flat2 = a2.reshape(m, a2.shape[-1])
95
+ lists = Array.new(m) { |r| flat2[r, nil].__send__(:__mode_flat).to_a }
96
+
97
+ # K = widest fiber's mode count. Emit K reduced CArrays (like quantile's
98
+ # per-axis Array<CArray>): slot j holds each fiber's j-th smallest mode,
99
+ # masked where a fiber has fewer than j+1 modes. Stack them to get the
100
+ # rectangular mask-padded form: CArray.stack(result, axis: k).
101
+ kk = lists.map(&:size).max || 0
102
+ (0...kk).map do |j|
103
+ # Take the column shape from self rather than building it from data_type:
104
+ # it carries the element width a fixlen array needs, and it keeps a Face
105
+ # (a time array), whose cells then accept the surface values in `lists`.
106
+ col = flat2[nil, 0].copy
107
+ col[] = UNDEF
108
+ m.times { |r| col[r] = lists[r][j] if j < lists[r].size }
109
+ outer.empty? ? col[0] : col.reshape(*outer)
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ # Flat mode: the distinct modal values ascending, 1-D CArray of self's dtype.
116
+ # The single source of what counts as a mode (per-axis reuses it per fiber).
117
+ # The distinct values with the maximum count, read from the frequency table
118
+ # (value_counts, which covers numeric / object / fixlen), then sorted
119
+ # ascending. value_counts already skips masked cells.
120
+ def __mode_flat
121
+ values, counts = value_counts
122
+ return values if values.elements == 0
123
+ values[counts.eq(counts.max)].sort
124
+ end
125
+
126
+ end