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,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
+ # * **dtype** is locked at construction (= the entry method's `weights:` kwarg
98
+ # fixes weighted vs unweighted; subsequent adds must match). Counts dtype:
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 dtype
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_dtype: 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_dtype [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_dtype: 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_dtype.nil?
170
+ @counts_dtype = @weighted ? weights_dtype : :int64
171
+ ext_dims = @n_list.map { |n| n + 2 }
172
+ ext_shape = @fiber_shape + ext_dims
173
+ @full_counts = CArray.public_send(@counts_dtype, *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_dtype)
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_dtype: @weighted ? @counts_dtype : 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 dtype (= 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 dtype is fixed here rather
501
+ # than derived from the weights' own dtype.
502
+ weights_dtype = (:float64 if weights)
503
+
504
+ h = Histogram.send(:new,
505
+ edges: edges,
506
+ fiber_shape: fiber_shape,
507
+ include_max: include_max,
508
+ weights_dtype: weights_dtype)
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-2020 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
@@ -58,7 +53,7 @@ class CArray::Inspector # :nodoc:
58
53
  when CA_OBJ_UNBOUND_REPEAT
59
54
  dim = @carray.spec
60
55
  else
61
- dim = @carray.dim
56
+ dim = @carray.shape
62
57
  end
63
58
  return dim
64
59
  end
@@ -108,22 +103,34 @@ class CArray::Inspector # :nodoc:
108
103
  list << "attached"
109
104
  end
110
105
  # ---
111
- if has_attribute?
112
- list << "attrs={" + attribute.keys.join(",") + "}"
106
+ if has_attr?
107
+ list << "attrs={" + attrs.keys.join(",") + "}"
113
108
  end
114
109
  }
115
110
  return list
116
111
  end
117
112
 
118
113
  def get_formatter
114
+ # A Face that defines storage_to_scalar decodes each cell into a surface
115
+ # value (CATime::Element, a String, a category label, ...) that has nothing
116
+ # to do with the storage data_type, so the formatter must follow the decoded
117
+ # value, not the storage. Faces without the hook (CAString) hand back the
118
+ # stored value itself and fall through to the storage formatters below.
119
+ if @carray.face? and @carray.respond_to?(:storage_to_scalar)
120
+ return lambda { |x| x.inspect }
121
+ end
119
122
  case @carray.data_type
120
- when CA_BOOLEAN, CA_INT8, CA_INT16, CA_INT32, CA_INT64
123
+ when CA_BOOLEAN
124
+ # Boolean cells fetch as true/false; show compact 1/0 (masked = _).
125
+ # The type name in the inspect header distinguishes this from an int array.
126
+ lambda{|x| x ? "1" : "0" }
127
+ when CA_INT8, CA_INT16, CA_INT32, CA_INT64
121
128
  lambda{|x| "%i" % x }
122
129
  when CA_UINT8, CA_UINT16, CA_UINT32, CA_UINT64
123
130
  lambda{|x| "%u" % x }
124
- when CA_FLOAT32, CA_FLOAT64, CA_FLOAT128
131
+ when CA_FLOAT32, CA_FLOAT64
125
132
  lambda{|x| x.inspect }
126
- when CA_CMPLX64, CA_CMPLX128, CA_CMPLX256
133
+ when CA_CMPLX64, CA_CMPLX128
127
134
  lambda{|x| format("%s%s%si",
128
135
  x.real.inspect, (x.imag >= 0) ? "+" : "-", x.imag.abs.inspect) }
129
136
  when CA_FIXLEN
@@ -146,9 +153,9 @@ class CArray::Inspector # :nodoc:
146
153
  end
147
154
 
148
155
  def get_data_spec (level, idx, formatter)
149
- io = "[ "
156
+ io = +"[ " # mutable buffer; `<<` below appends into it
150
157
  ndim = @carray.ndim
151
- dim = @carray.dim
158
+ dim = @carray.shape
152
159
  if level == ndim - 1
153
160
  over = false
154
161
  dim[level].times do |i|
@@ -209,6 +216,11 @@ end
209
216
 
210
217
  class CArray
211
218
 
219
+ # @overload inspect
220
+ # Returns a human-readable description of `self` including
221
+ # class, `data_type`, shape, element and memory summaries, mask
222
+ # count, and a truncated data preview.
223
+ # @return [String]
212
224
  def inspect
213
225
  return CArray::Inspector.new(self).inspect_string
214
226
  end
@@ -216,7 +228,7 @@ class CArray
216
228
  private
217
229
 
218
230
  def desc
219
- output = ""
231
+ output = +"" # mutable buffer; `<<` below appends into it
220
232
  case data_type
221
233
  when CA_FIXLEN
222
234
  output << sprintf("CArray.%s(%s, :bytes=>%i)",
@@ -230,7 +242,12 @@ class CArray
230
242
 
231
243
  public
232
244
 
233
- def code
245
+ # @overload source_code
246
+ # Returns a Ruby source-like string that would reconstruct
247
+ # `self`, combining the type/shape descriptor with a pretty
248
+ # printed value block. Useful for embedding fixtures in scripts.
249
+ # @return [String]
250
+ def source_code
234
251
  text = [
235
252
  desc,
236
253
  " { ",