carray 2.0.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -25
  3. data/CHANGELOG.md +338 -0
  4. data/{NEWS.md → CHANGELOG.v1.md} +3 -0
  5. data/LICENSE +1 -1
  6. data/README.md +120 -36
  7. data/carray.gemspec +32 -30
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1244 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +124 -0
  15. data/ext/ca_binop_dispatch.h +152 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +239 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4367 -0
  26. data/ext/ca_kernel_iterator.h +2596 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +839 -0
  33. data/ext/ca_obj_binop.c +948 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +967 -0
  39. data/ext/ca_obj_face.c +750 -0
  40. data/ext/ca_obj_face.h +279 -0
  41. data/ext/ca_obj_fake.c +239 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +308 -0
  45. data/ext/ca_obj_grid.c +866 -440
  46. data/ext/ca_obj_meld.c +1039 -0
  47. data/ext/ca_obj_moncmp.c +588 -0
  48. data/ext/ca_obj_monop.c +1123 -0
  49. data/ext/ca_obj_object.c +866 -296
  50. data/ext/ca_obj_record.c +470 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +593 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +624 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1306 -0
  58. data/ext/ca_obj_shift.c +231 -793
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2584 -0
  62. data/ext/ca_obj_string.c +270 -0
  63. data/ext/ca_obj_tile.c +622 -0
  64. data/ext/ca_obj_time.c +548 -0
  65. data/ext/ca_obj_timedelta.c +437 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +785 -0
  68. data/ext/ca_obj_window.c +1202 -565
  69. data/ext/ca_op_byte_swap.c +175 -0
  70. data/ext/ca_op_cmplx64.h +123 -0
  71. data/ext/ca_op_ipower.c +316 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +473 -0
  75. data/ext/ca_sweep_engine.h +166 -0
  76. data/ext/ca_transform_common.c +235 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +810 -420
  80. data/ext/carray_access.c +873 -731
  81. data/ext/carray_attribute.c +98 -329
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +376 -0
  84. data/ext/carray_build_flags.h +3 -0
  85. data/ext/carray_call_cfunc.c +2897 -874
  86. data/ext/carray_call_cfunc.h +313 -0
  87. data/ext/carray_cast.c +1264 -315
  88. data/ext/carray_cast_func.rb +81 -40
  89. data/ext/carray_class.c +53 -63
  90. data/ext/carray_config.h +28 -0
  91. data/ext/carray_conversion.c +350 -346
  92. data/ext/carray_copy.c +168 -270
  93. data/ext/carray_core.c +1396 -206
  94. data/ext/carray_count.c +312 -0
  95. data/ext/carray_data_type.c +43 -19
  96. data/ext/carray_element.c +585 -213
  97. data/ext/carray_factorize.c +2542 -0
  98. data/ext/carray_generate.c +230 -559
  99. data/ext/carray_histogram.c +490 -0
  100. data/ext/carray_hold.c +228 -0
  101. data/ext/carray_index_classifier.c +1021 -0
  102. data/ext/carray_index_classifier.h +27 -0
  103. data/ext/carray_internal.h +136 -0
  104. data/ext/carray_kernels_bincmp.c +4446 -0
  105. data/ext/carray_kernels_binop.c +11001 -0
  106. data/ext/carray_kernels_init.c +1131 -0
  107. data/ext/carray_kernels_map.c +3467 -0
  108. data/ext/carray_kernels_moncmp.c +2097 -0
  109. data/ext/carray_kernels_monop.c +18313 -0
  110. data/ext/carray_kernels_reduce_aggregate.c +25837 -0
  111. data/ext/carray_kernels_reduce_boolean.c +330 -0
  112. data/ext/carray_kernels_reduce_cumulative.c +14593 -0
  113. data/ext/carray_kernels_reduce_extreme.c +16948 -0
  114. data/ext/carray_kernels_reduce_variance.c +3910 -0
  115. data/ext/carray_kernels_scan.c +3693 -0
  116. data/ext/carray_kernels_search.c +32138 -0
  117. data/ext/carray_kernels_sort.c +10626 -0
  118. data/ext/carray_kernels_triop.c +1392 -0
  119. data/ext/carray_lazy.c +737 -0
  120. data/ext/carray_loop.c +88 -200
  121. data/ext/carray_mask.c +853 -158
  122. data/ext/carray_math_kernel.h +120 -0
  123. data/ext/carray_mathfunc.c +10 -241
  124. data/ext/carray_median_percentile.c +1257 -0
  125. data/ext/carray_memory_view.c +1650 -0
  126. data/ext/carray_operator.c +1525 -320
  127. data/ext/carray_order.c +664 -1394
  128. data/ext/carray_partition.c +416 -0
  129. data/ext/carray_random.c +518 -0
  130. data/ext/carray_scatter.c +357 -0
  131. data/ext/carray_slab.c +1219 -0
  132. data/ext/carray_slab.h +84 -0
  133. data/ext/carray_sort.c +829 -0
  134. data/ext/carray_sort_kernel.c +620 -0
  135. data/ext/carray_struct.c +695 -0
  136. data/ext/carray_test.c +343 -229
  137. data/ext/carray_undef.c +34 -17
  138. data/ext/carray_utils.c +175 -74
  139. data/ext/extconf.rb +234 -55
  140. data/ext/mk_call_cfunc.rb +671 -0
  141. data/ext/mkkernel.rb +9096 -0
  142. data/ext/ruby_carray.c +211 -108
  143. data/ext/version.h +4 -14
  144. data/ext/version.rb +5 -13
  145. data/lib/carray/arrow_tensor.rb +401 -0
  146. data/lib/carray/attribute.rb +166 -0
  147. data/lib/carray/autoload_carray.rb +239 -0
  148. data/lib/carray/autoload_method_extension.rb +45 -0
  149. data/lib/carray/axis_group.rb +711 -0
  150. data/lib/carray/basics.rb +481 -0
  151. data/lib/carray/bincount_nd.rb +358 -0
  152. data/lib/carray/block_iterator.rb +614 -0
  153. data/lib/carray/boolean_reduce.rb +109 -0
  154. data/lib/carray/categorical.rb +561 -0
  155. data/lib/carray/categorical_iterator.rb +1084 -0
  156. data/lib/carray/complex.rb +150 -0
  157. data/lib/carray/conditional.rb +216 -0
  158. data/lib/carray/const_string.rb +228 -0
  159. data/lib/carray/construct.rb +160 -328
  160. data/lib/carray/core_extensions.rb +297 -0
  161. data/lib/carray/data_type_extension.rb +250 -0
  162. data/lib/carray/fixlen_string.rb +95 -0
  163. data/lib/carray/frame/concat.rb +132 -0
  164. data/lib/carray/frame/convert.rb +95 -0
  165. data/lib/carray/frame/csv_parser.rb +211 -0
  166. data/lib/carray/frame/frame.rb +642 -0
  167. data/lib/carray/frame/group.rb +186 -0
  168. data/lib/carray/frame/io.rb +321 -0
  169. data/lib/carray/frame/join.rb +248 -0
  170. data/lib/carray/frame/records.rb +99 -0
  171. data/lib/carray/frame/sort.rb +113 -0
  172. data/lib/carray/frame/verbs.rb +316 -0
  173. data/lib/carray/frame.rb +16 -0
  174. data/lib/carray/fuse_source.rb +123 -0
  175. data/lib/carray/fusion.rb +218 -0
  176. data/lib/carray/histogram.rb +512 -0
  177. data/lib/carray/inspect.rb +37 -26
  178. data/lib/carray/iterator.rb +58 -349
  179. data/lib/carray/lazy.rb +941 -0
  180. data/lib/carray/mask_gap_fill.rb +200 -0
  181. data/lib/carray/math.rb +78 -342
  182. data/lib/carray/meld_reduce.rb +289 -0
  183. data/lib/carray/methods/align_addr.rb +116 -0
  184. data/lib/carray/methods/bin.rb +128 -0
  185. data/lib/carray/methods/bincount.rb +87 -0
  186. data/lib/carray/methods/bit_string.rb +92 -0
  187. data/lib/carray/methods/broadcast.rb +63 -0
  188. data/lib/carray/methods/choose.rb +39 -0
  189. data/lib/carray/methods/composition.rb +280 -0
  190. data/lib/carray/methods/gather_nd.rb +206 -0
  191. data/lib/carray/methods/index.rb +39 -0
  192. data/lib/carray/methods/insert_block.rb +99 -0
  193. data/lib/carray/methods/is_in.rb +141 -0
  194. data/lib/carray/methods/join.rb +90 -0
  195. data/lib/carray/methods/locate_addr.rb +52 -0
  196. data/lib/carray/methods/mask_duplicates.rb +41 -0
  197. data/lib/carray/methods/meshgrid.rb +90 -0
  198. data/lib/carray/methods/mode.rb +126 -0
  199. data/lib/carray/methods/nunique.rb +46 -0
  200. data/lib/carray/methods/resize.rb +56 -0
  201. data/lib/carray/methods/snap.rb +161 -0
  202. data/lib/carray/methods/string_format.rb +57 -0
  203. data/lib/carray/methods/unique.rb +47 -0
  204. data/lib/carray/methods/value_counts.rb +71 -0
  205. data/lib/carray/mkmf.rb +124 -101
  206. data/lib/carray/runtime.rb +89 -0
  207. data/lib/carray/serialize.rb +478 -167
  208. data/lib/carray/slab_iterator.rb +305 -0
  209. data/lib/carray/stack.rb +291 -0
  210. data/lib/carray/string.rb +56 -180
  211. data/lib/carray/string_operation_extension.rb +289 -0
  212. data/lib/carray/struct.rb +335 -323
  213. data/lib/carray/struct_builder.rb +697 -0
  214. data/lib/carray/table.rb +41 -2
  215. data/lib/carray/time.rb +2654 -38
  216. data/lib/carray/window_iterator.rb +927 -0
  217. data/lib/carray.rb +55 -57
  218. data/yard-stubs/ca_obj_array.rb +385 -0
  219. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  220. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  221. data/yard-stubs/ca_obj_block.rb +73 -0
  222. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  223. data/yard-stubs/ca_obj_fake.rb +31 -0
  224. data/yard-stubs/ca_obj_farray.rb +32 -0
  225. data/yard-stubs/ca_obj_field.rb +45 -0
  226. data/yard-stubs/ca_obj_grid.rb +35 -0
  227. data/yard-stubs/ca_obj_refer.rb +72 -0
  228. data/yard-stubs/ca_obj_roll.rb +45 -0
  229. data/yard-stubs/ca_obj_shift.rb +43 -0
  230. data/yard-stubs/ca_obj_stride.rb +181 -0
  231. data/yard-stubs/ca_obj_tile.rb +29 -0
  232. data/yard-stubs/ca_obj_transpose.rb +40 -0
  233. data/yard-stubs/ca_obj_window.rb +49 -0
  234. data/yard-stubs/carray_access.rb +131 -0
  235. data/yard-stubs/carray_attribute.rb +246 -0
  236. data/yard-stubs/carray_broadcast.rb +37 -0
  237. data/yard-stubs/carray_cast.rb +489 -0
  238. data/yard-stubs/carray_class.rb +65 -0
  239. data/yard-stubs/carray_conversion.rb +76 -0
  240. data/yard-stubs/carray_copy.rb +79 -0
  241. data/yard-stubs/carray_core.rb +114 -0
  242. data/yard-stubs/carray_count.rb +79 -0
  243. data/yard-stubs/carray_element.rb +108 -0
  244. data/yard-stubs/carray_generate.rb +66 -0
  245. data/yard-stubs/carray_lazy.rb +23 -0
  246. data/yard-stubs/carray_loop.rb +140 -0
  247. data/yard-stubs/carray_mask.rb +259 -0
  248. data/yard-stubs/carray_math.rb +132 -0
  249. data/yard-stubs/carray_mathfunc.rb +45 -0
  250. data/yard-stubs/carray_median_percentile.rb +89 -0
  251. data/yard-stubs/carray_memory_view.rb +163 -0
  252. data/yard-stubs/carray_order.rb +312 -0
  253. data/yard-stubs/carray_random.rb +89 -0
  254. data/yard-stubs/carray_scatter.rb +106 -0
  255. data/yard-stubs/carray_slab.rb +57 -0
  256. data/yard-stubs/carray_sort.rb +163 -0
  257. data/yard-stubs/carray_test.rb +85 -0
  258. data/yard-stubs/carray_undef.rb +64 -0
  259. data/yard-stubs/carray_utils.rb +97 -0
  260. data/yard-stubs/ruby_carray.rb +193 -0
  261. metadata +220 -138
  262. data/Rakefile +0 -51
  263. data/TODO.md +0 -18
  264. data/ext/ca_iter_block.c +0 -257
  265. data/ext/ca_iter_dimension.c +0 -299
  266. data/ext/ca_iter_window.c +0 -214
  267. data/ext/ca_obj_mapping.c +0 -644
  268. data/ext/ca_obj_unbound_repeat.c +0 -529
  269. data/ext/carray_iterator.c +0 -641
  270. data/ext/carray_math.rb +0 -850
  271. data/ext/carray_numeric.c +0 -259
  272. data/ext/carray_sort_addr.c +0 -254
  273. data/ext/carray_stat.c +0 -2100
  274. data/ext/carray_stat_proc.rb +0 -1999
  275. data/ext/mkmath.rb +0 -741
  276. data/ext/ruby_ccomplex.c +0 -509
  277. data/ext/ruby_float_func.c +0 -86
  278. data/lib/carray/array.rb +0 -8
  279. data/lib/carray/autoload/autoload_base.rb +0 -19
  280. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  281. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  282. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  283. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  284. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  285. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  286. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  287. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  288. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  289. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  290. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  291. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  292. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  293. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  294. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  295. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  296. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  297. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  298. data/lib/carray/autoload.rb +0 -141
  299. data/lib/carray/basic.rb +0 -191
  300. data/lib/carray/broadcast.rb +0 -101
  301. data/lib/carray/compose.rb +0 -315
  302. data/lib/carray/convert.rb +0 -115
  303. data/lib/carray/info.rb +0 -110
  304. data/lib/carray/io/imagemagick.rb +0 -235
  305. data/lib/carray/mask.rb +0 -102
  306. data/lib/carray/math/histogram.rb +0 -177
  307. data/lib/carray/math/recurrence.rb +0 -93
  308. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  309. data/lib/carray/object/ca_obj_link.rb +0 -50
  310. data/lib/carray/object/ca_obj_pack.rb +0 -99
  311. data/lib/carray/obsolete.rb +0 -256
  312. data/lib/carray/ordering.rb +0 -181
  313. data/lib/carray/testing.rb +0 -51
  314. data/lib/carray/transform.rb +0 -109
  315. data/mailmap +0 -1
  316. data/misc/Methods.ja.md +0 -182
  317. data/misc/NOTE +0 -51
  318. data/spec/Classes/CABitfield_spec.rb +0 -58
  319. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  320. data/spec/Classes/CABlock_spec.rb +0 -205
  321. data/spec/Classes/CAField_spec.rb +0 -39
  322. data/spec/Classes/CAGrid_spec.rb +0 -75
  323. data/spec/Classes/CAMap_spec.rb +0 -0
  324. data/spec/Classes/CAMapping_spec.rb +0 -105
  325. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  326. data/spec/Classes/CAObject_spec.rb +0 -33
  327. data/spec/Classes/CARefer_spec.rb +0 -93
  328. data/spec/Classes/CARepeat_spec.rb +0 -65
  329. data/spec/Classes/CASelect_spec.rb +0 -22
  330. data/spec/Classes/CAShift_spec.rb +0 -16
  331. data/spec/Classes/CAStruct_spec.rb +0 -71
  332. data/spec/Classes/CATranspose_spec.rb +0 -60
  333. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  334. data/spec/Classes/CAWindow_spec.rb +0 -54
  335. data/spec/Classes/CAWrap_spec.rb +0 -8
  336. data/spec/Classes/CArray_spec.rb +0 -184
  337. data/spec/Classes/CScalar_spec.rb +0 -55
  338. data/spec/Features/feature_130_spec.rb +0 -19
  339. data/spec/Features/feature_attributes_spec.rb +0 -280
  340. data/spec/Features/feature_boolean_spec.rb +0 -98
  341. data/spec/Features/feature_broadcast.rb +0 -116
  342. data/spec/Features/feature_cast_function.rb +0 -19
  343. data/spec/Features/feature_cast_spec.rb +0 -33
  344. data/spec/Features/feature_class_spec.rb +0 -84
  345. data/spec/Features/feature_complex_spec.rb +0 -42
  346. data/spec/Features/feature_composite_spec.rb +0 -124
  347. data/spec/Features/feature_convert_spec.rb +0 -46
  348. data/spec/Features/feature_copy_spec.rb +0 -123
  349. data/spec/Features/feature_creation_spec.rb +0 -84
  350. data/spec/Features/feature_element_spec.rb +0 -144
  351. data/spec/Features/feature_extream_spec.rb +0 -54
  352. data/spec/Features/feature_generate_spec.rb +0 -74
  353. data/spec/Features/feature_index_spec.rb +0 -69
  354. data/spec/Features/feature_mask_spec.rb +0 -580
  355. data/spec/Features/feature_math_spec.rb +0 -97
  356. data/spec/Features/feature_order_spec.rb +0 -146
  357. data/spec/Features/feature_ref_store_spec.rb +0 -209
  358. data/spec/Features/feature_serialization_spec.rb +0 -125
  359. data/spec/Features/feature_stat_spec.rb +0 -397
  360. data/spec/Features/feature_virtual_spec.rb +0 -48
  361. data/spec/Features/method_eq_spec.rb +0 -81
  362. data/spec/Features/method_is_nan_spec.rb +0 -12
  363. data/spec/Features/method_map_spec.rb +0 -54
  364. data/spec/Features/method_max_with.rb +0 -20
  365. data/spec/Features/method_min_with.rb +0 -19
  366. data/spec/Features/method_ne_spec.rb +0 -18
  367. data/spec/Features/method_project_spec.rb +0 -188
  368. data/spec/Features/method_ref_spec.rb +0 -27
  369. data/spec/Features/method_round_spec.rb +0 -11
  370. data/spec/Features/method_s_linspace_spec.rb +0 -48
  371. data/spec/Features/method_s_span_spec.rb +0 -14
  372. data/spec/Features/method_seq_spec.rb +0 -47
  373. data/spec/Features/method_sort_with.rb +0 -43
  374. data/spec/Features/method_sorted_with.rb +0 -29
  375. data/spec/Features/method_span_spec.rb +0 -42
  376. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  377. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  378. data/spec/spec_all.rb +0 -10
  379. data/utils/ca_ase.rb +0 -21
  380. data/utils/ca_methods.rb +0 -15
  381. data/utils/cast_checker.rb +0 -30
  382. data/utils/convert_test.rb +0 -73
  383. data/utils/extract_yard.rb +0 -22
  384. data/utils/guess_shape.rb +0 -76
  385. data/utils/monkey_patch_methods.rb +0 -62
  386. data/utils/remove_resource_fork.sh +0 -5
