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,2542 @@
1
+ /* ---------------------------------------------------------------------------
2
+
3
+ carray_factorize.c — single-pass value seen-set kernels.
4
+
5
+ The value-hash discovery family shares one open-addressing hash that maps a
6
+ widened 64-bit key to first-appearance state in a single linear pass, read
7
+ through the kernel_iterator fiber surface (no entry ca_attach). Each kernel
8
+ reads a different answer out of the same intern pass:
9
+
10
+ - __factorize_appearance__ (categorize): map each value to a dense code in
11
+ first-appearance order, emitting both the code storage and the level
12
+ vocabulary in one pass. Replaces categorize's discovery path, which reads
13
+ the levels then assigns codes with one full eq scan per distinct value
14
+ (O(distinct * N)).
15
+
16
+ - __mask_duplicates__ (mask_duplicates): mark every cell whose value
17
+ duplicates an earlier-seen one. The hash's first-seen flag IS the answer
18
+ (dup = not first-seen), so the sort-based mask_dup path collapses to the
19
+ same one pass.
20
+
21
+ - __unique_flat__ (unique): emit the distinct values in appearance order
22
+ (the levels alone, codes discarded).
23
+
24
+ - __value_counts_flat__ (value_counts): the levels plus a per-code count
25
+ (a count lane beside the level buffer).
26
+
27
+ - __nunique__ (nunique): the distinct count per fiber (the intern count is
28
+ the answer; the reduction accumulator is a no-op).
29
+
30
+ - __is_mode__ (is_mode): mark every cell holding a modal value (count equal
31
+ to the fiber max) with a per-fiber two-pass frequency table; ties are all
32
+ marked, never broken.
33
+
34
+ - __mode_axis__ (mode per-axis): read the distinct modal values out of the
35
+ per-fiber frequency table, ascending, as a ragged Array of reduced
36
+ CArrays (the value-form consumer of the is_mode primitive).
37
+
38
+ Peak memory is O(distinct values) for the hash, plus each kernel's own output.
39
+ No sort, no gathered copy.
40
+
41
+ The hash carries three key lanes. Numeric (integer / float) widens the
42
+ element to a lossless 64-bit key; boolean rides the uint8 numeric lane (its
43
+ storage is uint8 0/1, so at most two distinct keys ever intern). Float
44
+ reproduces `==` with two value-based
45
+ exceptions: all NaN collapse to one distinct value (so the second and later
46
+ NaN are duplicates) and +0.0 / -0.0 compare equal (so -0.0 is normalized to
47
+ +0.0 before the bitwise key). Object keys on rb_hash with an rb_eql re-check,
48
+ and fixlen on a byte-hash with a memcmp re-check; both reproduce Ruby Hash
49
+ distinctness (`hash` + `eql?`) -- which already folds -0.0 / +0.0 together
50
+ (Float#eql? treats them equal). The object lane adds one deviation from Ruby
51
+ Hash, aligning it with the numeric lane: every Float NaN collapses to one
52
+ distinct value (a NaN takes a fixed canonical key and matches any stored Float
53
+ NaN), where Ruby Hash would keep distinct NaN objects apart.
54
+ __factorize_appearance__ and the discovery members (unique, value_counts,
55
+ mask_duplicates) all cover the numeric / object / fixlen lanes.
56
+
57
+ Private surfaces:
58
+ self.__factorize_appearance__ -> [codes, levels]
59
+ codes : narrow unsigned CArray (uint8 / uint16 / uint32), the code
60
+ storage; masked source cells store the type-max sentinel
61
+ (from_codes derives the mask from it, matching categorize).
62
+ levels : integer CArray (source dtype) of the k distinct values in
63
+ first-appearance order.
64
+ self.__mask_duplicates__(axis) -> boolean CArray of self.shape, true at
65
+ each cell that duplicates an earlier-seen one along axis (per-fiber
66
+ independent seen-set). Masked cells do not participate and stay false.
67
+ self.__unique_flat__ -> 1-D CArray of the distinct values (appearance order).
68
+ self.__value_counts_flat__ -> [levels, counts] (counts is 1-D CA_INT64).
69
+ self.__nunique__(axis, keep_axis) -> reduced CA_INT64 distinct-count CArray.
70
+ self.__is_mode__(axis) -> boolean CArray of self.shape, true at every modal
71
+ cell (per-fiber max count) along axis; ties all marked.
72
+ self.__mode_axis__(axis) -> Array of K reduced CArrays (self.shape with axis
73
+ dropped), slot j = each fiber's j-th smallest modal value ascending, UNDEF
74
+ where a fiber has fewer than j+1 modes; K = widest fiber's modal count.
75
+
76
+ --------------------------------------------------------------------------- */
77
+
78
+ #include "carray.h"
79
+ #include "ca_obj_face.h"
80
+ #include <string.h>
81
+ #include <math.h>
82
+
83
+ /* ---- Face gate (same three moves as the search / sort / count families) ----
84
+
85
+ The value hash keys on raw cells, so a Face has to be brought into its
86
+ storage space first and put back afterwards:
87
+
88
+ - descend an ORDERABLE Face to its storage. ORDERABLE claims that
89
+ storage order == surface order, which for equality is the
90
+ part that matters: equal storage <=> equal surface.
91
+ - reconcile an operand through the reference Face (to_comparable), so a
92
+ set / membership question compares instants rather than the
93
+ ticks of whichever unit each side happens to carry.
94
+ - re-lift an output that carries *values* (unique / value_counts'
95
+ values / mode / the set operations), so the caller gets its
96
+ own type back. Counts, booleans and indices stay plain.
97
+
98
+ A Face without ORDERABLE is left exactly as it was: its equality is not its
99
+ storage's, so descending would answer the wrong question. CAConstString is
100
+ the live example -- a cell is a (start, end) byte range, so two equal strings
101
+ at different offsets have different storage -- and it needs its own
102
+ discovery surface rather than this gate (see
103
+ devel/PROPOSAL_DISCOVERY_FAMILY_FACE_GATE.md §4). */
104
+
105
+ /* Descend `self` when it is an ORDERABLE Face; *pface keeps the pre-strip Face
106
+ for the re-lift (Qnil when there is nothing to put back). */
107
+ static VALUE
108
+ fz_face_descend (VALUE self, volatile VALUE *pface)
109
+ {
110
+ CArray *ca;
111
+ GetCArray(self, ca);
112
+ if ( ca_is_face(ca) && ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
113
+ *pface = self;
114
+ return rb_ca_strip_face_value(self);
115
+ }
116
+ *pface = Qnil;
117
+ return self;
118
+ }
119
+
120
+ /* Bring `operand` into the space of `reference` (the *pre-strip* Face) and
121
+ descend it, so both sides hash in one storage space.
122
+
123
+ `to_comparable` exists because a Face can carry a *unit* -- an alternative
124
+ space for the same value -- so a reference that defines it reconciles any
125
+ operand type through it (and refuses what it cannot convert, which is how a
126
+ bare storage number stays out). A reference with one space only (COMPARABLE,
127
+ or a Face with no unit algebra at all, e.g. CAString) has nothing to
128
+ reconcile between: descending both sides is the whole job. */
129
+ static VALUE
130
+ fz_face_reconcile (VALUE reference, VALUE operand, const char *name)
131
+ {
132
+ CArray *ca;
133
+ if ( NIL_P(reference) ) {
134
+ return operand;
135
+ }
136
+ GetCArray(reference, ca);
137
+ if ( ! ca_test_flag(ca, CA_FLAG_FACE_COMPARABLE_STORAGE)
138
+ && rb_respond_to(reference, rb_intern("to_comparable")) ) {
139
+ return rb_ca_strip_face_value(rb_funcall(reference,
140
+ rb_intern("to_comparable"),
141
+ 1, operand));
142
+ }
143
+ if ( rb_obj_is_carray(operand) ) {
144
+ CArray *op;
145
+ GetCArray(operand, op);
146
+ if ( ca_is_face(op) ) {
147
+ return rb_ca_strip_face_value(operand);
148
+ }
149
+ }
150
+ (void) name;
151
+ return operand;
152
+ }
153
+
154
+ /* Put the Face back on a value-carrying output. */
155
+ static VALUE
156
+ fz_face_relift (VALUE out, VALUE face)
157
+ {
158
+ if ( NIL_P(face) ) {
159
+ return out;
160
+ }
161
+ return ca_face_lift(out, face);
162
+ }
163
+
164
+ /* ---- open-addressing hash: widened 64-bit key -> first-appearance code ---- */
165
+
166
+ typedef struct {
167
+ uint64_t *key;
168
+ int32_t *code;
169
+ uint8_t *used;
170
+ VALUE *val; /* object lane only: the interned VALUE, kept for an eql? re-check
171
+ on a hash collision (NULL in the numeric lane, where the widened
172
+ key is lossless so a key match already proves value equality).
173
+ Its VALUEs are always elements of the live receiver, so the GC
174
+ reaches them through `self`; this array is not separately marked. */
175
+ char *raw; /* fixlen lane only: cap*esz bytes, the interned element bytes kept
176
+ for a memcmp re-check on a hash collision (NULL otherwise). */
177
+ int esz; /* fixlen lane: element width in bytes (0 otherwise) */
178
+ ca_size_t cap; /* power of two */
179
+ ca_size_t n; /* distinct keys interned so far */
180
+ int shift; /* 64 - log2(cap); the home slot is the top log2(cap) bits */
181
+ } fz_hash;
182
+
183
+ /* Multiplicative hashing (Knuth, TAOCP vol. 3 sec. 6.4): multiply by
184
+ 2^64 / golden-ratio and take the TOP log2(cap) bits of the product -- the
185
+ high bits are where a multiply mixes best (the low bits carry little
186
+ entropy, which sparse keys such as small-integer float bit patterns expose).
187
+ The constant is a plain mathematical value = floor(2^64 / phi),
188
+ phi = (1 + sqrt 5) / 2. */
189
+ #define FZ_GOLDEN 0x9E3779B97F4A7C15ULL
190
+ #define FZ_HOME(h, key) ((ca_size_t) (((uint64_t)(key) * FZ_GOLDEN) >> (h)->shift))
191
+
192
+ static void
193
+ fz_hash_init (fz_hash *h)
194
+ {
195
+ h->cap = 1024;
196
+ h->shift = 64 - 10; /* log2(1024) = 10 */
197
+ h->n = 0;
198
+ h->key = ALLOC_N(uint64_t, h->cap);
199
+ h->code = ALLOC_N(int32_t, h->cap);
200
+ h->used = ALLOC_N(uint8_t, h->cap);
201
+ h->val = NULL; /* numeric lane: no VALUE re-check needed */
202
+ h->raw = NULL;
203
+ h->esz = 0;
204
+ MEMZERO(h->used, uint8_t, h->cap);
205
+ }
206
+
207
+ /* Object lane: the widened key is `rb_hash` (lossy), so a slot also stores the
208
+ interned VALUE for an `eql?` re-check on collision. Equality then matches
209
+ Ruby Hash exactly (`hash` + `eql?`), reproducing the object seen-set path. */
210
+ static void
211
+ fz_hash_init_obj (fz_hash *h)
212
+ {
213
+ fz_hash_init(h);
214
+ h->val = ALLOC_N(VALUE, h->cap);
215
+ }
216
+
217
+ /* Fixlen lane: the key is a byte-hash of the esz-wide element (lossy), so a slot
218
+ stores the element bytes for a `memcmp` re-check on collision. For fixlen
219
+ cells (uniform width, binary encoding) byte equality matches Ruby String
220
+ `eql?`, reproducing the Ruby Hash seen-set the fixlen path used. */
221
+ static void
222
+ fz_hash_init_mem (fz_hash *h, int esz)
223
+ {
224
+ fz_hash_init(h);
225
+ h->esz = esz;
226
+ h->raw = ALLOC_N(char, h->cap * esz);
227
+ }
228
+
229
+ static void
230
+ fz_hash_free (fz_hash *h)
231
+ {
232
+ if ( h->key ) { xfree(h->key); h->key = NULL; }
233
+ if ( h->code ) { xfree(h->code); h->code = NULL; }
234
+ if ( h->used ) { xfree(h->used); h->used = NULL; }
235
+ if ( h->val ) { xfree(h->val); h->val = NULL; }
236
+ if ( h->raw ) { xfree(h->raw); h->raw = NULL; }
237
+ }
238
+
239
+ /* FNV-1a over esz bytes: a lossy 64-bit key for the fixlen lane. */
240
+ static uint64_t
241
+ fz_bytehash (const char *b, int esz)
242
+ {
243
+ uint64_t x = 14695981039346656037ULL;
244
+ for ( int i = 0; i < esz; i++ ) {
245
+ x = (x ^ (unsigned char) b[i]) * 1099511628211ULL;
246
+ }
247
+ return x;
248
+ }
249
+
250
+ /* Clear all interned keys while keeping the allocated capacity, so a per-fiber
251
+ seen-set can be reused across fibers without reallocating each time. */
252
+ static void
253
+ fz_hash_reset (fz_hash *h)
254
+ {
255
+ h->n = 0;
256
+ MEMZERO(h->used, uint8_t, h->cap);
257
+ }
258
+
259
+ static void
260
+ fz_hash_grow (fz_hash *h)
261
+ {
262
+ ca_size_t oldcap = h->cap, newcap = oldcap << 1;
263
+ uint64_t *ok = h->key;
264
+ int32_t *oc = h->code;
265
+ uint8_t *ou = h->used;
266
+ VALUE *ov = h->val;
267
+ char *orw = h->raw;
268
+ int esz = h->esz;
269
+ h->cap = newcap;
270
+ h->shift -= 1; /* log2(cap) grew by one */
271
+ h->key = ALLOC_N(uint64_t, newcap);
272
+ h->code = ALLOC_N(int32_t, newcap);
273
+ h->used = ALLOC_N(uint8_t, newcap);
274
+ if ( ov ) { h->val = ALLOC_N(VALUE, newcap); }
275
+ if ( orw ) { h->raw = ALLOC_N(char, newcap * esz); }
276
+ MEMZERO(h->used, uint8_t, newcap);
277
+ ca_size_t mask = newcap - 1;
278
+ for ( ca_size_t s = 0; s < oldcap; s++ ) {
279
+ if ( ! ou[s] ) { continue; }
280
+ ca_size_t slot = FZ_HOME(h, ok[s]);
281
+ while ( h->used[slot] ) { slot = (slot + 1) & mask; }
282
+ h->used[slot] = 1;
283
+ h->key[slot] = ok[s];
284
+ h->code[slot] = oc[s];
285
+ if ( ov ) { h->val[slot] = ov[s]; }
286
+ if ( orw ) { memcpy(h->raw + slot * esz, orw + s * esz, esz); }
287
+ }
288
+ xfree(ok); xfree(oc); xfree(ou);
289
+ if ( ov ) { xfree(ov); }
290
+ if ( orw ) { xfree(orw); }
291
+ }
292
+
293
+ /* True when v is a Float holding NaN (any bit pattern). */
294
+ static inline int
295
+ fz_is_float_nan (VALUE v)
296
+ {
297
+ return RB_FLOAT_TYPE_P(v) && isnan(RFLOAT_VALUE(v));
298
+ }
299
+
300
+ /* Object-lane intern: key = rb_hash(v) (lossy), collision re-check via rb_eql.
301
+ Matches Ruby Hash distinctness (`hash` + `eql?`) element for element, with one
302
+ value-based exception aligning with the numeric lane: every Float NaN collapses
303
+ to a single distinct value (Ruby Hash keeps distinct NaN objects apart via
304
+ eql?, but the discovery family unifies them). A NaN takes a fixed canonical
305
+ key and matches any stored Float NaN, so its second and later occurrences fold
306
+ into the first. */
307
+ static int32_t
308
+ fz_hash_intern_obj (fz_hash *h, VALUE v, int *is_new)
309
+ {
310
+ if ( (h->n + 1) * 10 >= h->cap * 7 ) { fz_hash_grow(h); } /* load factor 0.7 */
311
+ int v_nan = fz_is_float_nan(v);
312
+ uint64_t key = v_nan ? 0x7ff8000000000000ULL
313
+ : (uint64_t) NUM2LL(rb_hash(v));
314
+ ca_size_t mask = h->cap - 1;
315
+ ca_size_t slot = FZ_HOME(h, key);
316
+ while ( h->used[slot] ) {
317
+ if ( h->key[slot] == key &&
318
+ ( v_nan ? fz_is_float_nan(h->val[slot]) : rb_eql(h->val[slot], v) ) ) {
319
+ *is_new = 0; return h->code[slot];
320
+ }
321
+ slot = (slot + 1) & mask;
322
+ }
323
+ int32_t code = (int32_t) h->n;
324
+ h->used[slot] = 1;
325
+ h->key[slot] = key;
326
+ h->val[slot] = v;
327
+ h->code[slot] = code;
328
+ h->n++;
329
+ *is_new = 1;
330
+ return code;
331
+ }
332
+
333
+ /* Fixlen-lane intern: key = byte-hash of the esz-wide element (lossy), collision
334
+ re-check via memcmp. Matches Ruby String eql? for uniform-width binary cells. */
335
+ static int32_t
336
+ fz_hash_intern_mem (fz_hash *h, const char *b, int *is_new)
337
+ {
338
+ if ( (h->n + 1) * 10 >= h->cap * 7 ) { fz_hash_grow(h); } /* load factor 0.7 */
339
+ int esz = h->esz;
340
+ uint64_t key = fz_bytehash(b, esz);
341
+ ca_size_t mask = h->cap - 1;
342
+ ca_size_t slot = FZ_HOME(h, key);
343
+ while ( h->used[slot] ) {
344
+ if ( h->key[slot] == key && memcmp(h->raw + slot * esz, b, esz) == 0 ) {
345
+ *is_new = 0; return h->code[slot];
346
+ }
347
+ slot = (slot + 1) & mask;
348
+ }
349
+ int32_t code = (int32_t) h->n;
350
+ h->used[slot] = 1;
351
+ h->key[slot] = key;
352
+ memcpy(h->raw + slot * esz, b, esz);
353
+ h->code[slot] = code;
354
+ h->n++;
355
+ *is_new = 1;
356
+ return code;
357
+ }
358
+
359
+ /* Intern `key`; return its code, set *is_new when first seen. */
360
+ static int32_t
361
+ fz_hash_intern (fz_hash *h, uint64_t key, int *is_new)
362
+ {
363
+ if ( (h->n + 1) * 10 >= h->cap * 7 ) { fz_hash_grow(h); } /* load factor 0.7 */
364
+ ca_size_t mask = h->cap - 1;
365
+ ca_size_t slot = FZ_HOME(h, key);
366
+ while ( h->used[slot] ) {
367
+ if ( h->key[slot] == key ) { *is_new = 0; return h->code[slot]; }
368
+ slot = (slot + 1) & mask;
369
+ }
370
+ int32_t code = (int32_t) h->n;
371
+ h->used[slot] = 1;
372
+ h->key[slot] = key;
373
+ h->code[slot] = code;
374
+ h->n++;
375
+ *is_new = 1;
376
+ return code;
377
+ }
378
+
379
+ /* ---- lookup-only probes: does the value exist in the seen-set? ------------
380
+ Each mirrors its intern sibling's search loop but never inserts, so a set
381
+ built once (by interning one array) can be probed by the other array's
382
+ elements. Return 1 when present, 0 when absent. The three lanes reuse the
383
+ same key derivation and collision re-check as intern, so membership matches
384
+ the discovery family's distinctness exactly.
385
+
386
+ code_out (nullable): when non-NULL and the value is present, the interned
387
+ dense code (appearance-order index intern wrote at that slot) is returned
388
+ through it. is_in passes NULL (membership only); the 3.1 locate / set-op
389
+ members read the code to map a hit to a flat address / distinct push. */
390
+
391
+ static int
392
+ fz_hash_lookup (fz_hash *h, uint64_t key, int32_t *code_out)
393
+ {
394
+ ca_size_t mask = h->cap - 1;
395
+ ca_size_t slot = FZ_HOME(h, key);
396
+ while ( h->used[slot] ) {
397
+ if ( h->key[slot] == key ) {
398
+ if ( code_out ) { *code_out = h->code[slot]; }
399
+ return 1;
400
+ }
401
+ slot = (slot + 1) & mask;
402
+ }
403
+ return 0;
404
+ }
405
+
406
+ static int
407
+ fz_hash_lookup_obj (fz_hash *h, VALUE v, int32_t *code_out)
408
+ {
409
+ int v_nan = fz_is_float_nan(v);
410
+ uint64_t key = v_nan ? 0x7ff8000000000000ULL
411
+ : (uint64_t) NUM2LL(rb_hash(v));
412
+ ca_size_t mask = h->cap - 1;
413
+ ca_size_t slot = FZ_HOME(h, key);
414
+ while ( h->used[slot] ) {
415
+ if ( h->key[slot] == key &&
416
+ ( v_nan ? fz_is_float_nan(h->val[slot]) : rb_eql(h->val[slot], v) ) ) {
417
+ if ( code_out ) { *code_out = h->code[slot]; }
418
+ return 1;
419
+ }
420
+ slot = (slot + 1) & mask;
421
+ }
422
+ return 0;
423
+ }
424
+
425
+ static int
426
+ fz_hash_lookup_mem (fz_hash *h, const char *b, int32_t *code_out)
427
+ {
428
+ int esz = h->esz;
429
+ uint64_t key = fz_bytehash(b, esz);
430
+ ca_size_t mask = h->cap - 1;
431
+ ca_size_t slot = FZ_HOME(h, key);
432
+ while ( h->used[slot] ) {
433
+ if ( h->key[slot] == key && memcmp(h->raw + slot * esz, b, esz) == 0 ) {
434
+ if ( code_out ) { *code_out = h->code[slot]; }
435
+ return 1;
436
+ }
437
+ slot = (slot + 1) & mask;
438
+ }
439
+ return 0;
440
+ }
441
+
442
+ /* ---- growable raw-element buffer: the levels in appearance order ---------- */
443
+
444
+ typedef struct {
445
+ char *p;
446
+ ca_size_t cap; /* capacity in elements */
447
+ ca_size_t n; /* elements stored */
448
+ int esz; /* element bytes */
449
+ } fz_levels;
450
+
451
+ static void
452
+ fz_levels_init (fz_levels *l, int esz)
453
+ {
454
+ l->esz = esz;
455
+ l->cap = 16;
456
+ l->n = 0;
457
+ l->p = ALLOC_N(char, l->cap * esz);
458
+ }
459
+
460
+ static void
461
+ fz_levels_push (fz_levels *l, const void *e)
462
+ {
463
+ if ( l->n == l->cap ) {
464
+ l->cap <<= 1;
465
+ REALLOC_N(l->p, char, l->cap * l->esz);
466
+ }
467
+ memcpy(l->p + l->n * l->esz, e, l->esz);
468
+ l->n++;
469
+ }
470
+
471
+ static void
472
+ fz_levels_free (fz_levels *l)
473
+ {
474
+ if ( l->p ) { xfree(l->p); l->p = NULL; }
475
+ }
476
+
477
+ /* @overload __factorize_appearance__
478
+
479
+ INTERNAL (categorize's discovery pass). Factorize self into dense codes in
480
+ first-appearance order, one linear pass, no sort. Accepts the integer, float,
481
+ object, and fixlen lanes (the numeric lane keys on the widened integer, the
482
+ float lane on the bitwise key with NaN collapsed to one value and -0.0 == +0.0,
483
+ the object lane on rb_hash + rb_eql, the fixlen lane on a byte-hash + memcmp),
484
+ matching the discovery family's lane coverage.
485
+
486
+ Returns [codes, levels]:
487
+ codes = narrow unsigned CArray; masked source cells store the type-max
488
+ sentinel (0xFF / 0xFFFF / 0xFFFFFFFF), so from_codes reconstructs
489
+ the mask exactly as the mask_duplicates path does.
490
+ levels = CArray (source dtype) of the k distinct values, in first-appearance
491
+ (row-major flatten) order.
492
+ */
493
+ static VALUE
494
+ rb_ca_factorize_appearance (VALUE self)
495
+ {
496
+ CArray *ca;
497
+ volatile VALUE face;
498
+ self = fz_face_descend(self, &face);
499
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
500
+
501
+ int8_t dt = ca->data_type;
502
+ switch ( dt ) {
503
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
504
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
505
+ case CA_FLOAT32: case CA_FLOAT64:
506
+ case CA_OBJECT: case CA_FIXLEN:
507
+ case CA_BOOLEAN:
508
+ break;
509
+ default:
510
+ rb_raise(rb_eCADataTypeError,
511
+ "__factorize_appearance__: integer, float, object, or fixlen dtype required (got %d)", dt);
512
+ }
513
+ if ( ca->ndim < 1 ) {
514
+ rb_raise(rb_eRuntimeError, "__factorize_appearance__: need ndim >= 1");
515
+ }
516
+
517
+ ca_size_t N = ca->elements;
518
+
519
+ /* uint32 code scratch, same shape as self; narrowed once k is known. */
520
+ VALUE vu32 = rb_carray_new(CA_UINT32, ca->ndim, ca->dim, 0, NULL);
521
+ CArray *cu32;
522
+ TypedData_Get_Struct(vu32, CArray, &carray_data_type, cu32);
523
+
524
+ fz_hash h;
525
+ fz_levels lv;
526
+ if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
527
+ else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
528
+ else { fz_hash_init(&h); }
529
+ fz_levels_init(&lv, (int) ca->bytes);
530
+
531
+ int8_t axis = (int8_t) (ca->ndim - 1); /* innermost fiber = row-major flatten */
532
+
533
+ ca_iter_state st_in, st_out;
534
+ char *p_in, *p_out;
535
+ boolean8_t *m;
536
+ ca_size_t n;
537
+
538
+ /* Fiber inner loop, monomorphised per dtype. WIDEN sign- or zero-extends the
539
+ element to a 64-bit key; equality within one dtype is preserved. */
540
+ #define FZ_LOOP(T, WIDEN) \
541
+ do { \
542
+ const T *ip = (const T *) p_in; \
543
+ uint32_t *op = (uint32_t *) p_out; \
544
+ for ( ca_size_t i = 0; i < n; i++ ) { \
545
+ if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
546
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
547
+ int is_new; \
548
+ int32_t code = fz_hash_intern(&h, key, &is_new); \
549
+ if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
550
+ op[i] = (uint32_t) code; \
551
+ } \
552
+ } while (0)
553
+
554
+ /* Float lane: reproduce `==` with two value-based exceptions matching the
555
+ discovery family -- all NaN collapse to one canonical key (v != v -> NANKEY),
556
+ and -0.0 == +0.0 (a zero normalizes to +0.0 before the bitwise key). The
557
+ level pushed is the raw first-seen element (an appearance-first NaN / -0.0 is
558
+ kept as-is), so masked source cells store the sentinel. */
559
+ #define FZ_LOOP_FLOAT(T, UINT, NANKEY) \
560
+ do { \
561
+ const T *ip = (const T *) p_in; \
562
+ uint32_t *op = (uint32_t *) p_out; \
563
+ for ( ca_size_t i = 0; i < n; i++ ) { \
564
+ if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
565
+ T v = ip[i]; \
566
+ uint64_t key; \
567
+ if ( v != v ) { key = (NANKEY); } \
568
+ else { \
569
+ if ( v == (T) 0 ) { v = (T) 0; } \
570
+ UINT bits; \
571
+ memcpy(&bits, &v, sizeof(bits)); \
572
+ key = (uint64_t) bits; \
573
+ } \
574
+ int is_new; \
575
+ int32_t code = fz_hash_intern(&h, key, &is_new); \
576
+ if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
577
+ op[i] = (uint32_t) code; \
578
+ } \
579
+ } while (0)
580
+
581
+ /* Object lane: intern the raw VALUE (rb_hash + rb_eql); the level pushed is the
582
+ first-seen VALUE, kept alive through self. Masked cells store the sentinel. */
583
+ #define FZ_LOOP_OBJ \
584
+ do { \
585
+ const VALUE *ip = (const VALUE *) p_in; \
586
+ uint32_t *op = (uint32_t *) p_out; \
587
+ for ( ca_size_t i = 0; i < n; i++ ) { \
588
+ if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
589
+ int is_new; \
590
+ int32_t code = fz_hash_intern_obj(&h, ip[i], &is_new); \
591
+ if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
592
+ op[i] = (uint32_t) code; \
593
+ } \
594
+ } while (0)
595
+
596
+ /* Fixlen lane: intern the esz-wide element bytes (byte-hash + memcmp); the
597
+ level pushed is a copy of the first-seen bytes. */
598
+ #define FZ_LOOP_MEM \
599
+ do { \
600
+ int esz = (int) ca->bytes; \
601
+ uint32_t *op = (uint32_t *) p_out; \
602
+ for ( ca_size_t i = 0; i < n; i++ ) { \
603
+ if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
604
+ const char *e = p_in + i * esz; \
605
+ int is_new; \
606
+ int32_t code = fz_hash_intern_mem(&h, e, &is_new); \
607
+ if ( is_new ) { fz_levels_push(&lv, e); } \
608
+ op[i] = (uint32_t) code; \
609
+ } \
610
+ } while (0)
611
+
612
+ CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cu32, axis,
613
+ CA_KERNEL_READ, p_in, p_out, n, m) {
614
+ switch ( dt ) {
615
+ case CA_INT8: FZ_LOOP(int8_t, (int64_t)); break;
616
+ case CA_INT16: FZ_LOOP(int16_t, (int64_t)); break;
617
+ case CA_INT32: FZ_LOOP(int32_t, (int64_t)); break;
618
+ case CA_INT64: FZ_LOOP(int64_t, (int64_t)); break;
619
+ case CA_BOOLEAN: case CA_UINT8: FZ_LOOP(uint8_t, (uint64_t)); break;
620
+ case CA_UINT16: FZ_LOOP(uint16_t, (uint64_t)); break;
621
+ case CA_UINT32: FZ_LOOP(uint32_t, (uint64_t)); break;
622
+ case CA_UINT64: FZ_LOOP(uint64_t, (uint64_t)); break;
623
+ case CA_FLOAT32: FZ_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
624
+ case CA_FLOAT64: FZ_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
625
+ case CA_OBJECT: FZ_LOOP_OBJ; break;
626
+ case CA_FIXLEN: FZ_LOOP_MEM; break;
627
+ }
628
+ }
629
+ #undef FZ_LOOP
630
+ #undef FZ_LOOP_FLOAT
631
+ #undef FZ_LOOP_OBJ
632
+ #undef FZ_LOOP_MEM
633
+
634
+ ca_size_t k = h.n;
635
+ fz_hash_free(&h);
636
+
637
+ /* Narrow the uint32 scratch to the smallest code storage that holds k codes
638
+ plus the type-max sentinel, mirroring categorize's width rule. */
639
+ int8_t code_dt;
640
+ if ( k <= 0xFF ) { code_dt = CA_UINT8; }
641
+ else if ( k <= 0xFFFF ) { code_dt = CA_UINT16; }
642
+ else { code_dt = CA_UINT32; }
643
+
644
+ VALUE vcodes;
645
+ if ( code_dt == CA_UINT32 ) {
646
+ vcodes = vu32; /* sentinel already 0xFFFFFFFF */
647
+ }
648
+ else {
649
+ vcodes = rb_carray_new(code_dt, ca->ndim, ca->dim, 0, NULL);
650
+ CArray *cco;
651
+ TypedData_Get_Struct(vcodes, CArray, &carray_data_type, cco);
652
+ const uint32_t *up = (const uint32_t *) cu32->ptr;
653
+ if ( code_dt == CA_UINT8 ) {
654
+ uint8_t *cp = (uint8_t *) cco->ptr;
655
+ for ( ca_size_t i = 0; i < N; i++ ) {
656
+ cp[i] = (up[i] == 0xFFFFFFFFu) ? (uint8_t) 0xFF : (uint8_t) up[i];
657
+ }
658
+ }
659
+ else {
660
+ uint16_t *cp = (uint16_t *) cco->ptr;
661
+ for ( ca_size_t i = 0; i < N; i++ ) {
662
+ cp[i] = (up[i] == 0xFFFFFFFFu) ? (uint16_t) 0xFFFF : (uint16_t) up[i];
663
+ }
664
+ }
665
+ }
666
+
667
+ /* Levels: the interned raw values, in source dtype. CA_FIXLEN carries its
668
+ element width; numeric / object use bytes = 0. */
669
+ ca_size_t ldim[1];
670
+ ldim[0] = k;
671
+ VALUE vlev = rb_carray_new(dt, 1, ldim, (dt == CA_FIXLEN) ? ca->bytes : 0, NULL);
672
+ CArray *clev;
673
+ TypedData_Get_Struct(vlev, CArray, &carray_data_type, clev);
674
+ if ( k > 0 ) {
675
+ memcpy(clev->ptr, lv.p, (size_t) k * (size_t) ca->bytes);
676
+ }
677
+ fz_levels_free(&lv);
678
+
679
+ /* codes stay plain; levels are *values* and become categorize's labels */
680
+ return rb_ary_new3(2, vcodes, fz_face_relift(vlev, face));
681
+ }
682
+
683
+ /* @overload __mask_duplicates__(axis)
684
+
685
+ INTERNAL (mask_duplicates). Mark each cell whose value duplicates an
686
+ earlier-seen one along axis, one linear pass per fiber, no sort.
687
+
688
+ Returns a boolean CArray of self.shape: true at every duplicate position
689
+ (the first occurrence of each distinct value stays false). The seen-set is
690
+ per-fiber independent along axis. Masked source cells do not participate in
691
+ duplicate judging and stay false (they remain masked via mask_where
692
+ downstream). Covers numeric / object / fixlen; boolean routes through the
693
+ uint8 numeric lane (storage is uint8 0/1, at most two distinct keys).
694
+
695
+ Numeric distinctness follows `==` with two value-based exceptions matching the
696
+ discovery family: all NaN collapse to one distinct value (so the second and
697
+ later NaN along the fiber are duplicates) and -0.0 == +0.0. Object keys on
698
+ rb_hash + rb_eql and fixlen on a byte-hash + memcmp, reproducing Ruby Hash
699
+ distinctness; the object lane additionally collapses every Float NaN to one
700
+ value (so the second and later NaN are duplicates, as in the numeric lane).
701
+ */
702
+ static VALUE
703
+ rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
704
+ {
705
+ CArray *ca;
706
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
707
+
708
+ int8_t dt = ca->data_type;
709
+ switch ( dt ) {
710
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
711
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
712
+ case CA_FLOAT32: case CA_FLOAT64:
713
+ case CA_OBJECT: case CA_FIXLEN:
714
+ case CA_BOOLEAN:
715
+ break;
716
+ default:
717
+ rb_raise(rb_eCADataTypeError,
718
+ "__mask_duplicates__: numeric, object, or fixlen dtype required (got %d)", dt);
719
+ }
720
+ if ( ca->ndim < 1 ) {
721
+ rb_raise(rb_eRuntimeError, "__mask_duplicates__: need ndim >= 1");
722
+ }
723
+
724
+ int axis = NUM2INT(vaxis);
725
+ if ( axis < 0 || axis >= ca->ndim ) {
726
+ rb_raise(rb_eArgError, "__mask_duplicates__: axis %d out of range", axis);
727
+ }
728
+
729
+ VALUE vout = rb_carray_new(CA_BOOLEAN, ca->ndim, ca->dim, 0, NULL);
730
+ CArray *cout;
731
+ TypedData_Get_Struct(vout, CArray, &carray_data_type, cout);
732
+
733
+ fz_hash h;
734
+ if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
735
+ else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
736
+ else { fz_hash_init(&h); }
737
+
738
+ ca_iter_state st_in, st_out;
739
+ char *p_in, *p_out;
740
+ boolean8_t *m;
741
+ ca_size_t n;
742
+
743
+ /* Fiber inner loop, monomorphised per dtype. WIDEN sign- or zero-extends the
744
+ element to a 64-bit key; equality within one dtype is preserved. The hash
745
+ interns first appearances, so is_new == 0 flags a duplicate. */
746
+ #define MD_LOOP(T, WIDEN) \
747
+ do { \
748
+ const T *ip = (const T *) p_in; \
749
+ boolean8_t *op = (boolean8_t *) p_out; \
750
+ for ( ca_size_t i = 0; i < n; i++ ) { \
751
+ if ( m && m[i] ) { op[i] = 0; continue; } \
752
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
753
+ int is_new; \
754
+ fz_hash_intern(&h, key, &is_new); \
755
+ op[i] = is_new ? 0 : 1; \
756
+ } \
757
+ } while (0)
758
+
759
+ /* Float variant: reproduce `==` with two value-based exceptions so the
760
+ distinct-value judgement matches uniq / the discovery family. All NaN
761
+ collapse to one canonical key (v != v -> NANKEY), so the second and later
762
+ NaN in a fiber are duplicates; -0.0 == +0.0, so a zero normalizes to +0.0
763
+ before the bitwise key. */
764
+ #define MD_LOOP_FLOAT(T, UINT, NANKEY) \
765
+ do { \
766
+ const T *ip = (const T *) p_in; \
767
+ boolean8_t *op = (boolean8_t *) p_out; \
768
+ for ( ca_size_t i = 0; i < n; i++ ) { \
769
+ if ( m && m[i] ) { op[i] = 0; continue; } \
770
+ T v = ip[i]; \
771
+ uint64_t key; \
772
+ if ( v != v ) { key = (NANKEY); } \
773
+ else { \
774
+ if ( v == (T) 0 ) { v = (T) 0; } \
775
+ UINT bits; \
776
+ memcpy(&bits, &v, sizeof(bits)); \
777
+ key = (uint64_t) bits; \
778
+ } \
779
+ int is_new; \
780
+ fz_hash_intern(&h, key, &is_new); \
781
+ op[i] = is_new ? 0 : 1; \
782
+ } \
783
+ } while (0)
784
+
785
+ /* Object: rb_hash + rb_eql lane, per-fiber seen-set (is_new == 0 -> dup). */
786
+ #define MD_LOOP_OBJ \
787
+ do { \
788
+ const VALUE *ip = (const VALUE *) p_in; \
789
+ boolean8_t *op = (boolean8_t *) p_out; \
790
+ for ( ca_size_t i = 0; i < n; i++ ) { \
791
+ if ( m && m[i] ) { op[i] = 0; continue; } \
792
+ int is_new; \
793
+ fz_hash_intern_obj(&h, ip[i], &is_new); \
794
+ op[i] = is_new ? 0 : 1; \
795
+ } \
796
+ } while (0)
797
+
798
+ /* Fixlen: byte-hash + memcmp lane, per-fiber seen-set. */
799
+ #define MD_LOOP_MEM \
800
+ do { \
801
+ boolean8_t *op = (boolean8_t *) p_out; \
802
+ int esz = (int) ca->bytes; \
803
+ for ( ca_size_t i = 0; i < n; i++ ) { \
804
+ if ( m && m[i] ) { op[i] = 0; continue; } \
805
+ int is_new; \
806
+ fz_hash_intern_mem(&h, p_in + i * esz, &is_new); \
807
+ op[i] = is_new ? 0 : 1; \
808
+ } \
809
+ } while (0)
810
+
811
+ CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, (int8_t) axis,
812
+ CA_KERNEL_READ, p_in, p_out, n, m) {
813
+ fz_hash_reset(&h); /* independent seen-set per fiber */
814
+ switch ( dt ) {
815
+ case CA_INT8: MD_LOOP(int8_t, (int64_t)); break;
816
+ case CA_INT16: MD_LOOP(int16_t, (int64_t)); break;
817
+ case CA_INT32: MD_LOOP(int32_t, (int64_t)); break;
818
+ case CA_INT64: MD_LOOP(int64_t, (int64_t)); break;
819
+ case CA_BOOLEAN: case CA_UINT8: MD_LOOP(uint8_t, (uint64_t)); break;
820
+ case CA_UINT16: MD_LOOP(uint16_t, (uint64_t)); break;
821
+ case CA_UINT32: MD_LOOP(uint32_t, (uint64_t)); break;
822
+ case CA_UINT64: MD_LOOP(uint64_t, (uint64_t)); break;
823
+ case CA_FLOAT32: MD_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
824
+ case CA_FLOAT64: MD_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
825
+ case CA_OBJECT: MD_LOOP_OBJ; break;
826
+ case CA_FIXLEN: MD_LOOP_MEM; break;
827
+ }
828
+ }
829
+ #undef MD_LOOP
830
+ #undef MD_LOOP_FLOAT
831
+ #undef MD_LOOP_OBJ
832
+ #undef MD_LOOP_MEM
833
+
834
+ fz_hash_free(&h);
835
+ return vout;
836
+ }
837
+
838
+ /* @overload __unique_flat__
839
+
840
+ INTERNAL (CArray#unique). Collect the distinct values of self in
841
+ first-appearance (row-major flatten) order, one linear pass, no sort. Returns
842
+ a 1-D CArray of source dtype. Masked cells do not participate.
843
+
844
+ Numeric (integer / float): distinctness follows `==` except NaN collapses to a
845
+ single distinct value (all NaN patterns map to one canonical hash key) and
846
+ -0.0 == +0.0 (a zero normalizes to +0.0 for the key). The emitted level value
847
+ is the first element seen for each key, so an appearance-first NaN or -0.0 is
848
+ preserved.
849
+
850
+ Object (CA_OBJECT): distinctness follows Ruby `hash` + `eql?` (the object lane
851
+ keys on rb_hash and re-checks with rb_eql), which already folds -0.0 / +0.0
852
+ together. It deviates from Ruby Hash in one way, aligning with the numeric
853
+ lane: every Float NaN collapses to one distinct value (a NaN takes a fixed
854
+ canonical key and matches any stored Float NaN), where Ruby Hash would keep
855
+ distinct NaN objects apart.
856
+
857
+ Fixlen (CA_FIXLEN): distinctness is byte equality (the fixlen lane keys on a
858
+ byte-hash and re-checks with memcmp), matching Ruby String eql? for the
859
+ uniform-width binary cells the fixlen seen-set used.
860
+ */
861
+ static VALUE
862
+ rb_ca_unique_flat (VALUE self)
863
+ {
864
+ CArray *ca;
865
+ volatile VALUE face;
866
+ self = fz_face_descend(self, &face);
867
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
868
+
869
+ int8_t dt = ca->data_type;
870
+ switch ( dt ) {
871
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
872
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
873
+ case CA_FLOAT32: case CA_FLOAT64:
874
+ case CA_OBJECT: case CA_FIXLEN:
875
+ case CA_BOOLEAN:
876
+ break;
877
+ default:
878
+ rb_raise(rb_eCADataTypeError,
879
+ "__unique_flat__: numeric, object, or fixlen dtype required (got %d)", dt);
880
+ }
881
+ if ( ca->ndim < 1 ) {
882
+ rb_raise(rb_eRuntimeError, "__unique_flat__: need ndim >= 1");
883
+ }
884
+
885
+ fz_hash h;
886
+ fz_levels lv;
887
+ if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
888
+ else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
889
+ else { fz_hash_init(&h); }
890
+ fz_levels_init(&lv, (int) ca->bytes);
891
+
892
+ int8_t axis = (int8_t) (ca->ndim - 1); /* innermost fiber; hash not reset =
893
+ one seen-set over the whole array */
894
+ ca_iter_state st_in;
895
+ char *p_in;
896
+ boolean8_t *m;
897
+ ca_size_t n;
898
+
899
+ #define UQ_LOOP(T, WIDEN) \
900
+ do { \
901
+ const T *ip = (const T *) p_in; \
902
+ for ( ca_size_t i = 0; i < n; i++ ) { \
903
+ if ( m && m[i] ) { continue; } \
904
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
905
+ int is_new; \
906
+ fz_hash_intern(&h, key, &is_new); \
907
+ if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
908
+ } \
909
+ } while (0)
910
+
911
+ /* Float: NaN collapses to one canonical key; -0.0 / +0.0 share a key. The
912
+ pushed level is the raw first-seen element. */
913
+ #define UQ_LOOP_FLOAT(T, UINT, NANKEY) \
914
+ do { \
915
+ const T *ip = (const T *) p_in; \
916
+ for ( ca_size_t i = 0; i < n; i++ ) { \
917
+ if ( m && m[i] ) { continue; } \
918
+ T v = ip[i]; \
919
+ uint64_t key; \
920
+ if ( v != v ) { key = (NANKEY); } \
921
+ else { \
922
+ if ( v == (T) 0 ) { v = (T) 0; } \
923
+ UINT bits; \
924
+ memcpy(&bits, &v, sizeof(bits)); \
925
+ key = (uint64_t) bits; \
926
+ } \
927
+ int is_new; \
928
+ fz_hash_intern(&h, key, &is_new); \
929
+ if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
930
+ } \
931
+ } while (0)
932
+
933
+ /* Object: intern the raw VALUE via the object lane (rb_hash + rb_eql); the
934
+ pushed level is the first-seen VALUE. The interned VALUEs stay alive through
935
+ `self` (they are its elements), so no separate GC registration is needed. */
936
+ #define UQ_LOOP_OBJ \
937
+ do { \
938
+ const VALUE *ip = (const VALUE *) p_in; \
939
+ for ( ca_size_t i = 0; i < n; i++ ) { \
940
+ if ( m && m[i] ) { continue; } \
941
+ int is_new; \
942
+ fz_hash_intern_obj(&h, ip[i], &is_new); \
943
+ if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
944
+ } \
945
+ } while (0)
946
+
947
+ /* Fixlen: intern the esz-wide element bytes via the fixlen lane; the pushed
948
+ level is a copy of the first-seen element bytes. */
949
+ #define UQ_LOOP_MEM \
950
+ do { \
951
+ int esz = (int) ca->bytes; \
952
+ for ( ca_size_t i = 0; i < n; i++ ) { \
953
+ if ( m && m[i] ) { continue; } \
954
+ const char *e = p_in + i * esz; \
955
+ int is_new; \
956
+ fz_hash_intern_mem(&h, e, &is_new); \
957
+ if ( is_new ) { fz_levels_push(&lv, e); } \
958
+ } \
959
+ } while (0)
960
+
961
+ CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
962
+ switch ( dt ) {
963
+ case CA_INT8: UQ_LOOP(int8_t, (int64_t)); break;
964
+ case CA_INT16: UQ_LOOP(int16_t, (int64_t)); break;
965
+ case CA_INT32: UQ_LOOP(int32_t, (int64_t)); break;
966
+ case CA_INT64: UQ_LOOP(int64_t, (int64_t)); break;
967
+ case CA_BOOLEAN: case CA_UINT8: UQ_LOOP(uint8_t, (uint64_t)); break;
968
+ case CA_UINT16: UQ_LOOP(uint16_t, (uint64_t)); break;
969
+ case CA_UINT32: UQ_LOOP(uint32_t, (uint64_t)); break;
970
+ case CA_UINT64: UQ_LOOP(uint64_t, (uint64_t)); break;
971
+ case CA_FLOAT32: UQ_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
972
+ case CA_FLOAT64: UQ_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
973
+ case CA_OBJECT: UQ_LOOP_OBJ; break;
974
+ case CA_FIXLEN: UQ_LOOP_MEM; break;
975
+ }
976
+ }
977
+ #undef UQ_LOOP
978
+ #undef UQ_LOOP_FLOAT
979
+ #undef UQ_LOOP_OBJ
980
+ #undef UQ_LOOP_MEM
981
+
982
+ ca_size_t k = h.n;
983
+ fz_hash_free(&h);
984
+
985
+ ca_size_t ldim[1];
986
+ ldim[0] = k;
987
+ /* CA_FIXLEN carries its element width; numeric / object use bytes = 0. */
988
+ VALUE vlev = rb_carray_new(dt, 1, ldim, (dt == CA_FIXLEN) ? ca->bytes : 0, NULL);
989
+ CArray *clev;
990
+ TypedData_Get_Struct(vlev, CArray, &carray_data_type, clev);
991
+ if ( k > 0 ) {
992
+ memcpy(clev->ptr, lv.p, (size_t) k * (size_t) ca->bytes);
993
+ }
994
+ fz_levels_free(&lv);
995
+
996
+ return fz_face_relift(vlev, face); /* distinct *values*: give the Face back */
997
+ }
998
+
999
+ /* Intern every non-masked cell of cv into h, one seen-set over the whole array
1000
+ (the hash is not reset between fibers). When lv is non-NULL, the first-seen
1001
+ raw element of each distinct value is pushed to it (appearance order), so the
1002
+ same pass builds both a probe set and the distinct-value list. Dispatches the
1003
+ three key lanes exactly as the discovery family: numeric widen with all NaN
1004
+ collapsed and -0.0 == +0.0, object rb_hash + rb_eql with Float NaN collapsed,
1005
+ fixlen byte-hash + memcmp. Used to build the probe set (is_in, set relations)
1006
+ and to accumulate distinct values (union). */
1007
+ static void
1008
+ fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
1009
+ {
1010
+ int8_t dt = cv->data_type;
1011
+ int8_t vaxis = (int8_t) (cv->ndim - 1);
1012
+ ca_iter_state st_v;
1013
+ char *p_v;
1014
+ boolean8_t *mv;
1015
+ ca_size_t nv;
1016
+
1017
+ #define FZ_IA(T, WIDEN) \
1018
+ do { \
1019
+ const T *ip = (const T *) p_v; \
1020
+ for ( ca_size_t i = 0; i < nv; i++ ) { \
1021
+ if ( mv && mv[i] ) { continue; } \
1022
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
1023
+ int is_new; \
1024
+ fz_hash_intern(h, key, &is_new); \
1025
+ if ( is_new && lv ) { fz_levels_push(lv, &ip[i]); } \
1026
+ } \
1027
+ } while (0)
1028
+
1029
+ #define FZ_IA_FLOAT(T, UINT, NANKEY) \
1030
+ do { \
1031
+ const T *ip = (const T *) p_v; \
1032
+ for ( ca_size_t i = 0; i < nv; i++ ) { \
1033
+ if ( mv && mv[i] ) { continue; } \
1034
+ T v = ip[i]; \
1035
+ uint64_t key; \
1036
+ if ( v != v ) { key = (NANKEY); } \
1037
+ else { \
1038
+ if ( v == (T) 0 ) { v = (T) 0; } \
1039
+ UINT bits; \
1040
+ memcpy(&bits, &v, sizeof(bits)); \
1041
+ key = (uint64_t) bits; \
1042
+ } \
1043
+ int is_new; \
1044
+ fz_hash_intern(h, key, &is_new); \
1045
+ if ( is_new && lv ) { fz_levels_push(lv, &ip[i]); } \
1046
+ } \
1047
+ } while (0)
1048
+
1049
+ #define FZ_IA_OBJ \
1050
+ do { \
1051
+ const VALUE *ip = (const VALUE *) p_v; \
1052
+ for ( ca_size_t i = 0; i < nv; i++ ) { \
1053
+ if ( mv && mv[i] ) { continue; } \
1054
+ int is_new; \
1055
+ fz_hash_intern_obj(h, ip[i], &is_new); \
1056
+ if ( is_new && lv ) { fz_levels_push(lv, &ip[i]); } \
1057
+ } \
1058
+ } while (0)
1059
+
1060
+ #define FZ_IA_MEM \
1061
+ do { \
1062
+ int esz = (int) cv->bytes; \
1063
+ for ( ca_size_t i = 0; i < nv; i++ ) { \
1064
+ if ( mv && mv[i] ) { continue; } \
1065
+ const char *e = p_v + i * esz; \
1066
+ int is_new; \
1067
+ fz_hash_intern_mem(h, e, &is_new); \
1068
+ if ( is_new && lv ) { fz_levels_push(lv, e); } \
1069
+ } \
1070
+ } while (0)
1071
+
1072
+ CA_FOR_EACH_FIBER_MASKED(st_v, cv, vaxis, CA_KERNEL_READ, p_v, nv, mv) {
1073
+ switch ( dt ) {
1074
+ case CA_INT8: FZ_IA(int8_t, (int64_t)); break;
1075
+ case CA_INT16: FZ_IA(int16_t, (int64_t)); break;
1076
+ case CA_INT32: FZ_IA(int32_t, (int64_t)); break;
1077
+ case CA_INT64: FZ_IA(int64_t, (int64_t)); break;
1078
+ case CA_BOOLEAN: case CA_UINT8: FZ_IA(uint8_t, (uint64_t)); break;
1079
+ case CA_UINT16: FZ_IA(uint16_t, (uint64_t)); break;
1080
+ case CA_UINT32: FZ_IA(uint32_t, (uint64_t)); break;
1081
+ case CA_UINT64: FZ_IA(uint64_t, (uint64_t)); break;
1082
+ case CA_FLOAT32: FZ_IA_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1083
+ case CA_FLOAT64: FZ_IA_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1084
+ case CA_OBJECT: FZ_IA_OBJ; break;
1085
+ case CA_FIXLEN: FZ_IA_MEM; break;
1086
+ }
1087
+ }
1088
+ #undef FZ_IA
1089
+ #undef FZ_IA_FLOAT
1090
+ #undef FZ_IA_OBJ
1091
+ #undef FZ_IA_MEM
1092
+ }
1093
+
1094
+ /* @overload __is_in__(values)
1095
+
1096
+ INTERNAL (CArray#is_in). Return a boolean CArray of self.shape, true at each
1097
+ cell whose value appears in the set `values` (any shape; flattened to one
1098
+ seen-set). One pass to build the set from `values`, one pass to probe self;
1099
+ no sort, peak memory O(distinct values).
1100
+
1101
+ `values` must be a CArray of the same dtype as self (the Ruby surface coerces
1102
+ Array / Range / other-dtype input first). Masked cells of `values` do not
1103
+ enter the set. Masked cells of self stay masked in the output (membership is
1104
+ unknown), reproducing the mask propagation of the retired `contains`
1105
+ (self.eq(v)); their boolean payload is false.
1106
+
1107
+ Distinctness follows the discovery family per lane: numeric `==` with all NaN
1108
+ collapsed and -0.0 == +0.0; object rb_hash + rb_eql with Float NaN collapsed;
1109
+ fixlen byte equality (byte-hash + memcmp).
1110
+ */
1111
+ static VALUE
1112
+ rb_ca_is_in (VALUE self, VALUE rvalues)
1113
+ {
1114
+ CArray *ca, *cv;
1115
+ volatile VALUE face;
1116
+ self = fz_face_descend(self, &face);
1117
+ rvalues = fz_face_reconcile(face, rvalues, "is_in");
1118
+ GetCArray(self, ca);
1119
+
1120
+ int8_t dt = ca->data_type;
1121
+ switch ( dt ) {
1122
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
1123
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
1124
+ case CA_FLOAT32: case CA_FLOAT64:
1125
+ case CA_OBJECT: case CA_FIXLEN:
1126
+ case CA_BOOLEAN:
1127
+ break;
1128
+ default:
1129
+ rb_raise(rb_eCADataTypeError,
1130
+ "__is_in__: numeric, object, or fixlen dtype required (got %d)", dt);
1131
+ }
1132
+ if ( ca->ndim < 1 ) {
1133
+ rb_raise(rb_eRuntimeError, "__is_in__: need ndim >= 1");
1134
+ }
1135
+
1136
+ if ( ! RTEST(rb_obj_is_kind_of(rvalues, rb_cCArray)) ) {
1137
+ rb_raise(rb_eArgError, "__is_in__: values must be a CArray");
1138
+ }
1139
+ GetCArray(rvalues, cv);
1140
+ if ( cv->data_type != dt || (dt == CA_FIXLEN && cv->bytes != ca->bytes) ) {
1141
+ rb_raise(rb_eCADataTypeError,
1142
+ "__is_in__: values dtype must match self (%d)", dt);
1143
+ }
1144
+ if ( cv->ndim < 1 ) {
1145
+ rb_raise(rb_eRuntimeError, "__is_in__: values need ndim >= 1");
1146
+ }
1147
+
1148
+ fz_hash h;
1149
+ if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
1150
+ else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
1151
+ else { fz_hash_init(&h); }
1152
+
1153
+ /* ---- Phase 1: build the seen-set from every non-masked cell of `values`. */
1154
+ fz_intern_all(&h, cv, NULL);
1155
+
1156
+ /* ---- Phase 2: probe every cell of self, writing the boolean membership. */
1157
+ VALUE vout = rb_carray_new(CA_BOOLEAN, ca->ndim, ca->dim, 0, NULL);
1158
+ CArray *cout;
1159
+ GetCArray(vout, cout);
1160
+ {
1161
+ ca_iter_state st_in, st_out;
1162
+ char *p_in, *p_out;
1163
+ boolean8_t *m;
1164
+ ca_size_t n;
1165
+ int8_t axis = (int8_t) (ca->ndim - 1);
1166
+
1167
+ #define IN_PROBE(T, WIDEN) \
1168
+ do { \
1169
+ const T *ip = (const T *) p_in; \
1170
+ boolean8_t *op = (boolean8_t *) p_out; \
1171
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1172
+ if ( m && m[i] ) { op[i] = 0; continue; } \
1173
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
1174
+ op[i] = (boolean8_t) fz_hash_lookup(&h, key, NULL); \
1175
+ } \
1176
+ } while (0)
1177
+
1178
+ #define IN_PROBE_FLOAT(T, UINT, NANKEY) \
1179
+ do { \
1180
+ const T *ip = (const T *) p_in; \
1181
+ boolean8_t *op = (boolean8_t *) p_out; \
1182
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1183
+ if ( m && m[i] ) { op[i] = 0; continue; } \
1184
+ T v = ip[i]; \
1185
+ uint64_t key; \
1186
+ if ( v != v ) { key = (NANKEY); } \
1187
+ else { \
1188
+ if ( v == (T) 0 ) { v = (T) 0; } \
1189
+ UINT bits; \
1190
+ memcpy(&bits, &v, sizeof(bits)); \
1191
+ key = (uint64_t) bits; \
1192
+ } \
1193
+ op[i] = (boolean8_t) fz_hash_lookup(&h, key, NULL); \
1194
+ } \
1195
+ } while (0)
1196
+
1197
+ #define IN_PROBE_OBJ \
1198
+ do { \
1199
+ const VALUE *ip = (const VALUE *) p_in; \
1200
+ boolean8_t *op = (boolean8_t *) p_out; \
1201
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1202
+ if ( m && m[i] ) { op[i] = 0; continue; } \
1203
+ op[i] = (boolean8_t) fz_hash_lookup_obj(&h, ip[i], NULL); \
1204
+ } \
1205
+ } while (0)
1206
+
1207
+ #define IN_PROBE_MEM \
1208
+ do { \
1209
+ boolean8_t *op = (boolean8_t *) p_out; \
1210
+ int esz = (int) ca->bytes; \
1211
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1212
+ if ( m && m[i] ) { op[i] = 0; continue; } \
1213
+ op[i] = (boolean8_t) fz_hash_lookup_mem(&h, p_in + i * esz, NULL); \
1214
+ } \
1215
+ } while (0)
1216
+
1217
+ CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, axis,
1218
+ CA_KERNEL_READ, p_in, p_out, n, m) {
1219
+ switch ( dt ) {
1220
+ case CA_INT8: IN_PROBE(int8_t, (int64_t)); break;
1221
+ case CA_INT16: IN_PROBE(int16_t, (int64_t)); break;
1222
+ case CA_INT32: IN_PROBE(int32_t, (int64_t)); break;
1223
+ case CA_INT64: IN_PROBE(int64_t, (int64_t)); break;
1224
+ case CA_BOOLEAN: case CA_UINT8: IN_PROBE(uint8_t, (uint64_t)); break;
1225
+ case CA_UINT16: IN_PROBE(uint16_t, (uint64_t)); break;
1226
+ case CA_UINT32: IN_PROBE(uint32_t, (uint64_t)); break;
1227
+ case CA_UINT64: IN_PROBE(uint64_t, (uint64_t)); break;
1228
+ case CA_FLOAT32: IN_PROBE_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1229
+ case CA_FLOAT64: IN_PROBE_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1230
+ case CA_OBJECT: IN_PROBE_OBJ; break;
1231
+ case CA_FIXLEN: IN_PROBE_MEM; break;
1232
+ }
1233
+ }
1234
+ #undef IN_PROBE
1235
+ #undef IN_PROBE_FLOAT
1236
+ #undef IN_PROBE_OBJ
1237
+ #undef IN_PROBE_MEM
1238
+ }
1239
+
1240
+ fz_hash_free(&h);
1241
+
1242
+ /* Masked self cells stay masked in the output (membership unknown), matching
1243
+ the retired contains (self.eq(v)) mask propagation. */
1244
+ if ( ca_has_mask(ca) ) { ca_copy_mask(cout, ca); }
1245
+
1246
+ return vout;
1247
+ }
1248
+
1249
+ /* @overload __locate_addr__(ref)
1250
+
1251
+ INTERNAL (CArray#locate_addr, exact hash lane). For each cell of self, the
1252
+ row-major flat address into `ref` where that value first occurs, or UNDEF
1253
+ where the value is absent from `ref`. Output is CA_INT64 of self.shape. One
1254
+ pass builds a value -> first-address map from `ref`, one pass probes self; no
1255
+ sort, peak memory O(distinct ref values). Numeric / object / fixlen all work
1256
+ through the discovery-family lanes (NaN collapse, rb_hash + rb_eql, byte
1257
+ equality).
1258
+
1259
+ `ref` must be a CArray of the same dtype as self (the Ruby surface coerces
1260
+ first). Masked cells of `ref` do not enter the map but still occupy their
1261
+ flat address (position counts). Masked cells of self, and cells whose value
1262
+ is absent from `ref`, are UNDEF in the output. "First" occurrence is
1263
+ appearance order (matching the discovery family); on a `ref` with duplicate
1264
+ values this is the earliest address, which can differ from the bsearch lane. */
1265
+ static VALUE
1266
+ rb_ca_locate_addr (VALUE self, VALUE rref)
1267
+ {
1268
+ CArray *ca, *cr;
1269
+ /* Here the *reference* is the argument (self is the query being placed on
1270
+ rref), so the gate runs the other way round: rref reconciles self. The
1271
+ output is an address, so nothing is lifted back. */
1272
+ {
1273
+ volatile VALUE ref_face;
1274
+ rref = fz_face_descend(rref, &ref_face);
1275
+ self = fz_face_reconcile(ref_face, self, "locate_addr");
1276
+ }
1277
+ GetCArray(self, ca);
1278
+
1279
+ int8_t dt = ca->data_type;
1280
+ switch ( dt ) {
1281
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
1282
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
1283
+ case CA_FLOAT32: case CA_FLOAT64:
1284
+ case CA_OBJECT: case CA_FIXLEN:
1285
+ case CA_BOOLEAN:
1286
+ break;
1287
+ default:
1288
+ rb_raise(rb_eCADataTypeError,
1289
+ "__locate_addr__: numeric, object, or fixlen dtype required (got %d)", dt);
1290
+ }
1291
+ if ( ca->ndim < 1 ) {
1292
+ rb_raise(rb_eRuntimeError, "__locate_addr__: need ndim >= 1");
1293
+ }
1294
+ if ( ! RTEST(rb_obj_is_kind_of(rref, rb_cCArray)) ) {
1295
+ rb_raise(rb_eArgError, "__locate_addr__: ref must be a CArray");
1296
+ }
1297
+ GetCArray(rref, cr);
1298
+ if ( cr->data_type != dt || (dt == CA_FIXLEN && cr->bytes != ca->bytes) ) {
1299
+ rb_raise(rb_eCADataTypeError,
1300
+ "__locate_addr__: ref dtype must match self (%d)", dt);
1301
+ }
1302
+ if ( cr->ndim < 1 ) {
1303
+ rb_raise(rb_eRuntimeError, "__locate_addr__: ref need ndim >= 1");
1304
+ }
1305
+
1306
+ fz_hash h;
1307
+ if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
1308
+ else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
1309
+ else { fz_hash_init(&h); }
1310
+
1311
+ /* addr.p[code] = flat address of the first (appearance-order) occurrence of
1312
+ the ref value carrying dense code `code`. Codes are assigned 0,1,2,... in
1313
+ insertion order, so pushing on is_new fills addr.p in code order. */
1314
+ fz_levels addr;
1315
+ fz_levels_init(&addr, (int) sizeof(int64_t));
1316
+
1317
+ /* ---- Phase 1: build value -> first-address map from ref (row-major). ----- */
1318
+ {
1319
+ ca_iter_state st_r;
1320
+ char *p_r;
1321
+ boolean8_t *mr;
1322
+ ca_size_t nr;
1323
+ int8_t raxis = (int8_t) (cr->ndim - 1);
1324
+ ca_size_t base = 0; /* row-major flat offset of the current fiber */
1325
+
1326
+ #define LOC_BUILD(T, WIDEN) \
1327
+ do { \
1328
+ const T *ip = (const T *) p_r; \
1329
+ for ( ca_size_t i = 0; i < nr; i++ ) { \
1330
+ if ( mr && mr[i] ) { continue; } \
1331
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
1332
+ int is_new; \
1333
+ fz_hash_intern(&h, key, &is_new); \
1334
+ if ( is_new ) { int64_t a = (int64_t) (base + i); \
1335
+ fz_levels_push(&addr, &a); } \
1336
+ } \
1337
+ } while (0)
1338
+
1339
+ #define LOC_BUILD_FLOAT(T, UINT, NANKEY) \
1340
+ do { \
1341
+ const T *ip = (const T *) p_r; \
1342
+ for ( ca_size_t i = 0; i < nr; i++ ) { \
1343
+ if ( mr && mr[i] ) { continue; } \
1344
+ T v = ip[i]; \
1345
+ uint64_t key; \
1346
+ if ( v != v ) { key = (NANKEY); } \
1347
+ else { \
1348
+ if ( v == (T) 0 ) { v = (T) 0; } \
1349
+ UINT bits; \
1350
+ memcpy(&bits, &v, sizeof(bits)); \
1351
+ key = (uint64_t) bits; \
1352
+ } \
1353
+ int is_new; \
1354
+ fz_hash_intern(&h, key, &is_new); \
1355
+ if ( is_new ) { int64_t a = (int64_t) (base + i); \
1356
+ fz_levels_push(&addr, &a); } \
1357
+ } \
1358
+ } while (0)
1359
+
1360
+ #define LOC_BUILD_OBJ \
1361
+ do { \
1362
+ const VALUE *ip = (const VALUE *) p_r; \
1363
+ for ( ca_size_t i = 0; i < nr; i++ ) { \
1364
+ if ( mr && mr[i] ) { continue; } \
1365
+ int is_new; \
1366
+ fz_hash_intern_obj(&h, ip[i], &is_new); \
1367
+ if ( is_new ) { int64_t a = (int64_t) (base + i); \
1368
+ fz_levels_push(&addr, &a); } \
1369
+ } \
1370
+ } while (0)
1371
+
1372
+ #define LOC_BUILD_MEM \
1373
+ do { \
1374
+ int esz = (int) cr->bytes; \
1375
+ for ( ca_size_t i = 0; i < nr; i++ ) { \
1376
+ if ( mr && mr[i] ) { continue; } \
1377
+ int is_new; \
1378
+ fz_hash_intern_mem(&h, p_r + i * esz, &is_new); \
1379
+ if ( is_new ) { int64_t a = (int64_t) (base + i); \
1380
+ fz_levels_push(&addr, &a); } \
1381
+ } \
1382
+ } while (0)
1383
+
1384
+ CA_FOR_EACH_FIBER_MASKED(st_r, cr, raxis, CA_KERNEL_READ, p_r, nr, mr) {
1385
+ switch ( dt ) {
1386
+ case CA_INT8: LOC_BUILD(int8_t, (int64_t)); break;
1387
+ case CA_INT16: LOC_BUILD(int16_t, (int64_t)); break;
1388
+ case CA_INT32: LOC_BUILD(int32_t, (int64_t)); break;
1389
+ case CA_INT64: LOC_BUILD(int64_t, (int64_t)); break;
1390
+ case CA_BOOLEAN: case CA_UINT8: LOC_BUILD(uint8_t, (uint64_t)); break;
1391
+ case CA_UINT16: LOC_BUILD(uint16_t, (uint64_t)); break;
1392
+ case CA_UINT32: LOC_BUILD(uint32_t, (uint64_t)); break;
1393
+ case CA_UINT64: LOC_BUILD(uint64_t, (uint64_t)); break;
1394
+ case CA_FLOAT32: LOC_BUILD_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1395
+ case CA_FLOAT64: LOC_BUILD_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1396
+ case CA_OBJECT: LOC_BUILD_OBJ; break;
1397
+ case CA_FIXLEN: LOC_BUILD_MEM; break;
1398
+ }
1399
+ base += nr;
1400
+ }
1401
+ #undef LOC_BUILD
1402
+ #undef LOC_BUILD_FLOAT
1403
+ #undef LOC_BUILD_OBJ
1404
+ #undef LOC_BUILD_MEM
1405
+ }
1406
+
1407
+ /* ---- Phase 2: probe every cell of self, writing the int64 address + UNDEF. */
1408
+ VALUE vout = rb_carray_new(CA_INT64, ca->ndim, ca->dim, 0, NULL);
1409
+ CArray *cout;
1410
+ GetCArray(vout, cout);
1411
+ int64_t *out = (int64_t *) cout->ptr;
1412
+ boolean8_t *um = ALLOC_N(boolean8_t, ca->elements); /* undef flags scratch */
1413
+ ca_size_t n_undef = 0;
1414
+ {
1415
+ ca_iter_state st_in;
1416
+ char *p_in;
1417
+ boolean8_t *m;
1418
+ ca_size_t n;
1419
+ int8_t axis = (int8_t) (ca->ndim - 1);
1420
+ ca_size_t base = 0;
1421
+
1422
+ #define LOC_PROBE(T, WIDEN) \
1423
+ do { \
1424
+ const T *ip = (const T *) p_in; \
1425
+ const int64_t *ab = (const int64_t *) addr.p; \
1426
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1427
+ ca_size_t o = base + i; \
1428
+ if ( m && m[i] ) { out[o] = 0; um[o] = 1; n_undef++; continue; } \
1429
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
1430
+ int32_t code; \
1431
+ if ( fz_hash_lookup(&h, key, &code) ) { out[o] = ab[code]; um[o] = 0; } \
1432
+ else { out[o] = 0; um[o] = 1; n_undef++; } \
1433
+ } \
1434
+ } while (0)
1435
+
1436
+ #define LOC_PROBE_FLOAT(T, UINT, NANKEY) \
1437
+ do { \
1438
+ const T *ip = (const T *) p_in; \
1439
+ const int64_t *ab = (const int64_t *) addr.p; \
1440
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1441
+ ca_size_t o = base + i; \
1442
+ if ( m && m[i] ) { out[o] = 0; um[o] = 1; n_undef++; continue; } \
1443
+ T v = ip[i]; \
1444
+ uint64_t key; \
1445
+ if ( v != v ) { key = (NANKEY); } \
1446
+ else { \
1447
+ if ( v == (T) 0 ) { v = (T) 0; } \
1448
+ UINT bits; \
1449
+ memcpy(&bits, &v, sizeof(bits)); \
1450
+ key = (uint64_t) bits; \
1451
+ } \
1452
+ int32_t code; \
1453
+ if ( fz_hash_lookup(&h, key, &code) ) { out[o] = ab[code]; um[o] = 0; } \
1454
+ else { out[o] = 0; um[o] = 1; n_undef++; } \
1455
+ } \
1456
+ } while (0)
1457
+
1458
+ #define LOC_PROBE_OBJ \
1459
+ do { \
1460
+ const VALUE *ip = (const VALUE *) p_in; \
1461
+ const int64_t *ab = (const int64_t *) addr.p; \
1462
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1463
+ ca_size_t o = base + i; \
1464
+ if ( m && m[i] ) { out[o] = 0; um[o] = 1; n_undef++; continue; } \
1465
+ int32_t code; \
1466
+ if ( fz_hash_lookup_obj(&h, ip[i], &code) ) { out[o] = ab[code]; um[o] = 0; } \
1467
+ else { out[o] = 0; um[o] = 1; n_undef++; } \
1468
+ } \
1469
+ } while (0)
1470
+
1471
+ #define LOC_PROBE_MEM \
1472
+ do { \
1473
+ const int64_t *ab = (const int64_t *) addr.p; \
1474
+ int esz = (int) ca->bytes; \
1475
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1476
+ ca_size_t o = base + i; \
1477
+ if ( m && m[i] ) { out[o] = 0; um[o] = 1; n_undef++; continue; } \
1478
+ int32_t code; \
1479
+ if ( fz_hash_lookup_mem(&h, p_in + i * esz, &code) ) { out[o] = ab[code]; um[o] = 0; } \
1480
+ else { out[o] = 0; um[o] = 1; n_undef++; } \
1481
+ } \
1482
+ } while (0)
1483
+
1484
+ CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1485
+ switch ( dt ) {
1486
+ case CA_INT8: LOC_PROBE(int8_t, (int64_t)); break;
1487
+ case CA_INT16: LOC_PROBE(int16_t, (int64_t)); break;
1488
+ case CA_INT32: LOC_PROBE(int32_t, (int64_t)); break;
1489
+ case CA_INT64: LOC_PROBE(int64_t, (int64_t)); break;
1490
+ case CA_BOOLEAN: case CA_UINT8: LOC_PROBE(uint8_t, (uint64_t)); break;
1491
+ case CA_UINT16: LOC_PROBE(uint16_t, (uint64_t)); break;
1492
+ case CA_UINT32: LOC_PROBE(uint32_t, (uint64_t)); break;
1493
+ case CA_UINT64: LOC_PROBE(uint64_t, (uint64_t)); break;
1494
+ case CA_FLOAT32: LOC_PROBE_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1495
+ case CA_FLOAT64: LOC_PROBE_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1496
+ case CA_OBJECT: LOC_PROBE_OBJ; break;
1497
+ case CA_FIXLEN: LOC_PROBE_MEM; break;
1498
+ }
1499
+ base += n;
1500
+ }
1501
+ #undef LOC_PROBE
1502
+ #undef LOC_PROBE_FLOAT
1503
+ #undef LOC_PROBE_OBJ
1504
+ #undef LOC_PROBE_MEM
1505
+ }
1506
+
1507
+ fz_hash_free(&h);
1508
+ fz_levels_free(&addr);
1509
+
1510
+ /* Attach an output mask only when some cell is UNDEF (miss or masked self), so
1511
+ an all-hit locate stays mask-free. */
1512
+ if ( n_undef > 0 ) {
1513
+ ca_create_mask(cout);
1514
+ memcpy(cout->mask->ptr, um, (size_t) ca->elements);
1515
+ }
1516
+ xfree(um);
1517
+
1518
+ return vout;
1519
+ }
1520
+
1521
+ /* Shared body of __intersection__ (keep_when_hit = 1) and __difference__
1522
+ (keep_when_hit = 0): the distinct values of self that are (resp. are not)
1523
+ present in `other`, in self's first-appearance order, as a 1-D CArray of
1524
+ self's dtype. Two seen-sets: `hoth` built from `other` is the probe set;
1525
+ `hself` dedups self so each distinct value is decided once. Masked cells of
1526
+ either array do not participate. Distinctness is the discovery family's per
1527
+ lane (numeric `==` + NaN collapse + -0.0 == +0.0, object hash/eql? + NaN
1528
+ collapse, fixlen byte equality). */
1529
+ static VALUE
1530
+ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1531
+ {
1532
+ CArray *ca, *co;
1533
+ volatile VALUE face;
1534
+ self = fz_face_descend(self, &face);
1535
+ rother = fz_face_reconcile(face, rother, "set relation");
1536
+ GetCArray(self, ca);
1537
+
1538
+ int8_t dt = ca->data_type;
1539
+ switch ( dt ) {
1540
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
1541
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
1542
+ case CA_FLOAT32: case CA_FLOAT64:
1543
+ case CA_OBJECT: case CA_FIXLEN:
1544
+ case CA_BOOLEAN:
1545
+ break;
1546
+ default:
1547
+ rb_raise(rb_eCADataTypeError,
1548
+ "set relation: numeric, object, or fixlen dtype required (got %d)", dt);
1549
+ }
1550
+ if ( ca->ndim < 1 ) {
1551
+ rb_raise(rb_eRuntimeError, "set relation: need ndim >= 1");
1552
+ }
1553
+ if ( ! RTEST(rb_obj_is_kind_of(rother, rb_cCArray)) ) {
1554
+ rb_raise(rb_eArgError, "set relation: other must be a CArray");
1555
+ }
1556
+ GetCArray(rother, co);
1557
+ if ( co->data_type != dt || (dt == CA_FIXLEN && co->bytes != ca->bytes) ) {
1558
+ rb_raise(rb_eCADataTypeError, "set relation: other dtype must match self (%d)", dt);
1559
+ }
1560
+ if ( co->ndim < 1 ) {
1561
+ rb_raise(rb_eRuntimeError, "set relation: other need ndim >= 1");
1562
+ }
1563
+
1564
+ fz_hash hoth, hself;
1565
+ if ( dt == CA_OBJECT ) {
1566
+ fz_hash_init_obj(&hoth); fz_hash_init_obj(&hself);
1567
+ }
1568
+ else if ( dt == CA_FIXLEN ) {
1569
+ fz_hash_init_mem(&hoth, (int) ca->bytes); fz_hash_init_mem(&hself, (int) ca->bytes);
1570
+ }
1571
+ else {
1572
+ fz_hash_init(&hoth); fz_hash_init(&hself);
1573
+ }
1574
+
1575
+ fz_levels lv;
1576
+ fz_levels_init(&lv, (int) ca->bytes);
1577
+
1578
+ /* Build the probe set from every non-masked cell of other. */
1579
+ fz_intern_all(&hoth, co, NULL);
1580
+
1581
+ /* Walk self: hself dedups, so each distinct self value is decided once; keep
1582
+ it when its membership in other equals keep_when_hit. */
1583
+ int8_t axis = (int8_t) (ca->ndim - 1);
1584
+ ca_iter_state st;
1585
+ char *p;
1586
+ boolean8_t *m;
1587
+ ca_size_t n;
1588
+
1589
+ #define SR(T, WIDEN) \
1590
+ do { \
1591
+ const T *ip = (const T *) p; \
1592
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1593
+ if ( m && m[i] ) { continue; } \
1594
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
1595
+ int is_new; \
1596
+ fz_hash_intern(&hself, key, &is_new); \
1597
+ if ( is_new && fz_hash_lookup(&hoth, key, NULL) == keep_when_hit ) { \
1598
+ fz_levels_push(&lv, &ip[i]); \
1599
+ } \
1600
+ } \
1601
+ } while (0)
1602
+
1603
+ #define SR_FLOAT(T, UINT, NANKEY) \
1604
+ do { \
1605
+ const T *ip = (const T *) p; \
1606
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1607
+ if ( m && m[i] ) { continue; } \
1608
+ T v = ip[i]; \
1609
+ uint64_t key; \
1610
+ if ( v != v ) { key = (NANKEY); } \
1611
+ else { \
1612
+ if ( v == (T) 0 ) { v = (T) 0; } \
1613
+ UINT bits; \
1614
+ memcpy(&bits, &v, sizeof(bits)); \
1615
+ key = (uint64_t) bits; \
1616
+ } \
1617
+ int is_new; \
1618
+ fz_hash_intern(&hself, key, &is_new); \
1619
+ if ( is_new && fz_hash_lookup(&hoth, key, NULL) == keep_when_hit ) { \
1620
+ fz_levels_push(&lv, &ip[i]); \
1621
+ } \
1622
+ } \
1623
+ } while (0)
1624
+
1625
+ #define SR_OBJ \
1626
+ do { \
1627
+ const VALUE *ip = (const VALUE *) p; \
1628
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1629
+ if ( m && m[i] ) { continue; } \
1630
+ int is_new; \
1631
+ fz_hash_intern_obj(&hself, ip[i], &is_new); \
1632
+ if ( is_new && fz_hash_lookup_obj(&hoth, ip[i], NULL) == keep_when_hit ) { \
1633
+ fz_levels_push(&lv, &ip[i]); \
1634
+ } \
1635
+ } \
1636
+ } while (0)
1637
+
1638
+ #define SR_MEM \
1639
+ do { \
1640
+ int esz = (int) ca->bytes; \
1641
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1642
+ if ( m && m[i] ) { continue; } \
1643
+ const char *e = p + i * esz; \
1644
+ int is_new; \
1645
+ fz_hash_intern_mem(&hself, e, &is_new); \
1646
+ if ( is_new && fz_hash_lookup_mem(&hoth, e, NULL) == keep_when_hit ) { \
1647
+ fz_levels_push(&lv, e); \
1648
+ } \
1649
+ } \
1650
+ } while (0)
1651
+
1652
+ CA_FOR_EACH_FIBER_MASKED(st, ca, axis, CA_KERNEL_READ, p, n, m) {
1653
+ switch ( dt ) {
1654
+ case CA_INT8: SR(int8_t, (int64_t)); break;
1655
+ case CA_INT16: SR(int16_t, (int64_t)); break;
1656
+ case CA_INT32: SR(int32_t, (int64_t)); break;
1657
+ case CA_INT64: SR(int64_t, (int64_t)); break;
1658
+ case CA_BOOLEAN: case CA_UINT8: SR(uint8_t, (uint64_t)); break;
1659
+ case CA_UINT16: SR(uint16_t, (uint64_t)); break;
1660
+ case CA_UINT32: SR(uint32_t, (uint64_t)); break;
1661
+ case CA_UINT64: SR(uint64_t, (uint64_t)); break;
1662
+ case CA_FLOAT32: SR_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1663
+ case CA_FLOAT64: SR_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1664
+ case CA_OBJECT: SR_OBJ; break;
1665
+ case CA_FIXLEN: SR_MEM; break;
1666
+ }
1667
+ }
1668
+ #undef SR
1669
+ #undef SR_FLOAT
1670
+ #undef SR_OBJ
1671
+ #undef SR_MEM
1672
+
1673
+ ca_size_t k = lv.n;
1674
+ fz_hash_free(&hoth);
1675
+ fz_hash_free(&hself);
1676
+
1677
+ ca_size_t ldim[1];
1678
+ ldim[0] = k;
1679
+ VALUE vlev = rb_carray_new(dt, 1, ldim, (dt == CA_FIXLEN) ? ca->bytes : 0, NULL);
1680
+ CArray *clev;
1681
+ GetCArray(vlev, clev);
1682
+ if ( k > 0 ) {
1683
+ memcpy(clev->ptr, lv.p, (size_t) k * (size_t) ca->bytes);
1684
+ }
1685
+ fz_levels_free(&lv);
1686
+
1687
+ return fz_face_relift(vlev, face); /* set *values*: give the Face back */
1688
+ }
1689
+
1690
+ /* @overload __intersection__(other)
1691
+ INTERNAL (CArray#intersection). 1-D CArray of the distinct values present in
1692
+ both self and other, in self's first-appearance order. */
1693
+ static VALUE
1694
+ rb_ca_intersection (VALUE self, VALUE rother)
1695
+ {
1696
+ return fz_set_relation(self, rother, 1);
1697
+ }
1698
+
1699
+ /* @overload __difference__(other)
1700
+ INTERNAL (CArray#difference). 1-D CArray of the distinct values in self that
1701
+ are absent from other, in self's first-appearance order. */
1702
+ static VALUE
1703
+ rb_ca_difference (VALUE self, VALUE rother)
1704
+ {
1705
+ return fz_set_relation(self, rother, 0);
1706
+ }
1707
+
1708
+ /* @overload __union__(other)
1709
+ INTERNAL (CArray#union). 1-D CArray of the distinct values appearing in either
1710
+ self or other, in self-then-other first-appearance order. One seen-set spans
1711
+ both arrays; each distinct value is pushed on its first appearance (self's
1712
+ distinct values first, then other's not-yet-seen ones). Masked cells of
1713
+ either array do not participate. */
1714
+ static VALUE
1715
+ rb_ca_set_union (VALUE self, VALUE rother)
1716
+ {
1717
+ CArray *ca, *co;
1718
+ volatile VALUE face;
1719
+ self = fz_face_descend(self, &face);
1720
+ rother = fz_face_reconcile(face, rother, "union");
1721
+ GetCArray(self, ca);
1722
+
1723
+ int8_t dt = ca->data_type;
1724
+ switch ( dt ) {
1725
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
1726
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
1727
+ case CA_FLOAT32: case CA_FLOAT64:
1728
+ case CA_OBJECT: case CA_FIXLEN:
1729
+ case CA_BOOLEAN:
1730
+ break;
1731
+ default:
1732
+ rb_raise(rb_eCADataTypeError,
1733
+ "__union__: numeric, object, or fixlen dtype required (got %d)", dt);
1734
+ }
1735
+ if ( ca->ndim < 1 ) {
1736
+ rb_raise(rb_eRuntimeError, "__union__: need ndim >= 1");
1737
+ }
1738
+ if ( ! RTEST(rb_obj_is_kind_of(rother, rb_cCArray)) ) {
1739
+ rb_raise(rb_eArgError, "__union__: other must be a CArray");
1740
+ }
1741
+ GetCArray(rother, co);
1742
+ if ( co->data_type != dt || (dt == CA_FIXLEN && co->bytes != ca->bytes) ) {
1743
+ rb_raise(rb_eCADataTypeError, "__union__: other dtype must match self (%d)", dt);
1744
+ }
1745
+ if ( co->ndim < 1 ) {
1746
+ rb_raise(rb_eRuntimeError, "__union__: other need ndim >= 1");
1747
+ }
1748
+
1749
+ fz_hash h;
1750
+ if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
1751
+ else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
1752
+ else { fz_hash_init(&h); }
1753
+
1754
+ fz_levels lv;
1755
+ fz_levels_init(&lv, (int) ca->bytes);
1756
+
1757
+ fz_intern_all(&h, ca, &lv); /* self's distinct values, appearance order */
1758
+ fz_intern_all(&h, co, &lv); /* + other's not-yet-seen distinct values */
1759
+
1760
+ ca_size_t k = lv.n;
1761
+ fz_hash_free(&h);
1762
+
1763
+ ca_size_t ldim[1];
1764
+ ldim[0] = k;
1765
+ VALUE vlev = rb_carray_new(dt, 1, ldim, (dt == CA_FIXLEN) ? ca->bytes : 0, NULL);
1766
+ CArray *clev;
1767
+ GetCArray(vlev, clev);
1768
+ if ( k > 0 ) {
1769
+ memcpy(clev->ptr, lv.p, (size_t) k * (size_t) ca->bytes);
1770
+ }
1771
+ fz_levels_free(&lv);
1772
+
1773
+ return fz_face_relift(vlev, face); /* set *values*: give the Face back */
1774
+ }
1775
+
1776
+ /* @overload __value_counts_flat__
1777
+
1778
+ INTERNAL (CArray#value_counts). Collect the distinct values of self in
1779
+ first-appearance (row-major flatten) order together with the number of times
1780
+ each occurs, one linear pass, no sort.
1781
+ Returns [levels, counts]:
1782
+ levels = 1-D CArray of source dtype, the k distinct values in appearance
1783
+ order (identical to __unique_flat__).
1784
+ counts = 1-D CA_INT64 of length k, counts[i] = occurrences of levels[i].
1785
+ Masked cells do not participate (skipped, not counted). Numeric distinctness
1786
+ follows the discovery family: all NaN collapse to one distinct value (their
1787
+ counts add up) and -0.0 == +0.0. Object keys on rb_hash + rb_eql and fixlen on
1788
+ a byte-hash + memcmp, reproducing Ruby Hash distinctness; the object lane also
1789
+ collapses every Float NaN to one value (their counts add up, as numeric does).
1790
+ */
1791
+ static VALUE
1792
+ rb_ca_value_counts_flat (VALUE self)
1793
+ {
1794
+ CArray *ca;
1795
+ volatile VALUE face;
1796
+ self = fz_face_descend(self, &face);
1797
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1798
+
1799
+ int8_t dt = ca->data_type;
1800
+ switch ( dt ) {
1801
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
1802
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
1803
+ case CA_FLOAT32: case CA_FLOAT64:
1804
+ case CA_OBJECT: case CA_FIXLEN:
1805
+ case CA_BOOLEAN:
1806
+ break;
1807
+ default:
1808
+ rb_raise(rb_eCADataTypeError,
1809
+ "__value_counts_flat__: numeric, object, or fixlen dtype required (got %d)", dt);
1810
+ }
1811
+ if ( ca->ndim < 1 ) {
1812
+ rb_raise(rb_eRuntimeError, "__value_counts_flat__: need ndim >= 1");
1813
+ }
1814
+
1815
+ fz_hash h;
1816
+ fz_levels lv; /* distinct values, appearance order */
1817
+ fz_levels ct; /* int64 count per code (code == push index) */
1818
+ if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
1819
+ else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
1820
+ else { fz_hash_init(&h); }
1821
+ fz_levels_init(&lv, (int) ca->bytes);
1822
+ fz_levels_init(&ct, (int) sizeof(int64_t));
1823
+
1824
+ int8_t axis = (int8_t) (ca->ndim - 1); /* one seen-set over the whole array */
1825
+ ca_iter_state st_in;
1826
+ char *p_in;
1827
+ boolean8_t *m;
1828
+ ca_size_t n;
1829
+
1830
+ #define VC_LOOP(T, WIDEN) \
1831
+ do { \
1832
+ const T *ip = (const T *) p_in; \
1833
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1834
+ if ( m && m[i] ) { continue; } \
1835
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
1836
+ int is_new; \
1837
+ int32_t code = fz_hash_intern(&h, key, &is_new); \
1838
+ if ( is_new ) { \
1839
+ int64_t one = 1; \
1840
+ fz_levels_push(&lv, &ip[i]); \
1841
+ fz_levels_push(&ct, &one); \
1842
+ } else { \
1843
+ ((int64_t *) ct.p)[code]++; \
1844
+ } \
1845
+ } \
1846
+ } while (0)
1847
+
1848
+ #define VC_LOOP_FLOAT(T, UINT, NANKEY) \
1849
+ do { \
1850
+ const T *ip = (const T *) p_in; \
1851
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1852
+ if ( m && m[i] ) { continue; } \
1853
+ T v = ip[i]; \
1854
+ uint64_t key; \
1855
+ if ( v != v ) { key = (NANKEY); } \
1856
+ else { \
1857
+ if ( v == (T) 0 ) { v = (T) 0; } \
1858
+ UINT bits; \
1859
+ memcpy(&bits, &v, sizeof(bits)); \
1860
+ key = (uint64_t) bits; \
1861
+ } \
1862
+ int is_new; \
1863
+ int32_t code = fz_hash_intern(&h, key, &is_new); \
1864
+ if ( is_new ) { \
1865
+ int64_t one = 1; \
1866
+ fz_levels_push(&lv, &ip[i]); \
1867
+ fz_levels_push(&ct, &one); \
1868
+ } else { \
1869
+ ((int64_t *) ct.p)[code]++; \
1870
+ } \
1871
+ } \
1872
+ } while (0)
1873
+
1874
+ /* Object: rb_hash + rb_eql lane; the level is the first-seen VALUE. */
1875
+ #define VC_LOOP_OBJ \
1876
+ do { \
1877
+ const VALUE *ip = (const VALUE *) p_in; \
1878
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1879
+ if ( m && m[i] ) { continue; } \
1880
+ int is_new; \
1881
+ int32_t code = fz_hash_intern_obj(&h, ip[i], &is_new); \
1882
+ if ( is_new ) { \
1883
+ int64_t one = 1; \
1884
+ fz_levels_push(&lv, &ip[i]); \
1885
+ fz_levels_push(&ct, &one); \
1886
+ } else { \
1887
+ ((int64_t *) ct.p)[code]++; \
1888
+ } \
1889
+ } \
1890
+ } while (0)
1891
+
1892
+ /* Fixlen: byte-hash + memcmp lane; the level is the first-seen element bytes. */
1893
+ #define VC_LOOP_MEM \
1894
+ do { \
1895
+ int esz = (int) ca->bytes; \
1896
+ for ( ca_size_t i = 0; i < n; i++ ) { \
1897
+ if ( m && m[i] ) { continue; } \
1898
+ const char *e = p_in + i * esz; \
1899
+ int is_new; \
1900
+ int32_t code = fz_hash_intern_mem(&h, e, &is_new); \
1901
+ if ( is_new ) { \
1902
+ int64_t one = 1; \
1903
+ fz_levels_push(&lv, e); \
1904
+ fz_levels_push(&ct, &one); \
1905
+ } else { \
1906
+ ((int64_t *) ct.p)[code]++; \
1907
+ } \
1908
+ } \
1909
+ } while (0)
1910
+
1911
+ CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1912
+ switch ( dt ) {
1913
+ case CA_INT8: VC_LOOP(int8_t, (int64_t)); break;
1914
+ case CA_INT16: VC_LOOP(int16_t, (int64_t)); break;
1915
+ case CA_INT32: VC_LOOP(int32_t, (int64_t)); break;
1916
+ case CA_INT64: VC_LOOP(int64_t, (int64_t)); break;
1917
+ case CA_BOOLEAN: case CA_UINT8: VC_LOOP(uint8_t, (uint64_t)); break;
1918
+ case CA_UINT16: VC_LOOP(uint16_t, (uint64_t)); break;
1919
+ case CA_UINT32: VC_LOOP(uint32_t, (uint64_t)); break;
1920
+ case CA_UINT64: VC_LOOP(uint64_t, (uint64_t)); break;
1921
+ case CA_FLOAT32: VC_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
1922
+ case CA_FLOAT64: VC_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
1923
+ case CA_OBJECT: VC_LOOP_OBJ; break;
1924
+ case CA_FIXLEN: VC_LOOP_MEM; break;
1925
+ }
1926
+ }
1927
+ #undef VC_LOOP
1928
+ #undef VC_LOOP_FLOAT
1929
+ #undef VC_LOOP_OBJ
1930
+ #undef VC_LOOP_MEM
1931
+
1932
+ ca_size_t k = h.n;
1933
+ fz_hash_free(&h);
1934
+
1935
+ ca_size_t ldim[1];
1936
+ ldim[0] = k;
1937
+ /* CA_FIXLEN carries its element width; numeric / object use bytes = 0. */
1938
+ VALUE vlev = rb_carray_new(dt, 1, ldim, (dt == CA_FIXLEN) ? ca->bytes : 0, NULL);
1939
+ CArray *clev;
1940
+ TypedData_Get_Struct(vlev, CArray, &carray_data_type, clev);
1941
+ VALUE vcnt = rb_carray_new(CA_INT64, 1, ldim, 0, NULL);
1942
+ CArray *ccnt;
1943
+ TypedData_Get_Struct(vcnt, CArray, &carray_data_type, ccnt);
1944
+ if ( k > 0 ) {
1945
+ memcpy(clev->ptr, lv.p, (size_t) k * (size_t) ca->bytes);
1946
+ memcpy(ccnt->ptr, ct.p, (size_t) k * sizeof(int64_t));
1947
+ }
1948
+ fz_levels_free(&lv);
1949
+ fz_levels_free(&ct);
1950
+
1951
+ /* values carry the Face, counts stay plain int64 */
1952
+ return rb_ary_new3(2, fz_face_relift(vlev, face), vcnt);
1953
+ }
1954
+
1955
+ /* @overload __nunique__(axis, keep_axis)
1956
+
1957
+ INTERNAL (CArray#nunique). Count the distinct values along axis, one linear
1958
+ pass per fiber with an independent seen-set. Returns a reduced
1959
+ CA_INT64 CArray of self.shape with axis removed (or kept as length-1 when
1960
+ keep_axis). Masked cells do not participate; an all-masked fiber counts 0
1961
+ (an empty set has zero distinct values, not UNDEF -- nunique has identity 0).
1962
+ Numeric distinctness collapses all NaN to one value and treats -0.0 == +0.0;
1963
+ object keys on rb_hash + rb_eql and fixlen on a byte-hash + memcmp, reproducing
1964
+ Ruby Hash distinctness; the object lane also collapses every Float NaN to one.
1965
+ */
1966
+ static VALUE
1967
+ rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
1968
+ {
1969
+ CArray *ca;
1970
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1971
+
1972
+ int8_t dt = ca->data_type;
1973
+ switch ( dt ) {
1974
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
1975
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
1976
+ case CA_FLOAT32: case CA_FLOAT64:
1977
+ case CA_OBJECT: case CA_FIXLEN:
1978
+ case CA_BOOLEAN:
1979
+ break;
1980
+ default:
1981
+ rb_raise(rb_eCADataTypeError,
1982
+ "__nunique__: numeric, object, or fixlen dtype required (got %d)", dt);
1983
+ }
1984
+ if ( ca->ndim < 1 ) {
1985
+ rb_raise(rb_eRuntimeError, "__nunique__: need ndim >= 1");
1986
+ }
1987
+
1988
+ int axis = NUM2INT(vaxis);
1989
+ if ( axis < 0 || axis >= ca->ndim ) {
1990
+ rb_raise(rb_eArgError, "__nunique__: axis %d out of range", axis);
1991
+ }
1992
+ int keep_axis = RTEST(vkeep);
1993
+
1994
+ int8_t ax = (int8_t) axis;
1995
+ VALUE vout = rb_ca_new_reduced(self, &ax, 1, CA_INT64, keep_axis);
1996
+ CArray *cout;
1997
+ TypedData_Get_Struct(vout, CArray, &carray_data_type, cout);
1998
+ int64_t *op = (int64_t *) cout->ptr;
1999
+
2000
+ fz_hash h;
2001
+ if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
2002
+ else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
2003
+ else { fz_hash_init(&h); }
2004
+
2005
+ ca_iter_state st;
2006
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, &ax, 1, 0);
2007
+ if ( rc != CA_ITER_OK ) {
2008
+ fz_hash_free(&h);
2009
+ rb_raise(rb_eRuntimeError, "__nunique__: kernel_iterator init failed rc=%d", rc);
2010
+ }
2011
+
2012
+ char *p;
2013
+ boolean8_t *m;
2014
+ ca_size_t out_i = 0;
2015
+ ca_size_t dummy; /* CA_SLAB_REDUCE_T needs an acc lvalue; unused here */
2016
+
2017
+ /* Intern each element into the per-fiber hash; the accumulator is a no-op.
2018
+ WIDEN sign- or zero-extends the integer element; float normalizes NaN and
2019
+ -0.0 before the bitwise key. */
2020
+ #define NU_INT(WIDEN) \
2021
+ do { int _n; fz_hash_intern(&h, (uint64_t) ((WIDEN) v), &_n); } while (0)
2022
+ #define NU_FLOAT(T, UINT, NANKEY) \
2023
+ do { \
2024
+ T _v = v; \
2025
+ uint64_t _key; \
2026
+ if ( _v != _v ) { _key = (NANKEY); } \
2027
+ else { \
2028
+ if ( _v == (T) 0 ) { _v = (T) 0; } \
2029
+ UINT _b; \
2030
+ memcpy(&_b, &_v, sizeof(_b)); \
2031
+ _key = (uint64_t) _b; \
2032
+ } \
2033
+ int _n; fz_hash_intern(&h, _key, &_n); \
2034
+ } while (0)
2035
+ /* Object: intern the VALUE via the object lane (v is bound by the macro). */
2036
+ #define NU_OBJ \
2037
+ do { int _n; fz_hash_intern_obj(&h, v, &_n); } while (0)
2038
+
2039
+ /* Fixlen has no scalar element type for CA_SLAB_REDUCE_T, so walk the slab by
2040
+ multi-index (order-independent -- nunique only counts distinct) and intern
2041
+ each esz-wide element via the fixlen lane. */
2042
+ #define NU_MEM_WALK \
2043
+ do { \
2044
+ int8_t K = st.slab_ndim; \
2045
+ ca_size_t idx[CA_RANK_MAX] = { 0 }; \
2046
+ ca_size_t total = st.slab_elements; \
2047
+ for ( ca_size_t e = 0; e < total; e++ ) { \
2048
+ ca_size_t doff = 0, moff = 0; \
2049
+ for ( int8_t kk = 0; kk < K; kk++ ) { \
2050
+ doff += idx[kk] * st.slab_strides[kk]; \
2051
+ moff += idx[kk] * st.slab_mask_strides[kk]; \
2052
+ } \
2053
+ if ( ! (m && m[moff]) ) { \
2054
+ int _n; fz_hash_intern_mem(&h, p + doff, &_n); \
2055
+ } \
2056
+ for ( int8_t kk = (int8_t)(K - 1); kk >= 0; kk-- ) { \
2057
+ if ( ++idx[kk] < st.slab_dims[kk] ) break; \
2058
+ idx[kk] = 0; \
2059
+ } \
2060
+ } \
2061
+ } while (0)
2062
+
2063
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2064
+ fz_hash_reset(&h); /* independent seen-set per fiber */
2065
+ switch ( dt ) {
2066
+ case CA_INT8: CA_SLAB_REDUCE_T(int8_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2067
+ case CA_INT16: CA_SLAB_REDUCE_T(int16_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2068
+ case CA_INT32: CA_SLAB_REDUCE_T(int32_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2069
+ case CA_INT64: CA_SLAB_REDUCE_T(int64_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
2070
+ case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2071
+ case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2072
+ case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2073
+ case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
2074
+ case CA_FLOAT32:
2075
+ CA_SLAB_REDUCE_T(float, st, p, m, dummy, 0, NU_FLOAT(float, uint32_t, 0x7fc00000ULL));
2076
+ break;
2077
+ case CA_FLOAT64:
2078
+ CA_SLAB_REDUCE_T(double, st, p, m, dummy, 0, NU_FLOAT(double, uint64_t, 0x7ff8000000000000ULL));
2079
+ break;
2080
+ case CA_OBJECT: CA_SLAB_REDUCE_T(VALUE, st, p, m, dummy, 0, NU_OBJ); break;
2081
+ case CA_FIXLEN: NU_MEM_WALK; break;
2082
+ }
2083
+ op[out_i++] = (int64_t) h.n;
2084
+ }
2085
+ #undef NU_INT
2086
+ #undef NU_FLOAT
2087
+ #undef NU_OBJ
2088
+ #undef NU_MEM_WALK
2089
+ (void) dummy;
2090
+
2091
+ ca_iter_state_finish(&st);
2092
+ fz_hash_free(&h);
2093
+ return vout;
2094
+ }
2095
+
2096
+ /* @overload __is_mode__(axis)
2097
+
2098
+ INTERNAL (CArray#is_mode). Mark each cell that holds a modal value along axis
2099
+ -- a value whose per-fiber occurrence count equals the fiber's maximum count.
2100
+ Two passes per fiber: pass one builds the frequency table (the fz_hash count
2101
+ lane) and finds the max count; pass two marks every cell whose value's count
2102
+ equals it, so ties are all marked (no tie-break). Returns a boolean CArray of
2103
+ self.shape.
2104
+
2105
+ Masked cells do not participate (excluded from the counts) and are marked
2106
+ false. An empty / all-masked fiber has max count 0 and marks every cell false
2107
+ (is_mode has no identity; it never raises). Numeric distinctness collapses all
2108
+ NaN to one value and treats -0.0 == +0.0, matching the discovery family;
2109
+ object keys on rb_hash + rb_eql and fixlen on a byte-hash + memcmp, reproducing
2110
+ Ruby Hash distinctness; the object lane also collapses every Float NaN to one.
2111
+ Callers pass flat input by flattening first (a single fiber over axis 0).
2112
+ */
2113
+ static VALUE
2114
+ rb_ca_is_mode (VALUE self, VALUE vaxis)
2115
+ {
2116
+ CArray *ca;
2117
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2118
+
2119
+ int8_t dt = ca->data_type;
2120
+ switch ( dt ) {
2121
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
2122
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
2123
+ case CA_FLOAT32: case CA_FLOAT64:
2124
+ case CA_OBJECT: case CA_FIXLEN:
2125
+ case CA_BOOLEAN:
2126
+ break;
2127
+ default:
2128
+ rb_raise(rb_eCADataTypeError,
2129
+ "__is_mode__: numeric, object, or fixlen dtype required (got %d)", dt);
2130
+ }
2131
+ if ( ca->ndim < 1 ) {
2132
+ rb_raise(rb_eRuntimeError, "__is_mode__: need ndim >= 1");
2133
+ }
2134
+ int axis = NUM2INT(vaxis);
2135
+ if ( axis < 0 || axis >= ca->ndim ) {
2136
+ rb_raise(rb_eArgError, "__is_mode__: axis %d out of range", axis);
2137
+ }
2138
+
2139
+ VALUE vout = rb_carray_new(CA_BOOLEAN, ca->ndim, ca->dim, 0, NULL);
2140
+ CArray *cout;
2141
+ TypedData_Get_Struct(vout, CArray, &carray_data_type, cout);
2142
+
2143
+ fz_hash h;
2144
+ fz_levels ct; /* int64 count per code (code == push index), reset per fiber */
2145
+ if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
2146
+ else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
2147
+ else { fz_hash_init(&h); }
2148
+ fz_levels_init(&ct, (int) sizeof(int64_t));
2149
+
2150
+ ca_iter_state st_in, st_out;
2151
+ char *p_in, *p_out;
2152
+ boolean8_t *m;
2153
+ ca_size_t n;
2154
+
2155
+ /* Integer: WIDEN gives the key directly. Two passes over the fiber. */
2156
+ #define IM_BODY(T, WIDEN) \
2157
+ do { \
2158
+ const T *ip = (const T *) p_in; \
2159
+ boolean8_t *op = (boolean8_t *) p_out; \
2160
+ for ( ca_size_t i = 0; i < n; i++ ) { \
2161
+ if ( m && m[i] ) { continue; } \
2162
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
2163
+ int is_new; \
2164
+ int32_t code = fz_hash_intern(&h, key, &is_new); \
2165
+ if ( is_new ) { int64_t one = 1; fz_levels_push(&ct, &one); } \
2166
+ else { ((int64_t *) ct.p)[code]++; } \
2167
+ } \
2168
+ int64_t mx = 0; \
2169
+ for ( ca_size_t c = 0; c < h.n; c++ ) { \
2170
+ int64_t cc = ((int64_t *) ct.p)[c]; \
2171
+ if ( cc > mx ) { mx = cc; } \
2172
+ } \
2173
+ for ( ca_size_t i = 0; i < n; i++ ) { \
2174
+ if ( m && m[i] ) { op[i] = 0; continue; } \
2175
+ uint64_t key = (uint64_t) (WIDEN ip[i]); \
2176
+ int is_new; \
2177
+ int32_t code = fz_hash_intern(&h, key, &is_new); \
2178
+ op[i] = (((int64_t *) ct.p)[code] == mx) ? 1 : 0; \
2179
+ } \
2180
+ } while (0)
2181
+
2182
+ /* Float: NaN collapses to one canonical key; -0.0 / +0.0 share a key. */
2183
+ #define IM_BODY_FLOAT(T, UINT, NANKEY) \
2184
+ do { \
2185
+ const T *ip = (const T *) p_in; \
2186
+ boolean8_t *op = (boolean8_t *) p_out; \
2187
+ for ( int pass = 0; pass < 2; pass++ ) { \
2188
+ int64_t mx = 0; \
2189
+ if ( pass == 1 ) { \
2190
+ for ( ca_size_t c = 0; c < h.n; c++ ) { \
2191
+ int64_t cc = ((int64_t *) ct.p)[c]; \
2192
+ if ( cc > mx ) { mx = cc; } \
2193
+ } \
2194
+ } \
2195
+ for ( ca_size_t i = 0; i < n; i++ ) { \
2196
+ if ( m && m[i] ) { if ( pass == 1 ) { op[i] = 0; } continue; } \
2197
+ T v = ip[i]; \
2198
+ uint64_t key; \
2199
+ if ( v != v ) { key = (NANKEY); } \
2200
+ else { \
2201
+ if ( v == (T) 0 ) { v = (T) 0; } \
2202
+ UINT bits; \
2203
+ memcpy(&bits, &v, sizeof(bits)); \
2204
+ key = (uint64_t) bits; \
2205
+ } \
2206
+ int is_new; \
2207
+ int32_t code = fz_hash_intern(&h, key, &is_new); \
2208
+ if ( pass == 0 ) { \
2209
+ if ( is_new ) { int64_t one = 1; fz_levels_push(&ct, &one); } \
2210
+ else { ((int64_t *) ct.p)[code]++; } \
2211
+ } \
2212
+ else { op[i] = (((int64_t *) ct.p)[code] == mx) ? 1 : 0; } \
2213
+ } \
2214
+ } \
2215
+ } while (0)
2216
+
2217
+ /* Object: rb_hash + rb_eql lane. Pass one builds the count lane, pass two
2218
+ marks cells whose value's count ties the fiber max (re-intern returns the
2219
+ existing code without inserting). */
2220
+ #define IM_BODY_OBJ \
2221
+ do { \
2222
+ const VALUE *ip = (const VALUE *) p_in; \
2223
+ boolean8_t *op = (boolean8_t *) p_out; \
2224
+ for ( ca_size_t i = 0; i < n; i++ ) { \
2225
+ if ( m && m[i] ) { continue; } \
2226
+ int is_new; \
2227
+ int32_t code = fz_hash_intern_obj(&h, ip[i], &is_new); \
2228
+ if ( is_new ) { int64_t one = 1; fz_levels_push(&ct, &one); } \
2229
+ else { ((int64_t *) ct.p)[code]++; } \
2230
+ } \
2231
+ int64_t mx = 0; \
2232
+ for ( ca_size_t c = 0; c < h.n; c++ ) { \
2233
+ int64_t cc = ((int64_t *) ct.p)[c]; \
2234
+ if ( cc > mx ) { mx = cc; } \
2235
+ } \
2236
+ for ( ca_size_t i = 0; i < n; i++ ) { \
2237
+ if ( m && m[i] ) { op[i] = 0; continue; } \
2238
+ int is_new; \
2239
+ int32_t code = fz_hash_intern_obj(&h, ip[i], &is_new); \
2240
+ op[i] = (((int64_t *) ct.p)[code] == mx) ? 1 : 0; \
2241
+ } \
2242
+ } while (0)
2243
+
2244
+ /* Fixlen: byte-hash + memcmp lane, same two-pass structure. */
2245
+ #define IM_BODY_MEM \
2246
+ do { \
2247
+ boolean8_t *op = (boolean8_t *) p_out; \
2248
+ int esz = (int) ca->bytes; \
2249
+ for ( ca_size_t i = 0; i < n; i++ ) { \
2250
+ if ( m && m[i] ) { continue; } \
2251
+ int is_new; \
2252
+ int32_t code = fz_hash_intern_mem(&h, p_in + i * esz, &is_new); \
2253
+ if ( is_new ) { int64_t one = 1; fz_levels_push(&ct, &one); } \
2254
+ else { ((int64_t *) ct.p)[code]++; } \
2255
+ } \
2256
+ int64_t mx = 0; \
2257
+ for ( ca_size_t c = 0; c < h.n; c++ ) { \
2258
+ int64_t cc = ((int64_t *) ct.p)[c]; \
2259
+ if ( cc > mx ) { mx = cc; } \
2260
+ } \
2261
+ for ( ca_size_t i = 0; i < n; i++ ) { \
2262
+ if ( m && m[i] ) { op[i] = 0; continue; } \
2263
+ int is_new; \
2264
+ int32_t code = fz_hash_intern_mem(&h, p_in + i * esz, &is_new); \
2265
+ op[i] = (((int64_t *) ct.p)[code] == mx) ? 1 : 0; \
2266
+ } \
2267
+ } while (0)
2268
+
2269
+ CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, (int8_t) axis,
2270
+ CA_KERNEL_READ, p_in, p_out, n, m) {
2271
+ fz_hash_reset(&h); /* independent frequency table per fiber */
2272
+ ct.n = 0;
2273
+ switch ( dt ) {
2274
+ case CA_INT8: IM_BODY(int8_t, (int64_t)); break;
2275
+ case CA_INT16: IM_BODY(int16_t, (int64_t)); break;
2276
+ case CA_INT32: IM_BODY(int32_t, (int64_t)); break;
2277
+ case CA_INT64: IM_BODY(int64_t, (int64_t)); break;
2278
+ case CA_BOOLEAN: case CA_UINT8: IM_BODY(uint8_t, (uint64_t)); break;
2279
+ case CA_UINT16: IM_BODY(uint16_t, (uint64_t)); break;
2280
+ case CA_UINT32: IM_BODY(uint32_t, (uint64_t)); break;
2281
+ case CA_UINT64: IM_BODY(uint64_t, (uint64_t)); break;
2282
+ case CA_FLOAT32: IM_BODY_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
2283
+ case CA_FLOAT64: IM_BODY_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
2284
+ case CA_OBJECT: IM_BODY_OBJ; break;
2285
+ case CA_FIXLEN: IM_BODY_MEM; break;
2286
+ }
2287
+ }
2288
+ #undef IM_BODY
2289
+ #undef IM_BODY_FLOAT
2290
+ #undef IM_BODY_OBJ
2291
+ #undef IM_BODY_MEM
2292
+
2293
+ fz_hash_free(&h);
2294
+ fz_levels_free(&ct);
2295
+ return vout;
2296
+ }
2297
+
2298
+ /* Ascending comparison for the modal-value sort, NaN ordered last so a
2299
+ collapsed NaN key (at most one per fiber) trails the real values, matching
2300
+ CArray#sort. */
2301
+ static int mode_gt_f32 (float a, float b) { if ( a != a ) return b == b; if ( b != b ) return 0; return a > b; }
2302
+ static int mode_gt_f64 (double a, double b) { if ( a != a ) return b == b; if ( b != b ) return 0; return a > b; }
2303
+ #define MODE_GTI(a, b) ((a) > (b))
2304
+
2305
+ /* @overload __mode_axis__(axis)
2306
+
2307
+ INTERNAL (CArray#mode's numeric per-axis path). Emit the distinct modal
2308
+ values along axis, per fiber, ascending, as an Array of reduced CArrays --
2309
+ the ragged value-form consumer of the per-fiber frequency table (see
2310
+ __is_mode__, which marks the modal cells; this reads out the values).
2311
+
2312
+ Two passes over the family substrate per fiber: pass one builds the
2313
+ frequency table (fz_hash + count lane + first-seen level buffer) and finds
2314
+ the fiber's max count; the modal values are the distinct values whose count
2315
+ equals it, sorted ascending. The ragged per-fiber lists are collected into
2316
+ one flat buffer, then redistributed into K reduced CArrays where K is the
2317
+ widest fiber's modal count: element j holds each fiber's j-th smallest modal
2318
+ value, masked (UNDEF) where a fiber has fewer than j+1 modes -- the same
2319
+ Array<CArray> shape as per-axis quantile. Stack them along axis to get the
2320
+ rectangular mask-padded form.
2321
+
2322
+ The result Array's row-major cell order per reduced CArray matches the fiber
2323
+ walk (self.shape with axis removed). An all-masked array (no modes anywhere,
2324
+ K == 0) yields an empty Array. Masked cells are excluded from the counts. A
2325
+ 1-D input reduces to length-1 reduced CArrays; the Ruby surface unwraps them
2326
+ to scalars. Float distinctness collapses all NaN to one value (sorted last)
2327
+ and treats -0.0 == +0.0 (the first-seen raw value is emitted, so -0.0 keeps
2328
+ its sign), matching the discovery family.
2329
+ */
2330
+ static VALUE
2331
+ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2332
+ {
2333
+ CArray *ca;
2334
+ volatile VALUE face;
2335
+ self = fz_face_descend(self, &face);
2336
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2337
+
2338
+ int8_t dt = ca->data_type;
2339
+ switch ( dt ) {
2340
+ case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
2341
+ case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
2342
+ case CA_FLOAT32: case CA_FLOAT64:
2343
+ case CA_BOOLEAN:
2344
+ break;
2345
+ default:
2346
+ rb_raise(rb_eCADataTypeError,
2347
+ "__mode_axis__: numeric dtype required (got %d)", dt);
2348
+ }
2349
+ if ( ca->ndim < 1 ) {
2350
+ rb_raise(rb_eRuntimeError, "__mode_axis__: need ndim >= 1");
2351
+ }
2352
+ int axis = NUM2INT(vaxis);
2353
+ if ( axis < 0 || axis >= ca->ndim ) {
2354
+ rb_raise(rb_eArgError, "__mode_axis__: axis %d out of range", axis);
2355
+ }
2356
+
2357
+ int esz = (int) ca->bytes;
2358
+
2359
+ /* Number of fibers = self.elements with axis dropped = reduced cell count. */
2360
+ ca_size_t M = 1;
2361
+ for ( int d = 0; d < ca->ndim; d++ ) { if ( d != axis ) M *= ca->dim[d]; }
2362
+
2363
+ fz_hash h;
2364
+ fz_levels lv; /* first-seen raw values, appearance order (reset per fiber) */
2365
+ fz_levels ct; /* int64 count per code (code == push index, reset per fiber) */
2366
+ fz_levels mod; /* per-fiber modal values, collected then sorted (reset) */
2367
+ fz_levels flat; /* all fibers' modal values concatenated, fiber order */
2368
+ fz_hash_init(&h);
2369
+ fz_levels_init(&lv, esz);
2370
+ fz_levels_init(&ct, (int) sizeof(int64_t));
2371
+ fz_levels_init(&mod, esz);
2372
+ fz_levels_init(&flat, esz);
2373
+
2374
+ ca_size_t *foff = ALLOC_N(ca_size_t, M + 1); /* prefix offsets into flat */
2375
+ foff[0] = 0;
2376
+
2377
+ int8_t ax = (int8_t) axis;
2378
+ ca_iter_state st;
2379
+ int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, &ax, 1, 0);
2380
+ if ( rc != CA_ITER_OK ) {
2381
+ fz_hash_free(&h); fz_levels_free(&lv); fz_levels_free(&ct);
2382
+ fz_levels_free(&mod); fz_levels_free(&flat); xfree(foff);
2383
+ rb_raise(rb_eRuntimeError, "__mode_axis__: kernel_iterator init failed rc=%d", rc);
2384
+ }
2385
+
2386
+ char *p;
2387
+ boolean8_t *m;
2388
+ ca_size_t out_i = 0;
2389
+ ca_size_t Kmax = 0;
2390
+ ca_size_t dummy; /* CA_SLAB_REDUCE_T needs an acc lvalue; unused here */
2391
+
2392
+ /* Pass-one body: intern each value, growing the count lane and first-seen
2393
+ level buffer (code == push index). WIDEN keys the integer element; float
2394
+ normalizes NaN and -0.0 before the bitwise key but pushes the raw value. */
2395
+ #define MB_INT(WIDEN) \
2396
+ do { \
2397
+ uint64_t key = (uint64_t) ((WIDEN) v); \
2398
+ int is_new; int32_t code = fz_hash_intern(&h, key, &is_new); \
2399
+ if ( is_new ) { int64_t one = 1; fz_levels_push(&lv, &v); \
2400
+ fz_levels_push(&ct, &one); } \
2401
+ else { ((int64_t *) ct.p)[code]++; } \
2402
+ } while (0)
2403
+ #define MB_FLOAT(T, UINT, NANKEY) \
2404
+ do { \
2405
+ T vn = v; uint64_t key; \
2406
+ if ( vn != vn ) { key = (NANKEY); } \
2407
+ else { if ( vn == (T) 0 ) { vn = (T) 0; } \
2408
+ UINT bits; memcpy(&bits, &vn, sizeof(bits)); key = (uint64_t) bits; } \
2409
+ int is_new; int32_t code = fz_hash_intern(&h, key, &is_new); \
2410
+ if ( is_new ) { int64_t one = 1; fz_levels_push(&lv, &v); \
2411
+ fz_levels_push(&ct, &one); } \
2412
+ else { ((int64_t *) ct.p)[code]++; } \
2413
+ } while (0)
2414
+
2415
+ /* Collect the distinct values whose count ties the fiber max, insertion-sort
2416
+ ascending (NaN last), and append them to the flat buffer. */
2417
+ #define MODE_EMIT(T, GT) \
2418
+ do { \
2419
+ T *lvp = (T *) lv.p; int64_t *ctp = (int64_t *) ct.p; \
2420
+ for ( ca_size_t c = 0; c < h.n; c++ ) { \
2421
+ if ( mx > 0 && ctp[c] == mx ) { fz_levels_push(&mod, &lvp[c]); } \
2422
+ } \
2423
+ T *dp = (T *) mod.p; ca_size_t cm = mod.n; \
2424
+ for ( ca_size_t x = 1; x < cm; x++ ) { \
2425
+ T kv = dp[x]; ca_size_t y = x; \
2426
+ while ( y > 0 && GT(dp[y - 1], kv) ) { dp[y] = dp[y - 1]; y--; } \
2427
+ dp[y] = kv; \
2428
+ } \
2429
+ for ( ca_size_t x = 0; x < cm; x++ ) { fz_levels_push(&flat, &dp[x]); } \
2430
+ } while (0)
2431
+
2432
+ while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
2433
+ fz_hash_reset(&h);
2434
+ lv.n = 0; ct.n = 0; mod.n = 0;
2435
+
2436
+ switch ( dt ) {
2437
+ case CA_INT8: CA_SLAB_REDUCE_T(int8_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2438
+ case CA_INT16: CA_SLAB_REDUCE_T(int16_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2439
+ case CA_INT32: CA_SLAB_REDUCE_T(int32_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2440
+ case CA_INT64: CA_SLAB_REDUCE_T(int64_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
2441
+ case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2442
+ case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2443
+ case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2444
+ case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
2445
+ case CA_FLOAT32:
2446
+ CA_SLAB_REDUCE_T(float, st, p, m, dummy, 0, MB_FLOAT(float, uint32_t, 0x7fc00000ULL));
2447
+ break;
2448
+ case CA_FLOAT64:
2449
+ CA_SLAB_REDUCE_T(double, st, p, m, dummy, 0, MB_FLOAT(double, uint64_t, 0x7ff8000000000000ULL));
2450
+ break;
2451
+ }
2452
+
2453
+ int64_t mx = 0;
2454
+ for ( ca_size_t c = 0; c < h.n; c++ ) {
2455
+ int64_t cc = ((int64_t *) ct.p)[c];
2456
+ if ( cc > mx ) { mx = cc; }
2457
+ }
2458
+
2459
+ switch ( dt ) {
2460
+ case CA_INT8: MODE_EMIT(int8_t, MODE_GTI); break;
2461
+ case CA_INT16: MODE_EMIT(int16_t, MODE_GTI); break;
2462
+ case CA_INT32: MODE_EMIT(int32_t, MODE_GTI); break;
2463
+ case CA_INT64: MODE_EMIT(int64_t, MODE_GTI); break;
2464
+ case CA_BOOLEAN: case CA_UINT8: MODE_EMIT(uint8_t, MODE_GTI); break;
2465
+ case CA_UINT16: MODE_EMIT(uint16_t, MODE_GTI); break;
2466
+ case CA_UINT32: MODE_EMIT(uint32_t, MODE_GTI); break;
2467
+ case CA_UINT64: MODE_EMIT(uint64_t, MODE_GTI); break;
2468
+ case CA_FLOAT32: MODE_EMIT(float, mode_gt_f32); break;
2469
+ case CA_FLOAT64: MODE_EMIT(double, mode_gt_f64); break;
2470
+ }
2471
+
2472
+ if ( out_i < M ) { foff[out_i + 1] = foff[out_i] + mod.n; }
2473
+ if ( mod.n > Kmax ) { Kmax = mod.n; }
2474
+ out_i++;
2475
+ }
2476
+ #undef MB_INT
2477
+ #undef MB_FLOAT
2478
+ #undef MODE_EMIT
2479
+ (void) dummy;
2480
+
2481
+ ca_iter_state_finish(&st);
2482
+ fz_hash_free(&h);
2483
+ fz_levels_free(&lv);
2484
+ fz_levels_free(&ct);
2485
+ fz_levels_free(&mod);
2486
+
2487
+ /* K columns, each a reduced CArray; slot j is fiber r's j-th modal value or
2488
+ UNDEF when r has fewer than j+1 modes. K == 0 -> empty Array. */
2489
+ VALUE result = rb_ary_new_capa((long) Kmax);
2490
+ for ( ca_size_t j = 0; j < Kmax; j++ ) {
2491
+ VALUE col = rb_ca_new_reduced(self, &ax, 1, dt, 0);
2492
+ CArray *cc;
2493
+ TypedData_Get_Struct(col, CArray, &carray_data_type, cc);
2494
+ ca_create_mask(cc);
2495
+ boolean8_t *cm = (boolean8_t *) cc->mask->ptr;
2496
+ char *dst = cc->ptr;
2497
+ for ( ca_size_t r = 0; r < M; r++ ) {
2498
+ ca_size_t cnt_r = foff[r + 1] - foff[r];
2499
+ if ( j < cnt_r ) {
2500
+ memcpy(dst + (size_t) r * esz, flat.p + (size_t) (foff[r] + j) * esz, esz);
2501
+ cm[r] = 0;
2502
+ }
2503
+ else {
2504
+ cm[r] = 1;
2505
+ }
2506
+ }
2507
+ rb_ary_push(result, fz_face_relift(col, face)); /* modal *values* */
2508
+ }
2509
+
2510
+ fz_levels_free(&flat);
2511
+ xfree(foff);
2512
+ return result;
2513
+ }
2514
+
2515
+ void
2516
+ Init_carray_factorize (void)
2517
+ {
2518
+ rb_define_private_method(rb_cCArray, "__factorize_appearance__",
2519
+ rb_ca_factorize_appearance, 0);
2520
+ rb_define_private_method(rb_cCArray, "__mask_duplicates__",
2521
+ rb_ca_mask_duplicates, 1);
2522
+ rb_define_private_method(rb_cCArray, "__unique_flat__",
2523
+ rb_ca_unique_flat, 0);
2524
+ rb_define_private_method(rb_cCArray, "__is_in__",
2525
+ rb_ca_is_in, 1);
2526
+ rb_define_private_method(rb_cCArray, "__locate_addr__",
2527
+ rb_ca_locate_addr, 1);
2528
+ rb_define_private_method(rb_cCArray, "__intersection__",
2529
+ rb_ca_intersection, 1);
2530
+ rb_define_private_method(rb_cCArray, "__difference__",
2531
+ rb_ca_difference, 1);
2532
+ rb_define_private_method(rb_cCArray, "__union__",
2533
+ rb_ca_set_union, 1);
2534
+ rb_define_private_method(rb_cCArray, "__value_counts_flat__",
2535
+ rb_ca_value_counts_flat, 0);
2536
+ rb_define_private_method(rb_cCArray, "__nunique__",
2537
+ rb_ca_nunique, 2);
2538
+ rb_define_private_method(rb_cCArray, "__is_mode__",
2539
+ rb_ca_is_mode, 1);
2540
+ rb_define_private_method(rb_cCArray, "__mode_axis__",
2541
+ rb_ca_mode_axis, 1);
2542
+ }