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,289 @@
1
+ # CAMeld reduce fast paths (per-parent decompose family).
2
+ #
3
+ # All decomposable reductions along the meld axis land at eager-entity
4
+ # parity via the identity
5
+ # op(concat_k p_k) = combine_k op(p_k)
6
+ # where op is sum/min/max/mean/etc. and combine_k is +, min, max, or the
7
+ # Welford update (variance/stddev). Each per-parent op runs on an entity,
8
+ # hitting the fastest kernel_iterator path, and dodges the whole-view
9
+ # SRC_ATTACH materialise (K per-parent xfer_all GET into scratch).
10
+ #
11
+ # Reductions along a non-meld axis (memo §7.4) also decompose: each
12
+ # parent independently reduces the non-meld axis, and the K results are
13
+ # concatenated back along the meld axis via `CArray.meld`. Result is
14
+ # materialised (`.copy`) to preserve the entity-returning semantic of
15
+ # `CArray#sum`/`#mean`/etc.
16
+ #
17
+ # Fast path activates when:
18
+ # - axis kwarg is a single Integer (or absent for flat reduce)
19
+ # - no mask on CAMeld or any parent (skipna needs per-cell dispatch;
20
+ # the decompose would not see correct mask propagation across parents)
21
+ # - no non-:axis kwargs (min_count / fill_value / keep_axis / etc. punt
22
+ # to super; those change finalisation semantics)
23
+ # When any condition fails the method falls through to super, landing on
24
+ # the kernel_iterator SRC_ATTACH path (correct, just slower).
25
+ #
26
+ # Order statistics (median / percentile / quantile) do NOT decompose —
27
+ # per-parent medians are not a function of the overall median — so those
28
+ # stay on the SRC_ATTACH path, which is already essentially parity because
29
+ # the sort cost dominates.
30
+ #
31
+ # See docs/objects/CAMeld.md and devel/bench_cameld_*.rb for numbers.
32
+
33
+ class CAMeld
34
+
35
+ # ---------- monoid reductions (sum / mean / min / max) ----------
36
+
37
+ def sum(*args, **kw)
38
+ return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
39
+ axis = kw[:axis]
40
+ if axis.nil?
41
+ parents.map(&:sum).inject(:+)
42
+ elsif meld_axis_normalized?(axis)
43
+ parents.map { |p| p.sum(axis: axis) }.inject(:+)
44
+ else
45
+ non_meld_axis_decompose(:sum, axis)
46
+ end
47
+ end
48
+
49
+ # Same result as `CArray#mean`, obtained by reducing each parent and
50
+ # combining, so the melded array is never materialised. Falls back to
51
+ # the generic path when the decomposition does not apply.
52
+ # @return [CArray, Numeric]
53
+ def mean(*args, **kw)
54
+ return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
55
+ axis = kw[:axis]
56
+ # `mean` has no identity: an empty set of contributors has no defined
57
+ # mean (0/0 = NaN). Punt to super, which returns UNDEF: a reduction
58
+ # with no contributors yields the identity where one exists and UNDEF
59
+ # where none does, and never raises.
60
+ if axis.nil?
61
+ return super if parents.all? { |p| p.elements == 0 }
62
+ total_sum = parents.map(&:sum).inject(:+)
63
+ total_count = parents.map(&:elements).inject(:+)
64
+ total_sum / total_count.to_f
65
+ elsif meld_axis_normalized?(axis)
66
+ return super if parents.all? { |p| p.dim[meld_axis] == 0 }
67
+ total_sum = parents.map { |p| p.sum(axis: axis) }.inject(:+)
68
+ total_count = parents.map { |p| p.dim[meld_axis] }.inject(:+)
69
+ total_sum / total_count.to_f
70
+ else
71
+ non_meld_axis_decompose(:mean, axis)
72
+ end
73
+ end
74
+
75
+ # Same result as `CArray#min`, obtained by reducing each parent and
76
+ # combining, so the melded array is never materialised. Falls back to
77
+ # the generic path when the decomposition does not apply.
78
+ # @return [CArray, Numeric]
79
+ def min(*args, **kw)
80
+ return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
81
+ axis = kw[:axis]
82
+ # `min` has no identity — empty parent list / all-empty parents punt to
83
+ # super for UNDEF.
84
+ if axis.nil?
85
+ return super if parents.all? { |p| p.elements == 0 }
86
+ parents.reject { |p| p.elements == 0 }.map(&:min).min
87
+ elsif meld_axis_normalized?(axis)
88
+ return super if parents.all? { |p| p.dim[meld_axis] == 0 }
89
+ nonempty = parents.reject { |p| p.dim[meld_axis] == 0 }
90
+ CArray.stack(nonempty.map { |p| p.min(axis: axis) }).min(axis: 0)
91
+ else
92
+ non_meld_axis_decompose(:min, axis)
93
+ end
94
+ end
95
+
96
+ # Same result as `CArray#max`, obtained by reducing each parent and
97
+ # combining, so the melded array is never materialised. Falls back to
98
+ # the generic path when the decomposition does not apply.
99
+ # @return [CArray, Numeric]
100
+ def max(*args, **kw)
101
+ return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
102
+ axis = kw[:axis]
103
+ if axis.nil?
104
+ return super if parents.all? { |p| p.elements == 0 }
105
+ parents.reject { |p| p.elements == 0 }.map(&:max).max
106
+ elsif meld_axis_normalized?(axis)
107
+ return super if parents.all? { |p| p.dim[meld_axis] == 0 }
108
+ nonempty = parents.reject { |p| p.dim[meld_axis] == 0 }
109
+ CArray.stack(nonempty.map { |p| p.max(axis: axis) }).max(axis: 0)
110
+ else
111
+ non_meld_axis_decompose(:max, axis)
112
+ end
113
+ end
114
+
115
+ # ---------- variance family (Welford combine along meld axis) ----------
116
+ #
117
+ # Chan / Welford parallel merge: each parent contributes (n_k, mean_k,
118
+ # M2_k = Σ (x - mean_k)^2). Combine two chunks (n1, m1, M1) + (n2, m2, M2):
119
+ # n = n1 + n2
120
+ # δ = m2 - m1
121
+ # mean = m1 + δ * n2 / n
122
+ # M2 = M1 + M2 + δ² * (n1 * n2 / n)
123
+ # sample variance = M2 / (n - 1)
124
+ # population variance = M2 / n
125
+ #
126
+ # M2 per parent is recovered from CArray's variance:
127
+ # M2_k = variance_k * (n_k - 1) [sample-variance CArray path]
128
+ # This inherits CArray's ε-close SIMD reduce contract (memo: reduction is
129
+ # ε-close, not bit-exact). Falls through to super when any parent has
130
+ # n <= 1 along the axis (sample variance undefined).
131
+
132
+ def variance(*args, **kw)
133
+ variance_family(args, kw, sample: true, sqrt: false) { super }
134
+ end
135
+
136
+ # Same result as `CArray#variancep`, obtained by reducing each parent and
137
+ # combining, so the melded array is never materialised. Falls back to
138
+ # the generic path when the decomposition does not apply.
139
+ # @return [CArray, Numeric]
140
+ def variancep(*args, **kw)
141
+ variance_family(args, kw, sample: false, sqrt: false) { super }
142
+ end
143
+
144
+ # Same result as `CArray#stddev`, obtained by reducing each parent and
145
+ # combining, so the melded array is never materialised. Falls back to
146
+ # the generic path when the decomposition does not apply.
147
+ # @return [CArray, Numeric]
148
+ def stddev(*args, **kw)
149
+ variance_family(args, kw, sample: true, sqrt: true) { super }
150
+ end
151
+
152
+ # Same result as `CArray#stddevp`, obtained by reducing each parent and
153
+ # combining, so the melded array is never materialised. Falls back to
154
+ # the generic path when the decomposition does not apply.
155
+ # @return [CArray, Numeric]
156
+ def stddevp(*args, **kw)
157
+ variance_family(args, kw, sample: false, sqrt: true) { super }
158
+ end
159
+
160
+ private
161
+
162
+ def meld_axis_normalized?(axis)
163
+ (axis < 0 ? axis + ndim : axis) == meld_axis
164
+ end
165
+
166
+ # Fast path fires when: no mask anywhere, no non-:axis kwargs, and axis
167
+ # (if given) is a valid single Integer within [-ndim, ndim). Anything
168
+ # more elaborate (min_count / fill_value / keep_axis / multi-axis /
169
+ # mask propagation) punts to super.
170
+ def meld_reduce_fast_path_ok?(kw)
171
+ return false unless (kw.keys - [:axis]).empty?
172
+ axis = kw[:axis]
173
+ unless axis.nil?
174
+ return false unless axis.is_a?(Integer)
175
+ norm = axis < 0 ? axis + ndim : axis
176
+ return false unless norm >= 0 && norm < ndim
177
+ end
178
+ return false if has_mask?
179
+ parents.each { |p| return false if p.has_mask? }
180
+ true
181
+ end
182
+
183
+ # Non-meld-axis decompose: each parent reduces the same axis independently,
184
+ # then the K per-parent results are concatenated back along the meld axis.
185
+ # Materialised to entity (`.copy`) to match CArray#reduce entity semantics.
186
+ #
187
+ # Axis index in the reduced result: if the reduced axis is < meld_axis in
188
+ # the CAMeld frame, meld_axis shifts down by 1 in the per-parent result
189
+ # (post-reduce ndim = ndim - 1). If reduced axis > meld_axis, meld_axis
190
+ # is unchanged. (Reduced axis == meld_axis is handled by the callers'
191
+ # meld_axis branch and never reaches here.)
192
+ def non_meld_axis_decompose(op, axis)
193
+ parts = parents.map { |p| p.send(op, axis: axis) }
194
+ axis_norm = axis < 0 ? axis + ndim : axis
195
+ new_meld_axis = axis_norm < meld_axis ? meld_axis - 1 : meld_axis
196
+ CArray.meld(parts, axis: new_meld_axis).copy
197
+ end
198
+
199
+ def variance_family(args, kw, sample:, sqrt:)
200
+ return yield unless args.empty? && meld_reduce_fast_path_ok?(kw)
201
+ axis = kw[:axis]
202
+ # Empty-parent / short-parent handling — Welford needs n >= 2 per parent
203
+ # to recover m2 from p.variance for sample, n >= 1 for variancep (n=1
204
+ # gives m2=0, fine). Any parent below its threshold punts to super,
205
+ # which handles UNDEF / 0-count per CLAUDE.md contract.
206
+ min_n = sample ? 2 : 1
207
+ if axis.nil?
208
+ return yield if parents.any? { |p| p.elements < min_n }
209
+ finalize_scalar(welford_flat, sample, sqrt)
210
+ elsif meld_axis_normalized?(axis)
211
+ return yield if parents.any? { |p| p.dim[meld_axis] < min_n }
212
+ finalize_vector(welford_axis(axis), sample, sqrt)
213
+ else
214
+ # Non-meld-axis: each parent's variance/stddev is independent along
215
+ # its own local axis; concatenate the K results along the meld axis.
216
+ op_super = sqrt ? (sample ? :stddev : :stddevp) : (sample ? :variance : :variancep)
217
+ non_meld_axis_decompose(op_super, axis)
218
+ end
219
+ end
220
+
221
+ # Flat Welford — reduces every parent fully; returns scalar (n_tot, mean, M2)
222
+ # trio in a shape suitable for finalize_scalar. Returns [n_tot, M2].
223
+ # Preconditions (checked in variance_family): every parent has enough
224
+ # elements (>= 2 for sample, >= 1 for population).
225
+ def welford_flat
226
+ n_tot = 0
227
+ mean = nil
228
+ m2 = nil
229
+ parents.each do |p|
230
+ np = p.elements
231
+ mp = p.mean
232
+ # variance is undefined at n=1 (returns UNDEF / raises). For
233
+ # variancep (min_n=1) with n=1, m2_p = 0 by definition; skip the
234
+ # p.variance call which would blow up.
235
+ m2p = np < 2 ? 0.0 : p.variance * (np - 1)
236
+ if mean.nil?
237
+ n_tot = np; mean = mp; m2 = m2p
238
+ else
239
+ delta = mp - mean
240
+ nn = n_tot + np
241
+ mean = mean + delta * np / nn.to_f
242
+ m2 = m2 + m2p + delta * delta * (n_tot * np / nn.to_f)
243
+ n_tot = nn
244
+ end
245
+ end
246
+ [n_tot, m2]
247
+ end
248
+
249
+ # Per-axis Welford — reduces every parent along the given (meld) axis and
250
+ # combines the vector-shaped (n_tot, mean, M2) trio. Returns [n_tot, M2]
251
+ # (n_tot is scalar since the meld axis is what we're reducing; M2 is a
252
+ # CArray of the collapsed shape).
253
+ def welford_axis(axis)
254
+ n_tot = 0
255
+ mean = nil
256
+ m2 = nil
257
+ parents.each do |p|
258
+ np = p.dim[meld_axis]
259
+ mp = p.mean(axis: axis)
260
+ # variance is undefined at n=1; for variancep with n=1, m2_p = 0 by
261
+ # definition (variance_family already gated n >= min_n).
262
+ m2p = np < 2 ? mp * 0 : p.variance(axis: axis) * (np - 1)
263
+ if mean.nil?
264
+ n_tot = np; mean = mp; m2 = m2p
265
+ else
266
+ delta = mp - mean
267
+ nn = n_tot + np
268
+ mean = mean + delta * (np.to_f / nn)
269
+ m2 = m2 + m2p + delta * delta * (n_tot * np.to_f / nn)
270
+ n_tot = nn
271
+ end
272
+ end
273
+ [n_tot, m2]
274
+ end
275
+
276
+ def finalize_scalar(pair, sample, sqrt)
277
+ n_tot, m2 = pair
278
+ div = sample ? (n_tot - 1) : n_tot
279
+ v = m2 / div.to_f
280
+ sqrt ? Math.sqrt(v) : v
281
+ end
282
+
283
+ def finalize_vector(pair, sample, sqrt)
284
+ n_tot, m2 = pair
285
+ div = sample ? (n_tot - 1) : n_tot
286
+ v = m2 / div.to_f
287
+ sqrt ? v.sqrt : v
288
+ end
289
+ end
@@ -0,0 +1,116 @@
1
+ class CArray
2
+
3
+ # @overload align_addr(*arrays, join: :outer)
4
+ # Aligns several arrays onto one common set of coordinate values and
5
+ # returns, for each array, the flat addresses that gather it onto that
6
+ # common set. Symmetric N-ary counterpart of the instance method
7
+ # {#locate_addr} (which is the asymmetric self-against-ref lookup).
8
+ #
9
+ # Returns `[common, idx_0, idx_1, ...]`:
10
+ # - `common` is a 1-D CArray of the common coordinate values, chosen by
11
+ # `join:` (see below), in first-appearance order.
12
+ # - `idx_k` is a `common`-shaped `:int64` CArray of flat addresses into
13
+ # `arrays[k]`: `idx_k[j]` is where `common[j]` lives in `arrays[k]`, or
14
+ # `UNDEF` when that array lacks the value. Each `idx_k` is exactly
15
+ # `common.locate_addr(arrays[k])`.
16
+ #
17
+ # Reindex each array onto the common grid with `project`, then compare
18
+ # element-wise (missing coordinates come through masked):
19
+ #
20
+ # common, idx_a, idx_b = CArray.align_addr(a_coord, b_coord, join: :outer)
21
+ # a_on_grid = a_data.project(idx_a) # common-shaped, UNDEF where a lacks it
22
+ # b_on_grid = b_data.project(idx_b)
23
+ #
24
+ # Because the addresses are returned (not the reindexed values), one
25
+ # alignment serves any number of `arrays[k]`-shaped variables — compute the
26
+ # idx once, project many.
27
+ #
28
+ # `join:` selects the common coordinate set:
29
+ # - `:outer` — union of the distinct values of every array.
30
+ # - `:inner` — distinct values present in every array.
31
+ # - `:left` — the first array's distinct values.
32
+ # - `:right` — the last array's distinct values.
33
+ #
34
+ # Value equality follows the value-hash discovery family (numeric `==` with
35
+ # NaN collapsed and `-0.0 == +0.0`; object `hash` / `eql?`; fixlen byte
36
+ # equality). Arrays are coerced to the first array's dtype within the same
37
+ # family (cross-family raises). Masked cells do not enter `common`.
38
+ #
39
+ # @param arrays [Array<CArray>] two or more arrays (Array / Range coerced
40
+ # via `to_ca`). One array is allowed (degenerate: `common` is its distinct
41
+ # values).
42
+ # @param join [:outer, :inner, :left, :right] how to build `common`.
43
+ # @return [Array<CArray>] `[common, idx_0, idx_1, ...]`.
44
+ # @raise [ArgumentError] when no array is given or `join` is not one of the
45
+ # accepted symbols.
46
+ def self.align_addr (*arrays, join: :outer)
47
+ raise ArgumentError, "align_addr: need at least one array" if arrays.empty?
48
+ arrays = arrays.map { |a| a.is_a?(CArray) ? a : a.to_ca }
49
+ # Seed the fold with the first array's distinct values so N == 1 and the
50
+ # union/intersection folds all agree (a bare reduce over one element would
51
+ # return it with duplicates intact).
52
+ seed = arrays.first.unique
53
+ common = case join
54
+ when :outer then arrays[1..-1].reduce(seed) { |acc, a| acc.union(a) }
55
+ when :inner then arrays[1..-1].reduce(seed) { |acc, a| acc.intersection(a) }
56
+ when :left then seed
57
+ when :right then arrays.last.unique
58
+ else
59
+ raise ArgumentError,
60
+ "align_addr: join must be :outer / :inner / :left / :right " \
61
+ "(got #{join.inspect})"
62
+ end
63
+ idxs = arrays.map { |a| common.locate_addr(a) }
64
+ [common, *idxs]
65
+ end
66
+
67
+ # @overload align_nearest_addr(*arrays, grid: nil, direction: :round, tolerance: nil)
68
+ # Aligns several arrays onto one common coordinate grid by nearest match,
69
+ # the ordered-lane (continuous) sibling of {align_addr}. Returns
70
+ # `[common, idx_0, idx_1, ...]` with the same reindex contract: `idx_k` is a
71
+ # `common`-shaped `:int64` array of flat addresses into `arrays[k]` giving,
72
+ # for each grid point, the nearest value in that array — exactly
73
+ # `common.locate_nearest_addr(arrays[k], direction:, tolerance:)`.
74
+ #
75
+ # Unlike {align_addr}, the common grid is **not** built by a set union:
76
+ # continuous coordinates rarely coincide exactly, so a union would merely
77
+ # pile up near-duplicate points. Instead the grid is a reference axis —
78
+ # `grid:` when given, otherwise the first array verbatim (kept as-is, not
79
+ # deduplicated). Pass `grid: arrays.last` to align onto the last array's
80
+ # axis. (`join:` has no meaning here and is not accepted; clustering nearby
81
+ # coordinates into a synthesised grid is out of scope.)
82
+ #
83
+ # common, ia, ib = CArray.align_nearest_addr(a_coord, b_coord, grid: ref)
84
+ # a_on_grid = a_data.project(ia) # each grid point <- nearest a value
85
+ # b_on_grid = b_data.project(ib)
86
+ #
87
+ # `direction:` (`:round` / `:floor` / `:ceil`) and `tolerance:` are
88
+ # forwarded to {#locate_nearest_addr}: out-of-range grid points, and points
89
+ # whose nearest value is farther than `tolerance`, come back masked.
90
+ #
91
+ # @param arrays [Array<CArray>] one or more arrays (Array / Range coerced
92
+ # via `to_ca`).
93
+ # @param grid [CArray, Array, Range, nil] the reference coordinate grid;
94
+ # `nil` uses the first array verbatim.
95
+ # @param direction [:round, :floor, :ceil] rounding rule for the nearest
96
+ # match.
97
+ # @param tolerance [Numeric, nil] maximum accepted distance; farther grid
98
+ # points are masked. `nil` disables the check.
99
+ # @return [Array<CArray>] `[common, idx_0, idx_1, ...]`.
100
+ # @raise [ArgumentError] when no array is given (or `direction` is invalid,
101
+ # raised by {#locate_nearest_addr}).
102
+ def self.align_nearest_addr (*arrays, grid: nil, direction: :round, tolerance: nil)
103
+ raise ArgumentError, "align_nearest_addr: need at least one array" if arrays.empty?
104
+ arrays = arrays.map { |a| a.is_a?(CArray) ? a : a.to_ca }
105
+ common = if grid.nil?
106
+ arrays.first
107
+ else
108
+ grid.is_a?(CArray) ? grid : grid.to_ca
109
+ end
110
+ idxs = arrays.map { |a|
111
+ common.locate_nearest_addr(a, direction: direction, tolerance: tolerance)
112
+ }
113
+ [common, *idxs]
114
+ end
115
+
116
+ end
@@ -0,0 +1,128 @@
1
+ class CArray
2
+
3
+ # @overload bin(vmin, vmax, step = nil, bins: nil, lfill: nil, ufill: nil, include_max: true)
4
+ # Returns each element's bin index for equal-width, half-open bins
5
+ # over `[vmin, vmax]`. Bin `k` covers `[vmin + k*w, vmin + (k+1)*w)`
6
+ # where `w = (vmax - vmin) / n`. The number of bins is set either by
7
+ # `step` (positional; bin width, matching `snap`'s step) or by
8
+ # `bins:` (kwarg; count of bins, matching histogram convention);
9
+ # exactly one must be given.
10
+ #
11
+ # Because `[vmin, vmax]` is a user-declared inclusive range, values
12
+ # exactly equal to `vmax` land in the last bin by default
13
+ # (`include_max: true`); this differs from `bin_to`, where the
14
+ # user-supplied edges are treated as-is (half-open, default `false`).
15
+ #
16
+ # Out-of-range convention follows `bin_to` / `project`: `lfill` for
17
+ # below-range, `ufill` for above-range; `nil` on either side masks
18
+ # that side. NaN / masked input cells are always masked in the
19
+ # output, independently of `lfill` / `ufill`.
20
+ #
21
+ # Use `snap(step, offset:)` when the desired output is the snapped
22
+ # value; use `bin_to(edges)` when the edges are non-uniform.
23
+ #
24
+ # @param vmin [Numeric] range lower bound.
25
+ # @param vmax [Numeric] range upper bound; must be `>= vmin`.
26
+ # @param step [Numeric, nil] bin width. The number of bins is
27
+ # `((vmax - vmin) / step).round` (silent FP rounding, standard
28
+ # numeric convention).
29
+ # @param bins [Integer, nil] number of bins (alternative to `step`);
30
+ # must be `>= 1`.
31
+ # @param lfill [Integer, nil] fill for below-range cells.
32
+ # @param ufill [Integer, nil] fill for above-range cells.
33
+ # @param include_max [Boolean] fold values equal to `vmax` into the
34
+ # last bin instead of treating them as above-range.
35
+ # @return [CArray] `CA_INT64` bin indices in `[0, n-1]`, same
36
+ # shape as `self`.
37
+ # @raise [ArgumentError] when neither / both of `step` and `bins:`
38
+ # are given, `bins < 1`, or `vmin > vmax`.
39
+ # @example
40
+ # temp.bin(270, 300, 0.5) # 60 uniform bins every 0.5 K
41
+ # temp.bin(0, 1, bins: 100) # 100 equal-width bins over [0, 1]
42
+ # temp.bin(0, 9, 1, lfill: 0, ufill: 8) # clamp OOB to end bins
43
+ def bin(vmin, vmax, step = nil, bins: nil, lfill: nil, ufill: nil, include_max: true)
44
+ if step.nil? == bins.nil?
45
+ raise ArgumentError, "bin: give exactly one of `step` or `bins:`"
46
+ end
47
+ raise ArgumentError, "bin: vmin > vmax" if vmin > vmax
48
+
49
+ n = bins || ((vmax - vmin).to_f / step).round
50
+ raise ArgumentError, "bin: n must be >= 1" if n < 1
51
+
52
+ if vmin == vmax
53
+ # Degenerate: zero interval → all cells fall on the single edge;
54
+ # with include_max: true they land in bin 0.
55
+ out = CArray.int64(*shape) { 0 }
56
+ out.mask = self.mask.to_ca if self.has_mask?
57
+ if self.float?
58
+ inv = self.is_invalid
59
+ if inv.count(true) > 0
60
+ out.mask = out.has_mask? ? (out.mask | inv) : inv
61
+ end
62
+ end
63
+ return out
64
+ end
65
+
66
+ # Delegate to `bin_to` with generated uniform edges — same kernel
67
+ # (`histbin_ki`) as `histogram`, so semantics are identical.
68
+ edges = CArray.float64(n + 1).span(vmin..vmax)
69
+ bin_to(edges, lfill: lfill, ufill: ufill, include_max: include_max)
70
+ end
71
+
72
+ # @overload bin_to(edges, lfill: nil, ufill: nil, include_max: false)
73
+ # Returns each element's bin index against an explicit ascending
74
+ # `edges` array (non-uniform binning). Sibling of `bin` (uniform,
75
+ # range + step) and `snap_to` (nearest-value snap to the same shape
76
+ # of grid).
77
+ #
78
+ # `edges` are `N+1` ascending boundaries defining `N` bins; bin
79
+ # `k` covers the half-open interval `[edges[k], edges[k+1])`.
80
+ # Out-of-range values follow the `bin` / `project` convention:
81
+ # a value below `edges[0]` becomes `lfill`, a value at or above
82
+ # `edges[-1]` becomes `ufill`; `nil` on either side masks that
83
+ # side. When `include_max` is true, a value exactly equal to
84
+ # `edges[-1]` lands in the last bin `N-1` instead of being
85
+ # treated as above-range. NaN or masked input cells are masked
86
+ # in the output, independently of `lfill` / `ufill`.
87
+ #
88
+ # The inner binning kernel is shared with `histogram`, which counts
89
+ # how many values land in each bin.
90
+ #
91
+ # @param edges [CArray, Array<Numeric>] 1-D ascending boundaries
92
+ # with at least 2 values.
93
+ # @param lfill [Integer, nil] fill for below-range cells; `nil`
94
+ # masks them.
95
+ # @param ufill [Integer, nil] fill for above-range cells; `nil`
96
+ # masks them.
97
+ # @param include_max [Boolean] fold values equal to `edges[-1]`
98
+ # into the last bin instead of treating them as above-range.
99
+ # @return [CArray] `CA_INT64` array with the same shape as `self`
100
+ # holding bin indices in `[0, N-1]` (or the fill values / mask
101
+ # for out-of-range and masked cells).
102
+ # @raise [ArgumentError] when `edges` is not 1-D or has fewer
103
+ # than 2 values.
104
+ # @example
105
+ # e = CA_FLOAT64([0, 1, 10, 100])
106
+ # v = CA_FLOAT64([0.5, 5.0, 50.0, -1.0, 200.0])
107
+ # v.bin_to(e) # => [0, 1, 2, UNDEF, UNDEF]
108
+ # v.bin_to(e, lfill: 0, ufill: 2)
109
+ # # => [0, 1, 2, 0, 2]
110
+ def bin_to(edges, lfill: nil, ufill: nil, include_max: false)
111
+ e = CArray.wrap_readonly(edges, :float64)
112
+ raise ArgumentError, "bin_to: edges must be 1-D" unless e.ndim == 1
113
+ raise ArgumentError, "bin_to: edges needs at least 2 values" if e.elements < 2
114
+ n = e.elements - 1 # number of bins
115
+
116
+ src = data_type == CA_FLOAT64 ? self : CArray.wrap_readonly(self, :float64)
117
+
118
+ # histbin_ki returns the extended index (0 = under, 1..N = in-range bins,
119
+ # N+1 = over; NaN / masked -> masked). Shift to the in-range convention:
120
+ # under -> -1, in-range -> 0..N-1, over -> N.
121
+ out = src.send(:histbin_ki, e, include_max) - 1
122
+
123
+ out[:eq, -1] = lfill.nil? ? UNDEF : lfill # under
124
+ out[:eq, n] = ufill.nil? ? UNDEF : ufill # over
125
+ out
126
+ end
127
+
128
+ end
@@ -0,0 +1,87 @@
1
+ # Ruby front-end for CArray#bincount. Validates inputs, picks the
2
+ # output length, and dispatches to the dedicated C kernels in
3
+ # ext/carray_bincount.c (`__bincount_count__` / `__bincount_weighted__`).
4
+
5
+ class CArray
6
+
7
+ # @overload bincount(weights: nil, length: 0)
8
+ # Returns occurrence counts per non-negative integer label in
9
+ # `self`, or a per-label sum of `weights`.
10
+ #
11
+ # Masked labels are skipped (not counted). Masked weights are
12
+ # skipped too (their label contributes 0).
13
+ #
14
+ # @param weights [CArray, nil] when given, sums weights per
15
+ # label instead of counting; length must equal `self.elements`
16
+ # and the output `data_type` is inherited from `weights`.
17
+ # When `nil` (default), counts occurrences and returns
18
+ # `CA_UINT32` (or `CA_UINT64` if `length >= 2^32`).
19
+ # @param length [Integer] minimum output length; the actual
20
+ # length is `max(length, self.max + 1)`.
21
+ # @return [CArray] 1-D output of length `max(length, self.max + 1)`.
22
+ # @raise [CArray::DataTypeError] when `self` is not an integer
23
+ # `data_type`.
24
+ # @raise [ArgumentError] when a label is negative or `weights`
25
+ # length disagrees with `self.elements`.
26
+ # @example
27
+ # labels = CA_INT32([0, 1, 1, 2, 0, 1])
28
+ # labels.bincount # => CA_UINT32([2, 3, 1])
29
+ # labels.bincount(length: 5) # => CA_UINT32([2, 3, 1, 0, 0])
30
+ # weights = CA_DOUBLE([1, 2, 3, 4, 5, 6])
31
+ # labels.bincount(weights: weights) # => CA_DOUBLE([6, 11, 4])
32
+ def bincount(weights: nil, length: 0)
33
+ unless [CA_INT8, CA_INT16, CA_INT32, CA_INT64,
34
+ CA_UINT8, CA_UINT16, CA_UINT32, CA_UINT64].include?(data_type)
35
+ raise CArray::DataTypeError,
36
+ "bincount requires an integer label array (got #{data_type_name})"
37
+ end
38
+
39
+ if elements.zero?
40
+ if weights
41
+ out = CArray.new(weights.data_type, [length])
42
+ else
43
+ out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32
44
+ out = CArray.new(out_type, [length])
45
+ end
46
+ out.fill(0) unless length.zero?
47
+ return out
48
+ end
49
+
50
+ # Single-pass fused min+max so the prereq scan over labels costs
51
+ # one walk instead of two.
52
+ label_min, label_max = minmax
53
+ if label_min.equal?(UNDEF)
54
+ # Every cell is masked: no labels to count, same result as an empty
55
+ # input (all-zero output of the requested minimum length).
56
+ if weights
57
+ out = CArray.new(weights.data_type, [length])
58
+ else
59
+ out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32
60
+ out = CArray.new(out_type, [length])
61
+ end
62
+ out.fill(0) unless length.zero?
63
+ return out
64
+ end
65
+ if label_min < 0
66
+ raise ArgumentError,
67
+ "bincount: negative label not allowed (got #{label_min})"
68
+ end
69
+
70
+ n = [length, label_max + 1].max
71
+
72
+ if weights
73
+ unless weights.is_a?(CArray)
74
+ raise ArgumentError, "bincount: weights must be a CArray"
75
+ end
76
+ if weights.elements != elements
77
+ raise ArgumentError,
78
+ "bincount: weights length (#{weights.elements}) doesn't " \
79
+ "match labels length (#{elements})"
80
+ end
81
+ __bincount_weighted__(weights, n)
82
+ else
83
+ __bincount_count__(n)
84
+ end
85
+ end
86
+
87
+ end