@@ -0,0 +1,512 @@
1
+ # ----------------------------------------------------------------------------
2
+ #
3
+ # `CArray::Histogram` + `CArray#histogram1d` / `#histogram2d` / `#histogram`.
4
+ #
5
+ # A single class handles M=1, 2, ..., general M-D. The 1-D entry point
6
+ # (`data.histogram1d`) reshapes the input to add a trailing channel axis of
7
+ # length 1, then dispatches into the same Histogram path; the returned
8
+ # object is a Histogram instance with M=1.
9
+ #
10
+ # ## Concepts / vocabulary
11
+ #
12
+ # ### Histogram dimensionality
13
+ #
14
+ # * **M** — the histogram's dimensionality (= number of edges arrays = length
15
+ # of the channel axis in input data). M=1 for `histogram1d`, M=2 for
16
+ # `histogram2d`, M>=1 for general `histogram`.
17
+ #
18
+ # ### Input layout
19
+ #
20
+ # Data passed to the entry methods has shape:
21
+ #
22
+ # fiber_shape + (A,) + (M,)
23
+ # ^ ^ ^
24
+ # | | channel axis (= M coordinate dims per sample)
25
+ # | sample axis (= A independent samples per fiber)
26
+ # leading fiber axes (= per-fiber histogram is computed)
27
+ #
28
+ # For `histogram1d` (M=1), the trailing `(M,)` axis may be omitted (= raw
29
+ # `fiber_shape + (A,)` is accepted directly; the entry method reshapes).
30
+ #
31
+ # * **fiber_shape** — the leading axes of the input. Each fiber position
32
+ # gets its own independent histogram. shape `()` (= no fiber axes) means
33
+ # "one global histogram over all samples".
34
+ # * **sample axis** — the axis along which independent samples are drawn.
35
+ # Locked at construction (= the entry method's `axis:` kwarg fixes it).
36
+ # * **channel axis** — the trailing axis of length M carrying the M coordinate
37
+ # dimensions of each sample. For M=1 it is length 1 (= degenerate).
38
+ #
39
+ # ### Bin structure
40
+ #
41
+ # * **edges** — N+1 boundary values defining N bins per dimension. Edges
42
+ # must be sorted ascending. bin k spans `[edges[k], edges[k+1])` (left-
43
+ # closed, right-open). See `include_max:` for the upper-edge variant.
44
+ # * **bin** — half-open interval between two consecutive edges. Bin index
45
+ # k in 0..N-1.
46
+ # * **N** (= n_list[k]) — number of bins along dimension k (= edges.size - 1).
47
+ # * **midpoints** — bin center points: `(edges[0..-2] + edges[1..-1]) / 2`,
48
+ # shape (N,). Returned per-dim (single CArray for M=1, Array of M for M>=2).
49
+ #
50
+ # ### Counts storage (= extended counts model)
51
+ #
52
+ # Internal storage is a single CArray of shape:
53
+ #
54
+ # fiber_shape + (n_0 + 2, n_1 + 2, ..., n_{M-1} + 2)
55
+ #
56
+ # Each bin dimension is "extended" by 2 (= +1 cell on each side for under /
57
+ # over). All accumulation lands in this one buffer; outlier counters are
58
+ # views (= no separate allocation).
59
+ #
60
+ # * **full_counts** — the extended storage, shape includes outlier cells.
61
+ # * **counts** — CABlock inner view: shape fiber + (n_0, ..., n_{M-1}), the
62
+ # in-range bins only. Zero-copy view of `full_counts`.
63
+ # * **under(axis: k)** — count of samples whose dim-k coordinate fell below
64
+ # `edges[k][0]`. Other bin axes are marginalised (= summed over all
65
+ # positions including their outliers). shape = fiber_shape.
66
+ # * **over(axis: k)** — symmetric upper outlier marginal.
67
+ # * **outlier_total** — count of samples that fell outside on *any* axis
68
+ # (= `total - counts.sum`). shape = fiber_shape.
69
+ # * **total** — count of all samples seen (including outliers).
70
+ # shape = fiber_shape.
71
+ #
72
+ # Per-dim marginals may "double-count" samples that fall out on multiple
73
+ # axes (= a sample that's under on dim 0 AND over on dim 1 appears in both
74
+ # `under(axis: 0)` and `over(axis: 1)`). This is the intended marginal
75
+ # reading; for exact non-overlapping breakdown, slice `full_counts`
76
+ # directly.
77
+ #
78
+ # ### Bin closure (`include_max:`)
79
+ #
80
+ # By default, all bins are left-closed right-open: `[edges[k], edges[k+1])`.
81
+ # A sample exactly at `edges[-1]` (= the upper boundary) falls into the over
82
+ # counter, not bin N-1. For bounded physical ranges (humidity 0-100%,
83
+ # probability, angles mod 360°), opt in with `include_max:`:
84
+ #
85
+ # h = data.histogram1d(edges: linspace(0, 100, 21), include_max: true)
86
+ # # v == 100.0 now lands in bin 19 (= [95, 100]), not over.
87
+ #
88
+ # Per-dim Array of booleans is accepted for joint histograms (= mark each
89
+ # dim independently as bounded vs unbounded).
90
+ #
91
+ # ### Weighted accumulation
92
+ #
93
+ # Pass `weights: w` to `histogram1d` / etc., or to `add(chunk, weights: w)`.
94
+ # Each sample contributes `w[i]` instead of 1 to its target cell.
95
+ #
96
+ # * **weights.shape** = chunk.shape minus the channel axis (= fiber + (A,)).
97
+ # * **The data type** is locked at construction (= the entry method's `weights:` kwarg
98
+ # fixes weighted vs unweighted; subsequent adds must match). Counts type:
99
+ # int64 unweighted, float64 weighted. Weighted counts are always float64:
100
+ # the fused scatter kernel requires float64 weights, so integer weights are
101
+ # taken as float64 (= integer weighted counts are not supported).
102
+ #
103
+ # ### Streaming via `add`
104
+ #
105
+ # The instance returned by the entry methods is a live accumulator; further
106
+ # data can be added via `acc.add(chunk, weights: ...)`. Shape contract:
107
+ # the chunk's fiber_shape must match, sample axis length is free. Empty
108
+ # accumulator bootstrap: pass zero-length sample axis to the entry method
109
+ # (e.g. `CArray.float64(K, L, 0).histogram1d(edges: e)`).
110
+ #
111
+ # ### Mask handling
112
+ #
113
+ # Input data with a CArray mask: masked samples are skipped (= not counted
114
+ # in any cell, including outliers). NaN inputs are treated the same way
115
+ # (= masked via `mask_invalid`). Per-sample mask is the union over all
116
+ # channels (= a sample with even one masked channel is dropped entirely).
117
+ #
118
+ # ### Composition
119
+ #
120
+ # `h1 + h2` returns a new Histogram with cells summed elementwise. Both
121
+ # operands must agree on edges / fiber_shape / include_max / weighted data type
122
+ # (= the structure-level semantic guard); cells themselves are just
123
+ # integer / float tallies. See the `+` method.
124
+ #
125
+ # ----------------------------------------------------------------------------
126
+
127
+ class CArray
128
+
129
+ # Binned counts over one or more continuous variables, of any
130
+ # dimensionality M. Built by `CArray#histogram1d` / `#histogram2d` /
131
+ # `#histogram` rather than constructed directly; the 1-D entry point is the
132
+ # same class with M = 1.
133
+ #
134
+ # For discrete integer labels (value == bin index, no edges) use
135
+ # {BincountND} instead.
136
+ class Histogram
137
+
138
+ # @overload initialize(edges:, fiber_shape: [], include_max: false, weights_data_type: nil)
139
+ # Allocates a new histogram accumulator.
140
+ # @param edges [Array<CArray, Array<Numeric>>] one edges array
141
+ # per histogram dimension; each must be 1-D sorted ascending
142
+ # with at least 2 values.
143
+ # @param fiber_shape [Array<Integer>] shape of the leading
144
+ # (non-sample, non-channel) axes.
145
+ # @param include_max [Boolean, Array<Boolean>] whether values
146
+ # equal to the last edge fold into the last bin; a scalar
147
+ # broadcasts across dimensions.
148
+ # @param weights_data_type [Symbol, nil] `data_type` of the
149
+ # accumulator when weighted; `nil` for a count-only
150
+ # accumulator (int64 counts).
151
+ # @return [Histogram]
152
+ def initialize (edges:, fiber_shape: [], include_max: false, weights_data_type: nil)
153
+ @edges_list = edges.map { |e| CArray.wrap_readonly(e, :float64) }
154
+ raise ArgumentError, "edges must be a non-empty list" if @edges_list.empty?
155
+ @edges_list.each_with_index do |e, k|
156
+ raise ArgumentError, "edges[#{k}] must be 1-D" unless e.ndim == 1
157
+ raise ArgumentError, "edges[#{k}] needs at least 2 values" if e.elements < 2
158
+ end
159
+ @m = @edges_list.size # histogram dimensionality (= channel axis length)
160
+ @n_list = @edges_list.map { |e| e.elements - 1 } # per-dim bin count
161
+ @fiber_shape = fiber_shape.map(&:to_i).freeze
162
+ @include_max = case include_max
163
+ when Array
164
+ raise ArgumentError, "include_max length mismatch" unless include_max.size == @m
165
+ include_max.map { |v| !!v }
166
+ else
167
+ [!!include_max] * @m
168
+ end
169
+ @weighted = !weights_data_type.nil?
170
+ @counts_data_type = @weighted ? weights_data_type : :int64
171
+ ext_dims = @n_list.map { |n| n + 2 }
172
+ ext_shape = @fiber_shape + ext_dims
173
+ @full_counts = CArray.public_send(@counts_data_type, *ext_shape).fill(0)
174
+ @sample_axis = nil
175
+ @channel_axis = nil
176
+ end
177
+ private_class_method :new
178
+
179
+ attr_reader :edges_list, :fiber_shape, :include_max, :n_list, :full_counts, :m
180
+
181
+ # @overload edges
182
+ # Returns the bin edges: a single CArray when the accumulator
183
+ # is 1-D (M == 1), an Array of CArrays otherwise.
184
+ # @return [CArray, Array<CArray>]
185
+ def edges
186
+ @m == 1 ? @edges_list[0] : @edges_list
187
+ end
188
+
189
+ # @overload counts
190
+ # Returns the in-range counts view with shape
191
+ # `fiber_shape + (n1, n2, ..., nM)`, excluding under- and
192
+ # over-flow bins.
193
+ # @return [CArray]
194
+ def counts
195
+ idx = [nil] * @fiber_shape.size + [1..-2] * @m
196
+ @full_counts[*idx]
197
+ end
198
+
199
+ # @overload under(axis: nil)
200
+ # Returns the underflow marginal along the given bin `axis`,
201
+ # shape `fiber_shape`. For 1-D accumulators `axis` may be
202
+ # omitted; for joint histograms it must be specified.
203
+ # @param axis [Integer, nil] bin dimension to marginalise.
204
+ # @return [CArray]
205
+ # @raise [ArgumentError] when `axis` is required but omitted.
206
+ def under (axis: nil)
207
+ raise ArgumentError, "axis: keyword required (M=#{@m})" if axis.nil? && @m > 1
208
+ ax = axis.nil? ? 0 : CArray.normalize_axis(axis, @m, "under")
209
+ outlier_marginal(ax, 0)
210
+ end
211
+
212
+ # @overload over(axis: nil)
213
+ # Returns the overflow marginal along the given bin `axis`,
214
+ # shape `fiber_shape`. Same `axis` convention as {#under}.
215
+ # @param axis [Integer, nil] bin dimension to marginalise.
216
+ # @return [CArray]
217
+ # @raise [ArgumentError] when `axis` is required but omitted.
218
+ def over (axis: nil)
219
+ raise ArgumentError, "axis: keyword required (M=#{@m})" if axis.nil? && @m > 1
220
+ ax = axis.nil? ? 0 : CArray.normalize_axis(axis, @m, "over")
221
+ outlier_marginal(ax, -1)
222
+ end
223
+
224
+ # @overload midpoints
225
+ # Returns the midpoint of each in-range bin. Polymorphic like
226
+ # {#edges}: a single CArray for M == 1, an Array of CArrays
227
+ # otherwise.
228
+ # @return [CArray, Array<CArray>]
229
+ def midpoints
230
+ arr = @edges_list.map { |e| (e[0..-2] + e[1..-1]) / 2.0 }
231
+ @m == 1 ? arr[0] : arr
232
+ end
233
+
234
+ # @overload total
235
+ # Returns the per-fiber sample total, including outliers, with
236
+ # shape `fiber_shape` (or a scalar when `fiber_shape` is
237
+ # empty).
238
+ # @return [CArray]
239
+ def total
240
+ sum_along_bin_axes(@full_counts)
241
+ end
242
+
243
+ # @overload outlier_total
244
+ # Returns the per-fiber count of samples that fell outside
245
+ # every in-range bin.
246
+ # @return [CArray]
247
+ def outlier_total
248
+ sum_along_bin_axes(@full_counts) - sum_along_bin_axes(counts)
249
+ end
250
+
251
+ # @overload add(chunk, axis: nil, weights: nil)
252
+ # Accumulates `chunk` into `self`. On the first call the
253
+ # sample and channel axes are locked; subsequent calls must
254
+ # supply the same axis pair. When the accumulator is
255
+ # weighted, `weights` are required with a shape equal to
256
+ # `chunk.shape` minus the channel axis.
257
+ # @param chunk [CArray] sample values with shape
258
+ # `fiber_shape + (A, M)` (channel axis size must equal `m`).
259
+ # @param axis [Array(Integer, Integer), Integer, nil]
260
+ # `[sample, channel]` axis pair; a bare Integer is treated
261
+ # as the sample axis for 1-D accumulators.
262
+ # @param weights [CArray, nil] optional per-sample weights.
263
+ # @return [self]
264
+ # @raise [ArgumentError] on shape / axis / weighted-state
265
+ # mismatch.
266
+ def add (chunk, axis: nil, weights: nil)
267
+ chunk = CArray.wrap_readonly(chunk, :float64)
268
+
269
+ # For M=1, accept chunks without the trailing channel axis (= the 1-D
270
+ # user convention from `histogram1d`). Auto-reshape adds a length-1
271
+ # axis at the end; scalar `axis:` is interpreted as the sample axis in
272
+ # the unwrapped layout.
273
+ if @m == 1 && chunk.ndim == @fiber_shape.size + 1
274
+ chunk = chunk.reshape(*(chunk.shape + [1]))
275
+ if axis.is_a?(Integer)
276
+ # axis was given in the unwrapped (pre-reshape) layout: normalize
277
+ # against ndim-1 (= the unwrapped ndim) then pair with the new
278
+ # trailing channel position.
279
+ ax = CArray.normalize_axis(axis, chunk.ndim - 1, "add axis")
280
+ axis = [ax, chunk.ndim - 1]
281
+ end
282
+ end
283
+
284
+ # --- normalize axis: into [sample, channel] pair -----------------
285
+ ax = axis || [-2, -1]
286
+ ax = [ax] if ax.is_a?(Integer)
287
+ raise ArgumentError, "axis must be [sample, channel]" unless ax.is_a?(Array) && ax.size == 2
288
+ sample_ax = CArray.normalize_axis(ax[0], chunk.ndim, "sample axis")
289
+ channel_ax = CArray.normalize_axis(ax[1], chunk.ndim, "channel axis")
290
+ raise ArgumentError, "same axis used twice" if sample_ax == channel_ax
291
+
292
+ # --- lock axes on first add, otherwise verify against locked -----
293
+ if @sample_axis.nil?
294
+ @sample_axis = sample_ax
295
+ @channel_axis = channel_ax
296
+ elsif @sample_axis != sample_ax || @channel_axis != channel_ax
297
+ raise ArgumentError,
298
+ "axis mismatch (locked at [#{@sample_axis}, #{@channel_axis}], got [#{sample_ax}, #{channel_ax}])"
299
+ end
300
+
301
+ # --- validate chunk shape against (fiber_shape, M) ---------------
302
+ expected_ndim = @fiber_shape.size + 2
303
+ unless chunk.ndim == expected_ndim
304
+ raise ArgumentError,
305
+ "chunk.ndim=#{chunk.ndim} expected #{expected_ndim} " \
306
+ "(fiber #{@fiber_shape.inspect} + sample + channel)"
307
+ end
308
+ unless chunk.shape[channel_ax] == @m
309
+ raise ArgumentError,
310
+ "channel axis length #{chunk.shape[channel_ax]} != M=#{@m}"
311
+ end
312
+ chunk_fiber = chunk.shape.dup
313
+ [sample_ax, channel_ax].sort.reverse.each { |p| chunk_fiber.delete_at(p) }
314
+ unless chunk_fiber == @fiber_shape
315
+ raise ArgumentError,
316
+ "fiber shape mismatch: chunk yields #{chunk_fiber.inspect}, expected #{@fiber_shape.inspect}"
317
+ end
318
+
319
+ sample_count = chunk.shape[sample_ax]
320
+ return self if sample_count == 0
321
+
322
+ if weights
323
+ raise ArgumentError, "weights given but accumulator is unweighted" unless @weighted
324
+ weights = CArray.wrap_readonly(weights, @counts_data_type)
325
+ expected_w_shape = chunk.shape.dup
326
+ expected_w_shape.delete_at(channel_ax)
327
+ unless weights.shape == expected_w_shape
328
+ raise ArgumentError,
329
+ "weights shape #{weights.shape.inspect} expected #{expected_w_shape.inspect} " \
330
+ "(chunk minus channel axis at #{channel_ax})"
331
+ end
332
+ elsif @weighted
333
+ raise ArgumentError, "weights required (accumulator is weighted)"
334
+ end
335
+
336
+ # --- fused scatter kernel (stage 2) ------------------------------
337
+ # Bin all M channels per sample and scatter directly into @full_counts
338
+ # with NO intermediate index arrays (peak memory O(1), not O(M * A) for
339
+ # A samples).
340
+ # self is transposed to [fiber..., sample, channel] (a view; the kernel
341
+ # iterator delivers it strided, no materialise). Weights, if present,
342
+ # are transposed to [fiber..., sample] and delivered by a second
343
+ # iterator in lockstep (both views, no materialise).
344
+ fiber_axes = (0...chunk.ndim).to_a - [sample_ax, channel_ax]
345
+ tchunk = chunk.transpose(*(fiber_axes + [sample_ax, channel_ax]))
346
+
347
+ tweights = nil
348
+ if weights
349
+ # weights axes = chunk axes with channel removed: an index above
350
+ # channel_ax shifts down by 1.
351
+ shift = ->(p) { p < channel_ax ? p : p - 1 }
352
+ tweights = weights.transpose(*(fiber_axes.map(&shift) + [shift.call(sample_ax)]))
353
+ end
354
+
355
+ tchunk.send(:histogram_scatter_ki, @full_counts, @edges_list, @include_max, tweights)
356
+
357
+ self
358
+ end
359
+
360
+ # @overload +(other)
361
+ # Returns a new Histogram whose counts are the element-wise
362
+ # sum of `self` and `other`. Both operands must share edges,
363
+ # `fiber_shape`, `include_max`, and weighted/unweighted state.
364
+ # @param other [Histogram] compatible accumulator.
365
+ # @return [Histogram]
366
+ # @raise [ArgumentError] when the structure does not match.
367
+ def + (other)
368
+ # --- semantic guards: structure must match exactly ---------------
369
+ raise ArgumentError, "type mismatch" unless other.is_a?(Histogram)
370
+ raise ArgumentError, "M mismatch" unless @m == other.m
371
+ @edges_list.each_with_index do |e, k|
372
+ raise ArgumentError, "edges[#{k}] mismatch" unless e == other.edges_list[k]
373
+ end
374
+ raise ArgumentError, "fiber_shape mismatch" unless @fiber_shape == other.fiber_shape
375
+ raise ArgumentError, "include_max mismatch (semantic guard)" unless @include_max == other.include_max
376
+ raise ArgumentError, "weighted/unweighted mismatch" unless @weighted == other.weighted?
377
+
378
+ result = self.class.send(:new,
379
+ edges: @edges_list,
380
+ fiber_shape: @fiber_shape,
381
+ include_max: @include_max,
382
+ weights_data_type: @weighted ? @counts_data_type : nil)
383
+ rf = result.instance_variable_get(:@full_counts)
384
+ rf[] = @full_counts + other.full_counts
385
+ result.instance_variable_set(:@sample_axis, @sample_axis)
386
+ result.instance_variable_set(:@channel_axis, @channel_axis)
387
+ result
388
+ end
389
+
390
+ protected
391
+
392
+ # Exposed to sibling instances only (= `+` reads the other operand's
393
+ # weighted state for the semantic guard). protected, not public: this is
394
+ # internal accumulator state, not part of the user-facing surface.
395
+ def weighted?
396
+ @weighted
397
+ end
398
+
399
+ private
400
+
401
+ # arr.shape = fiber_shape + (last M bin axes).
402
+ # Reduce along the last M axes, returns shape fiber_shape (or scalar).
403
+ #
404
+ # `accumulate` preserves the data type (= int64 stays int64, float64 stays float64),
405
+ # unlike `sum` which always lifts to float64. Caveat: int64 overflows at
406
+ # ~9.2e18 (silent wrap); weighted float64 loses precision past 2^53 but
407
+ # does not overflow. Realistic histograms do not hit these limits.
408
+ def sum_along_bin_axes (arr)
409
+ out = arr
410
+ @m.times { out = out.accumulate(axis: out.ndim - 1) }
411
+ out
412
+ end
413
+
414
+ # axis k of the bin dims (= which channel's outlier to look at).
415
+ # offset = 0 (under) or -1 (over). Other bin axes are marginalised away
416
+ # (= including their outlier positions).
417
+ def outlier_marginal (axis, offset)
418
+ base = [nil] * @fiber_shape.size
419
+ bin_idx = (0...@m).map { |k|
420
+ if k == axis
421
+ offset == 0 ? 0 : @n_list[k] + 1
422
+ else
423
+ nil
424
+ end
425
+ }
426
+ slice = @full_counts[*(base + bin_idx)]
427
+ remaining = @m - 1
428
+ remaining.times { slice = slice.accumulate(axis: slice.ndim - 1) }
429
+ slice
430
+ end
431
+
432
+ end
433
+
434
+ end
435
+
436
+
437
+ class CArray
438
+
439
+ # @overload histogram1d(edges:, axis: -1, include_max: false, weights: nil)
440
+ # Returns a 1-D {Histogram} built from `self` with shape
441
+ # `fiber_shape + (A,)`, where `A` is the sample axis of length
442
+ # picked by `axis`.
443
+ # @param edges [CArray, Array<Numeric>] 1-D ascending bin edges.
444
+ # @param axis [Integer] sample axis.
445
+ # @param include_max [Boolean] fold last-edge equality into the
446
+ # last bin.
447
+ # @param weights [CArray, nil] optional per-sample weights.
448
+ # @return [Histogram] 1-D accumulator (`m == 1`).
449
+ def histogram1d (edges:, axis: -1, include_max: false, weights: nil)
450
+ ax = normalize_axis(axis, "histogram1d")
451
+
452
+ new_shape = shape + [1]
453
+ arr_with_channel = reshape(*new_shape)
454
+ # `include_max` passes straight through: the Histogram constructor
455
+ # normalizes a scalar bool to per-dim, and raises on a wrong-length Array
456
+ # (= same path as histogram2d, no M=1 special-casing here).
457
+ arr_with_channel.histogram(edges: [edges],
458
+ axis: [ax, new_shape.size - 1],
459
+ include_max: include_max,
460
+ weights: weights)
461
+ end
462
+
463
+ # @overload histogram2d(edges:, axis: [-2, -1], include_max: false, weights: nil)
464
+ # Returns a 2-D joint {Histogram} built from `self` with shape
465
+ # `fiber_shape + (A, 2)`.
466
+ # @param edges [Array<CArray, Array<Numeric>>] two edges arrays.
467
+ # @param axis [Array(Integer, Integer)] `[sample, channel]`
468
+ # axis pair.
469
+ # @param include_max [Boolean, Array<Boolean>] fold-max flag,
470
+ # per dimension.
471
+ # @param weights [CArray, nil] optional per-sample weights.
472
+ # @return [Histogram] 2-D accumulator (`m == 2`).
473
+ # @raise [ArgumentError] when `edges` is not a length-2 Array.
474
+ def histogram2d (edges:, axis: [-2, -1], include_max: false, weights: nil)
475
+ raise ArgumentError, "edges must be a list of 2" unless edges.is_a?(Array) && edges.size == 2
476
+ histogram(edges: edges, axis: axis, include_max: include_max, weights: weights)
477
+ end
478
+
479
+ # @overload histogram(edges:, axis: [-2, -1], include_max: false, weights: nil)
480
+ # Returns an M-D joint {Histogram} built from `self` with shape
481
+ # `fiber_shape + (A, M)`, where `M == edges.size`.
482
+ # @param edges [Array<CArray, Array<Numeric>>] one edges array
483
+ # per dimension.
484
+ # @param axis [Array(Integer, Integer)] `[sample, channel]`
485
+ # axis pair.
486
+ # @param include_max [Boolean, Array<Boolean>] fold-max flag,
487
+ # per dimension.
488
+ # @param weights [CArray, nil] optional per-sample weights.
489
+ # @return [Histogram]
490
+ # @raise [ArgumentError] when `edges` is not an Array.
491
+ def histogram (edges:, axis: [-2, -1], include_max: false, weights: nil)
492
+ raise ArgumentError, "edges must be an Array of edges arrays" unless edges.is_a?(Array)
493
+ arr = self
494
+ sample_ax = normalize_axis(axis[0], "histogram sample axis")
495
+ channel_ax = normalize_axis(axis[1], "histogram channel axis")
496
+ fiber_shape = arr.shape.dup
497
+ [sample_ax, channel_ax].sort.reverse.each { |p| fiber_shape.delete_at(p) }
498
+
499
+ # Weighted counts are float64-only (the fused scatter kernel requires
500
+ # float64 weights and float64 counts), so the type is fixed here rather
501
+ # than derived from the weights' own type.
502
+ weights_data_type = (:float64 if weights)
503
+
504
+ h = Histogram.send(:new,
505
+ edges: edges,
506
+ fiber_shape: fiber_shape,
507
+ include_max: include_max,
508
+ weights_data_type: weights_data_type)
509
+ h.add(arr, axis: axis, weights: weights)
510
+ h
511
+ end
512
+ end
@@ -1,12 +1,4 @@
1
- # ----------------------------------------------------------------------------
2
- #
3
- # carray/inspect.rb
4
- #
5
- # This file is part of Ruby/CArray extension library.
6
- #
7
- # Copyright (C) 2005-2025 Hiroki Motoyoshi
8
- #
9
- # ----------------------------------------------------------------------------
1
+ require "pp" # CArray#source_code uses Array#pretty_inspect
10
2
 
11
3
  class CArray::Inspector # :nodoc:
12
4
 
@@ -14,6 +6,7 @@ class CArray::Inspector # :nodoc:
14
6
  @carray = carray
15
7
  end
16
8
 
9
+ # @!visibility private
17
10
  def inspect_string
18
11
  if @carray.ndim == 0
19
12
  raise "can't inspect CArray of ndim == 0"
@@ -46,7 +39,9 @@ class CArray::Inspector # :nodoc:
46
39
  @carray.instance_exec {
47
40
  case data_type
48
41
  when CA_FIXLEN
49
- return format("%s[%i]", type_name, bytes)
42
+ # Kernel.format explicitly: inside instance_exec self is the CArray,
43
+ # where a bare format() would resolve to the public CArray#format.
44
+ return Kernel.format("%s[%i]", type_name, bytes)
50
45
  else
51
46
  return type_name
52
47
  end
@@ -54,13 +49,7 @@ class CArray::Inspector # :nodoc:
54
49
  end
55
50
 
56
51
  def get_shape
57
- case @carray.obj_type
58
- when CA_OBJ_UNBOUND_REPEAT
59
- dim = @carray.spec
60
- else
61
- dim = @carray.dim
62
- end
63
- return dim
52
+ return @carray.shape
64
53
  end
65
54
 
66
55
  def get_info_list
@@ -108,22 +97,34 @@ class CArray::Inspector # :nodoc:
108
97
  list << "attached"
109
98
  end
110
99
  # ---
111
- if has_attribute?
112
- list << "attrs={" + attribute.keys.join(",") + "}"
100
+ if has_attr?
101
+ list << "attrs={" + attrs.keys.join(",") + "}"
113
102
  end
114
103
  }
115
104
  return list
116
105
  end
117
106
 
118
107
  def get_formatter
108
+ # A Face that defines storage_to_scalar decodes each cell into a surface
109
+ # value (CATime::Element, a String, a category label, ...) that has nothing
110
+ # to do with the storage data_type, so the formatter must follow the decoded
111
+ # value, not the storage. Faces without the hook (CAString) hand back the
112
+ # stored value itself and fall through to the storage formatters below.
113
+ if @carray.face? and @carray.respond_to?(:storage_to_scalar)
114
+ return lambda { |x| x.inspect }
115
+ end
119
116
  case @carray.data_type
120
- when CA_BOOLEAN, CA_INT8, CA_INT16, CA_INT32, CA_INT64
117
+ when CA_BOOLEAN
118
+ # Boolean cells fetch as true/false; show compact 1/0 (masked = _).
119
+ # The type name in the inspect header distinguishes this from an int array.
120
+ lambda{|x| x ? "1" : "0" }
121
+ when CA_INT8, CA_INT16, CA_INT32, CA_INT64
121
122
  lambda{|x| "%i" % x }
122
123
  when CA_UINT8, CA_UINT16, CA_UINT32, CA_UINT64
123
124
  lambda{|x| "%u" % x }
124
- when CA_FLOAT32, CA_FLOAT64, CA_FLOAT128
125
+ when CA_FLOAT32, CA_FLOAT64
125
126
  lambda{|x| x.inspect }
126
- when CA_CMPLX64, CA_CMPLX128, CA_CMPLX256
127
+ when CA_CMPLX64, CA_CMPLX128
127
128
  lambda{|x| format("%s%s%si",
128
129
  x.real.inspect, (x.imag >= 0) ? "+" : "-", x.imag.abs.inspect) }
129
130
  when CA_FIXLEN
@@ -146,9 +147,9 @@ class CArray::Inspector # :nodoc:
146
147
  end
147
148
 
148
149
  def get_data_spec (level, idx, formatter)
149
- io = "[ "
150
+ io = +"[ " # mutable buffer; `<<` below appends into it
150
151
  ndim = @carray.ndim
151
- dim = @carray.dim
152
+ dim = @carray.shape
152
153
  if level == ndim - 1
153
154
  over = false
154
155
  dim[level].times do |i|
@@ -209,6 +210,11 @@ end
209
210
 
210
211
  class CArray
211
212
 
213
+ # @overload inspect
214
+ # Returns a human-readable description of `self` including
215
+ # class, `data_type`, shape, element and memory summaries, mask
216
+ # count, and a truncated data preview.
217
+ # @return [String]
212
218
  def inspect
213
219
  return CArray::Inspector.new(self).inspect_string
214
220
  end
@@ -216,7 +222,7 @@ class CArray
216
222
  private
217
223
 
218
224
  def desc
219
- output = ""
225
+ output = +"" # mutable buffer; `<<` below appends into it
220
226
  case data_type
221
227
  when CA_FIXLEN
222
228
  output << sprintf("CArray.%s(%s, :bytes=>%i)",
@@ -230,7 +236,12 @@ class CArray
230
236
 
231
237
  public
232
238
 
233
- def code
239
+ # @overload source_code
240
+ # Returns a Ruby source-like string that would reconstruct
241
+ # `self`, combining the type/shape descriptor with a pretty
242
+ # printed value block. Useful for embedding fixtures in scripts.
243
+ # @return [String]
244
+ def source_code
234
245
  text = [
235
246
  desc,
236
247
  " { ",