carray 2.0.1 → 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 +772 -297
  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 +229 -791
  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 +207 -240
  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 -329
  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 +1341 -198
  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 +5 -15
  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/mailmap +0 -1
  269. data/misc/Methods.ja.md +0 -182
  270. data/misc/NOTE +0 -51
  271. data/spec/Classes/CABitfield_spec.rb +0 -58
  272. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  273. data/spec/Classes/CABlock_spec.rb +0 -205
  274. data/spec/Classes/CAField_spec.rb +0 -39
  275. data/spec/Classes/CAGrid_spec.rb +0 -75
  276. data/spec/Classes/CAMap_spec.rb +0 -0
  277. data/spec/Classes/CAMapping_spec.rb +0 -105
  278. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  279. data/spec/Classes/CAObject_spec.rb +0 -33
  280. data/spec/Classes/CARefer_spec.rb +0 -93
  281. data/spec/Classes/CARepeat_spec.rb +0 -65
  282. data/spec/Classes/CASelect_spec.rb +0 -22
  283. data/spec/Classes/CAShift_spec.rb +0 -16
  284. data/spec/Classes/CAStruct_spec.rb +0 -71
  285. data/spec/Classes/CATranspose_spec.rb +0 -60
  286. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  287. data/spec/Classes/CAWindow_spec.rb +0 -54
  288. data/spec/Classes/CAWrap_spec.rb +0 -8
  289. data/spec/Classes/CArray_spec.rb +0 -184
  290. data/spec/Classes/CScalar_spec.rb +0 -55
  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,92 @@
1
+ # Pack / unpack an integer CArray to and from a packed-bit byte string,
2
+ # +nb+ bits per element.
3
+
4
+ class CArray
5
+
6
+ # @overload to_bit_string(nb)
7
+ # Returns a packed-bit byte string built from `self`, using `nb`
8
+ # bits per element.
9
+ # @param nb [Integer] bits per element.
10
+ # @return [String] byte string of length `ceil(nb * elements / 8)`.
11
+ def to_bit_string (nb)
12
+ hex = CArray.uint8(((nb*elements)/8.0).ceil)
13
+ hex.bits[nil].paste([0], self.bits[false,[(nb-1)..0]].flatten)
14
+ hex.bits[] = hex.bits[nil,[-1..0]]
15
+ return hex.to_s
16
+ end
17
+
18
+ # @overload from_bit_string(bstr, nb)
19
+ # Sets `self` by unpacking `bstr` as a packed-bit byte string
20
+ # with `nb` bits per element.
21
+ # @param bstr [String] packed byte string.
22
+ # @param nb [Integer] bits per element.
23
+ # @return [self]
24
+ def from_bit_string (bstr, nb)
25
+ hex = CArray.uint8(bstr.length).load_binary(bstr)
26
+ hex.bits[] = hex.bits[nil,[-1..0]]
27
+ bits = hex.bits.flatten
28
+ self.bits[false,[(nb-1)..0]][nil].paste([0], bits)
29
+ return self
30
+ end
31
+
32
+ # @overload from_bit_string(bstr, nb, data_type = CA_INT32, dim = nil)
33
+ # Returns a new CArray built by unpacking `bstr` as a packed-bit
34
+ # byte string with `nb` bits per element.
35
+ # @param bstr [String] packed byte string.
36
+ # @param nb [Integer] bits per element.
37
+ # @param data_type [Symbol, Integer] result `data_type`.
38
+ # @param dim [Array<Integer>, nil] result shape; when `nil` the
39
+ # length is `floor(bstr.length * 8 / nb)`.
40
+ # @return [CArray]
41
+ def self.from_bit_string (bstr, nb, data_type=CA_INT32, dim=nil)
42
+ if dim
43
+ obj = CArray.new(data_type, dim)
44
+ else
45
+ dim0 = ((bstr.length*8)/nb.to_f).floor
46
+ obj = CArray.new(data_type, [dim0])
47
+ end
48
+ obj.from_bit_string(bstr, nb)
49
+ return obj
50
+ end
51
+
52
+ # @overload pack_bits
53
+ # Packs a 1-D boolean / 0-1 uint8 CArray of length `n` into a uint8
54
+ # CArray of `ceil(n / 8)` bytes, LSB-first within each byte. Exact
55
+ # inverse of the `.bitarray` view's unpack direction: for any packed
56
+ # uint8 array `p`, `p.bitarray.reshape(-1)[0...p.elements * 8].pack_bits`
57
+ # round-trips to `p`. Tail bits of the last byte (when `n` is not a
58
+ # multiple of 8) are zero-filled. The byte order matches the packed-bit
59
+ # convention used by Apache Arrow validity bitmaps and PEP 3118 `?` /
60
+ # `_Bool` at the bit level.
61
+ # @return [CArray] uint8 CArray of shape `[ceil(n / 8)]`.
62
+ # @raise [ArgumentError] when the receiver is not 1-D or its
63
+ # `data_type` is not one of `CA_BOOLEAN` / `CA_UINT8` / `CA_INT8`.
64
+ def pack_bits
65
+ unless data_type == CA_BOOLEAN || data_type == CA_UINT8 || data_type == CA_INT8
66
+ raise ArgumentError,
67
+ "pack_bits: expected CA_BOOLEAN / CA_UINT8 / CA_INT8 (got #{data_type_name})"
68
+ end
69
+ raise ArgumentError, "pack_bits: 1-D CArray expected (got rank #{rank})" unless rank == 1
70
+ n = elements
71
+ n_bytes = (n + 7) / 8
72
+ packed = CArray.uint8(n_bytes) { 0 }
73
+ return packed if n == 0
74
+ packed.bitarray.reshape(-1)[0..n-1] = self
75
+ packed
76
+ end
77
+
78
+ # @overload validity_bits
79
+ # Returns a packed uint8 CArray where bit `i` is 1 iff cell `i` of the
80
+ # receiver is *not* masked (LSB-first, length `ceil(elements / 8)`).
81
+ # Returns `nil` when the receiver has no mask; consumers such as Arrow
82
+ # treat a missing bitmap as "all valid", so `nil` is the correct
83
+ # omission-signalling value.
84
+ # Equivalent to `is_not_masked.pack_bits` when a mask is present.
85
+ # @return [CArray, nil] uint8 CArray of shape `[ceil(elements / 8)]`,
86
+ # or `nil` when no mask is set.
87
+ def validity_bits
88
+ return nil unless has_mask?
89
+ is_not_masked.reshape(-1).pack_bits
90
+ end
91
+
92
+ end
@@ -0,0 +1,63 @@
1
+ class CArray
2
+
3
+ # @overload broadcast(*argv, expand_scalar: false)
4
+ # Returns each CArray argument broadcast to the common shape, or
5
+ # yields the broadcast tuple to a block.
6
+ #
7
+ # Non-CArray arguments (Float / Integer) pass through unchanged
8
+ # unless `expand_scalar` is true. CScalar instances are treated
9
+ # as ndim-0 scalars and excluded from the ndim check.
10
+ #
11
+ # All real CArray arguments must share the same `ndim`; implicit
12
+ # cross-ndim trailing-align is rejected. Insert size-1 axes with
13
+ # `:_` to align explicitly, e.g. `arg[:_, nil]` turns `(N,)` into
14
+ # `(1, N)` (row vector) and `arg[nil, :_]` turns it into `(N, 1)`
15
+ # (column vector).
16
+ #
17
+ # @param argv [Array<CArray, Numeric>] arguments to broadcast.
18
+ # @param expand_scalar [Boolean] when true, also expand CScalar
19
+ # and pass-through scalar arguments to the common shape.
20
+ # @yieldparam broadcast [Array<CArray, Numeric>] the broadcast
21
+ # arguments in input order.
22
+ # @return [Array<CArray, Numeric>] broadcast arguments, or the
23
+ # block's return value when a block is given.
24
+ # @raise [ArgumentError] when CArray arguments differ in `ndim`.
25
+ def CArray.broadcast (*argv, expand_scalar: false, &block)
26
+
27
+ sel = argv.select { |arg| arg.is_a?(CArray) && !arg.is_a?(CScalar) }
28
+ return argv if sel.empty?
29
+
30
+ ndims = sel.map(&:ndim).uniq
31
+ if ndims.size > 1
32
+ shapes = argv.each_with_index
33
+ .select { |a, _| a.is_a?(CArray) && !a.is_a?(CScalar) }
34
+ .map { |a, i| " arg[#{i}]: shape=(#{a.shape.join(', ')})" }
35
+ raise ArgumentError, <<~MSG.strip
36
+ CArray.broadcast: ndim mismatch (got #{ndims.sort.join(' and ')})
37
+ #{shapes.join("\n")}
38
+ CArray does not implicit-broadcast across ndim. Insert a size-1
39
+ axis with :_ to align explicitly, e.g.
40
+ arg[:_, nil] # (N,) -> (1, N) row-vector
41
+ arg[nil, :_] # (N,) -> (N, 1) column-vector
42
+ MSG
43
+ end
44
+
45
+ ndim = sel.first.ndim
46
+ dim = (0...ndim).map { |k| sel.map { |a| a.shape[k] }.max }
47
+
48
+ list = argv.map do |arg|
49
+ case arg
50
+ when CScalar
51
+ expand_scalar ? arg.broadcast_to(*dim) : arg
52
+ when CArray
53
+ arg.broadcast_to(*dim)
54
+ else
55
+ expand_scalar ? arg : arg
56
+ end
57
+ end
58
+
59
+ return block.call(*list) if block
60
+ return list
61
+ end
62
+
63
+ end
@@ -0,0 +1,39 @@
1
+ class CArray
2
+
3
+ # @overload choose(choices, data_type: nil)
4
+ # Returns label-based per-cell selection: `self` is an integer
5
+ # label array, and `choices` is a list indexed by those labels.
6
+ #
7
+ # Where `self == i`, the result takes `choices[i]` -- a scalar
8
+ # fills those cells, a CArray contributes its corresponding
9
+ # cells. The result has the same shape as `self`.
10
+ #
11
+ # @param choices [Array<CArray, Object>] values indexed by the
12
+ # labels in `self`; each entry is either a same-shape CArray
13
+ # or a scalar fill.
14
+ # @param data_type [Symbol, Integer, nil] result `data_type`.
15
+ # When `nil` it is inferred: `CArray.result_type` of the CArray
16
+ # choices, or `CA_OBJECT` when every choice is a scalar.
17
+ # @return [CArray] new CArray with the shape of `self` holding
18
+ # the chosen values.
19
+ # @example
20
+ # ref = CA_INT([[0, 1, 2], [1, 2, 0], [2, 0, 1]])
21
+ # a = CArray.int(3, 3).seq(1)
22
+ # b = CArray.int(3, 3).seq(11)
23
+ # c = CArray.int(3, 3).seq(21)
24
+ # ref.choose([a, b, c]) # per-cell pick from a / b / c
25
+ # ref.choose(["a", "b", "c"]) # recode labels to values
26
+ def choose (choices, data_type: nil)
27
+ unless data_type
28
+ ca = choices.select { |v| v.is_a?(CArray) }
29
+ data_type = ca.empty? ? CA_OBJECT : CArray.result_type(*ca)
30
+ end
31
+ out = template(data_type)
32
+ choices.each_with_index do |v, i|
33
+ s = self.eq(i)
34
+ out[s] = v.is_a?(CArray) ? v[s] : v
35
+ end
36
+ out
37
+ end
38
+
39
+ end
@@ -0,0 +1,280 @@
1
+ # Composition methods cheat sheet (CArray 3.0 surface)
2
+ #
3
+ # The three class methods `meld` / `stack` / `montage` form a uniform-input
4
+ # view-default surface; their ragged eager counterparts `concatenate` / --
5
+ # / `mosaic` accept non-uniform pieces.
6
+ #
7
+ # This file holds the eager ragged family (`concatenate` / `mosaic`) and
8
+ # the 1-D column bundler (`tabulate`). `split` (the inverse of stack) lives
9
+ # in carray/stack.rb; the sub-region copy pair (`paste` / `crop`) lives in
10
+ # carray/basics.rb; the shape-edit helpers (`resize`, `insert_block` /
11
+ # `delete_block`) live in carray/methods/. The CAStack view-default trio
12
+ # (`stack` / `meld` / `montage`, plus `CArray#stack` and `CAStack#append`)
13
+ # lives in carray/stack.rb. The cheat sheet below covers the whole family.
14
+ #
15
+ # | view (same dtype) | eager (auto-cast)
16
+ # -------------------+------------------------+-----------------------
17
+ # concat existing | meld (CAMeld view) | concatenate (materialised)
18
+ # axis (ndim same) | |
19
+ # -------------------+------------------------+-----------------------
20
+ # add new axis | stack (CAStack view) | (= ragged impossible)
21
+ # (ndim grows) | |
22
+ # -------------------+------------------------+-----------------------
23
+ # tile grid | montage | mosaic (materialised)
24
+ # (ndim same, axes | |
25
+ # extend by tdim) | |
26
+ #
27
+ # `meld` (CAMeld view) is the unified surface for concatenation along an
28
+ # existing axis: it handles both uniform and ragged pieces and returns a
29
+ # CAMeld view. Parents must agree on data_type — cast beforehand or use
30
+ # `concatenate` (eager, auto-casts).
31
+ #
32
+ # Vocabulary:
33
+ # meld = melt + weld; pieces dissolve their boundaries along the
34
+ # named axis and are regarded as one; returns a CAMeld view
35
+ # stack = K parents pushed onto a new K axis (CAStack view)
36
+ # montage = uniform tiles arranged in a regular grid (ImageMagick)
37
+ # concatenate = 1-axis concat, eager materialised copy with auto-cast
38
+ # mosaic = irregular pieces fitted into a grid (art-form analog)
39
+ #
40
+ # Examples (a, b, c are each shape [3, 4]):
41
+ #
42
+ # CArray.meld([a, b, c]) #=> view, shape (9, 4)
43
+ # CArray.meld([a, b, c], axis: 1) #=> view, shape (3, 12)
44
+ # CArray.meld([a, b, c]).to_ca #=> eager CArray, shape (9, 4)
45
+ # CArray.stack([a, b, c]) #=> view, shape (3, 3, 4) (K outermost)
46
+ # CArray.stack([a, b, c], axis: -1) #=> view, shape (3, 4, 3) (RGB pattern)
47
+ # CArray.montage([a, b, c, d], [2, 2], axis: 0) #=> view, shape (6, 8) (2x2 image tile)
48
+ #
49
+ # # data_type is a kwarg (= 3.0 breaking); omitted = result_type auto-infer
50
+ # CArray.meld([a, b, c], axis: 0, data_type: :float64)
51
+ # CArray.meld([a, b, c]) # data_type inferred
52
+ #
53
+ # `tabulate` bundles a list of 1-D arrays into a 2-D table (one column
54
+ # each). Block-matrix assembly is `mosaic`; vertical stacking of 2-D
55
+ # tables is `concatenate(axis: 0)`. (The old `join` / block-style
56
+ # helper was retired in 3.0 -- its column-major nesting was confusing and
57
+ # `mosaic` covers block assembly correctly.)
58
+ #
59
+ # Known limitation: scalar indexing on a CAStack-rooted view drops ndim
60
+ # and triggers a full materialise fallback. Use range indexing (e.g.
61
+ # `meld(...)[k..k, nil]` instead of `meld(...)[k, nil]`) for partial-use
62
+ # perf, or `.to_ca` upfront to materialise eagerly.
63
+
64
+ class CArray
65
+
66
+ # CArray#paste / #crop (sub-region copy) live in carray/basics.rb.
67
+
68
+ # ---------------------------------------------------------------- eager ragged 2
69
+
70
+ # @overload concatenate(list, axis: 0, data_type: nil)
71
+ # Returns `list` concatenated along a single existing axis. Eager
72
+ # (returns a fresh CArray) and accepts non-uniform pieces (varying
73
+ # sizes along the `axis`); non-tile axes must agree across pieces.
74
+ #
75
+ # Use `CArray.meld` for the uniform-shape view-default counterpart.
76
+ #
77
+ # @param list [Array<CArray>] pieces to concatenate.
78
+ # @param axis [Integer] axis to concatenate along.
79
+ # @param data_type [Symbol, Integer, nil] result `data_type`;
80
+ # inferred via `result_type` when `nil`.
81
+ # @return [CArray] fresh CArray with per-piece `axis` sizes summed.
82
+ # @raise [ArgumentError] when `list` is empty or piece shapes are
83
+ # inconsistent.
84
+ def self.concatenate (list, axis: 0, data_type: nil)
85
+ raise ArgumentError, "concatenate: list must not be empty" if list.empty?
86
+ __ragged_paste(list, [list.size], axis, data_type)
87
+ end
88
+
89
+ # @overload concatenate(*others, axis: 0, data_type: nil)
90
+ # Instance form of {CArray.concatenate}: returns `[self, *others]`
91
+ # concatenated along `axis` as a fresh CArray. Eager auto-cast
92
+ # counterpart of `#meld` (view).
93
+ #
94
+ # @param others [Array<CArray>] additional pieces.
95
+ # @param axis [Integer] axis to concatenate along.
96
+ # @param data_type [Symbol, Integer, nil] result `data_type`;
97
+ # inferred via `result_type` when `nil`.
98
+ # @return [CArray] fresh CArray.
99
+ # @raise [ArgumentError] when no `others` are given.
100
+ def concatenate (*others, axis: 0, data_type: nil)
101
+ raise ArgumentError, "concatenate: at least one other array required" if others.empty?
102
+ CArray.concatenate([self, *others], axis: axis, data_type: data_type)
103
+ end
104
+
105
+ # @overload mosaic(list, tdim, axis: 0, data_type: nil)
106
+ # Returns `list` tiled into an N-D grid layout described by
107
+ # `tdim`. Eager (returns a fresh CArray), accepts non-uniform
108
+ # sizes along the tile axes with block-matrix consistency
109
+ # (row-by-row / column-by-column agreement).
110
+ #
111
+ # @param list [Array<CArray>] pieces to tile; length must equal
112
+ # the product of `tdim`.
113
+ # @param tdim [Array<Integer>] tile grid shape.
114
+ # @param axis [Integer] first tile axis in the result.
115
+ # @param data_type [Symbol, Integer, nil] result `data_type`;
116
+ # inferred when `nil`.
117
+ # @return [CArray] fresh CArray with tile axes extended by summed
118
+ # per-tile sizes.
119
+ # @raise [ArgumentError] when `list` is empty, `tdim` is
120
+ # ill-formed, or piece shapes violate block-matrix consistency.
121
+ def self.mosaic (list, tdim, axis: 0, data_type: nil)
122
+ raise ArgumentError, "mosaic: list must not be empty" if list.empty?
123
+ unless tdim.is_a?(Array) && tdim.size > 0
124
+ raise ArgumentError, "mosaic: tdim must be a non-empty Array of Integer"
125
+ end
126
+ expected = tdim.inject(1, :*)
127
+ unless expected == list.size
128
+ raise ArgumentError,
129
+ "mosaic: tdim product (#{expected}) must equal list size (#{list.size})"
130
+ end
131
+ __ragged_paste(list, tdim, axis, data_type)
132
+ end
133
+
134
+ # Shared eager paste-loop helper (= ex-`combine` paste implementation,
135
+ # preserved verbatim for concatenate / mosaic). Returns a fresh CArray of
136
+ # shape obtained by tiling `list` over `tdim` starting at `axis`, with
137
+ # per-piece dim sizes summed along each tile axis (= block-matrix
138
+ # consistency required across rows / columns).
139
+ def self.__ragged_paste (list, tdim, axis, data_type)
140
+ list = CArray.promote_list(list, data_type: data_type)
141
+ # promote_list has already enforced homogeneity (= common data_type, and
142
+ # for Face elements: same Face class + portable + state-compatible). So,
143
+ # mirroring CArray.stack, paste at the storage level and re-wrap the
144
+ # result with face_lift, rather than special-casing data_class / DATA_SIZE
145
+ # here (= which only ever handled CARecord and silently corrupted plain
146
+ # fixlen / numeric Faces by leaving bytes nil).
147
+ face_parent = list[0].face? ? list[0] : nil
148
+ if face_parent
149
+ list = list.map { |x| s = x; s = s.parent while s.face?; s }
150
+ end
151
+ data_type = list[0].data_type
152
+ bytes = (data_type == :fixlen) ? list[0].bytes : nil
153
+ # promote_list guarantees a non-empty list of CArrays, so the reference
154
+ # shape is simply the first element (a CScalar carries shape [1], so a
155
+ # leading scalar is fine; the scalar-expansion pass below broadcasts it).
156
+ ref = list[0]
157
+ dim = ref.shape
158
+ ndim = ref.ndim
159
+ tndim = tdim.size
160
+ axis = CArray.normalize_axis(axis, ndim - tndim + 1, "concatenate/mosaic")
161
+
162
+ list = list.map do |x|
163
+ if x.scalar?
164
+ rdim = dim.clone
165
+ rdim[axis] = :%
166
+ x = x[*rdim]
167
+ end
168
+ x
169
+ end
170
+
171
+ block = CArray.object(*tdim).tap { |a| a[] = list }
172
+ # Measure the per-tile-axis sizes from the representative line (= all
173
+ # other tile coords 0). Tile axes may be ragged, but every piece must
174
+ # then be block-matrix consistent (= same size along a tile axis as its
175
+ # line) and must agree with the reference on each non-tile axis.
176
+ edim = tdim.clone
177
+ tile_sizes = Array.new(tndim) { [] }
178
+ offset = Array.new(tndim) { [] }
179
+ probe = Array.new(tndim, 0)
180
+ tndim.times do |i|
181
+ edim[i] = 0
182
+ probe.map! { 0 }
183
+ probe[i] = nil
184
+ block[*probe].each do |e|
185
+ offset[i] << edim[i]
186
+ tile_sizes[i] << e.shape[axis + i]
187
+ edim[i] += e.shape[axis + i]
188
+ end
189
+ end
190
+ block.each_with_index do |item, *tidx|
191
+ unless item.ndim == ndim
192
+ raise ArgumentError,
193
+ "concatenate/mosaic: piece at tile #{tidx.inspect} has ndim " \
194
+ "#{item.ndim} (expected #{ndim})"
195
+ end
196
+ ndim.times do |d|
197
+ i = d - axis
198
+ on_tile = (i >= 0 && i < tndim)
199
+ expected = on_tile ? tile_sizes[i][tidx[i]] : dim[d]
200
+ unless item.shape[d] == expected
201
+ raise ArgumentError,
202
+ "concatenate/mosaic: piece at tile #{tidx.inspect} has " \
203
+ "#{on_tile ? 'tile' : 'non-tile'} axis #{d} size " \
204
+ "#{item.shape[d]} (expected #{expected}); pieces must agree " \
205
+ "on non-tile axes and be block-matrix consistent along tile axes"
206
+ end
207
+ end
208
+ end
209
+ newdim = dim.clone
210
+ newdim[axis, tndim] = edim
211
+ obj = CArray.new(data_type, newdim, bytes: bytes)
212
+ idx = newdim.map { 0 }
213
+ block.each_with_index do |item, *tidx|
214
+ (axis...axis + tndim).each_with_index do |d, i|
215
+ idx[d] = offset[i][tidx[i]]
216
+ end
217
+ obj.paste(idx, item)
218
+ end
219
+ obj = obj.face_lift(face_parent) if face_parent
220
+ obj
221
+ end
222
+ private_class_method :__ragged_paste
223
+
224
+ # ---------------------------------------------------------------- tabulate
225
+
226
+ # @overload tabulate(columns, data_type: nil)
227
+ # Returns a 2-D table assembled from a list of column blocks,
228
+ # coerced to a common `data_type`. Eager (returns a fresh, owned
229
+ # CArray) -- the point is to materialise a typed table, not a view.
230
+ #
231
+ # Each entry is a 1-D array (one column, length L) or a 2-D array
232
+ # (a block of `L x k` columns). All entries must share the same
233
+ # length L; `tabulate` does not pad ragged lengths. Column counts
234
+ # may differ: entries are concatenated along the column axis, so
235
+ # a 1-column, a 3-column and a 2-column block produce a 6-column
236
+ # table. The result `data_type` is inferred (`result_type` of the
237
+ # entries) unless `data_type` is given.
238
+ #
239
+ # For block-matrix assembly use `mosaic`; to stack 2-D tables
240
+ # vertically use `concatenate(axis: 0)`.
241
+ #
242
+ # @param columns [Array<CArray>] 1-D columns and/or 2-D column
243
+ # blocks, all of equal length `L`.
244
+ # @param data_type [Symbol, Integer, nil] result `data_type`;
245
+ # inferred when `nil`.
246
+ # @return [CArray] 2-D CArray of shape `(L, total column count)`.
247
+ # @raise [ArgumentError] when `columns` is empty, entries are
248
+ # not 1-D or 2-D CArrays, or row counts disagree.
249
+ # @example
250
+ # c1 = CA_INT([1, 2, 3])
251
+ # c2 = CA_DOUBLE([4.5, 5.5, 6.5])
252
+ # CArray.tabulate([c1, c2]) # float64 (3, 2)
253
+ # CArray.tabulate([c1, c2], data_type: :int32) # int32 (3, 2)
254
+ def self.tabulate (columns, data_type: nil)
255
+ raise ArgumentError, "tabulate: columns must not be empty" if columns.empty?
256
+ blocks = columns.map do |c|
257
+ unless c.is_a?(CArray) && (c.ndim == 1 || c.ndim == 2)
258
+ raise ArgumentError, "tabulate: each column must be a 1-D or 2-D CArray"
259
+ end
260
+ c.ndim == 1 ? c[nil, :_] : c # promote a bare column to (L, 1)
261
+ end
262
+ len = blocks[0].shape[0]
263
+ blocks.each_with_index do |b, i|
264
+ unless b.shape[0] == len
265
+ raise ArgumentError,
266
+ "tabulate: all columns must have equal length (row count) " \
267
+ "(column 0 has length #{len}, column #{i} has length " \
268
+ "#{b.shape[0]}); tabulate does not pad ragged lengths"
269
+ end
270
+ end
271
+ # Equal-length blocks, ragged column counts -> concatenate along the
272
+ # column axis with a common (coerced or inferred) data_type.
273
+ concatenate(blocks, axis: 1, data_type: data_type)
274
+ end
275
+
276
+ # CArray#split (the inverse of CArray.stack) lives in carray/stack.rb,
277
+ # next to stack. CArray#resize / #insert_block / #delete_block live in
278
+ # carray/methods/ (resize.rb, insert_block.rb), autoloaded.
279
+
280
+ end
@@ -0,0 +1,206 @@
1
+ # N-D arbitrary-position gather / scatter (CArray implementation of
2
+ # TensorFlow `gather_nd` / `scatter_nd_update`).
3
+ #
4
+ # Natural N-D extension of `take_along_axis` (= fiber-aligned gather along axis).
5
+ # Treats the trailing K axes of `indices.shape = (..., K)` as coordinate tuples
6
+ # and consumes the first K axes of params while carrying the remaining axes (= rest).
7
+ #
8
+ # Implementation goes through a CAMapping view of the flattened array:
9
+ # `params.flatten[flat_addr]`. Pure Ruby, no additional framework, composed
10
+ # entirely from existing indexers as in `take_along_axis`. Since CAMapping is
11
+ # writable, write-through via `put_nd` uses last-write-wins semantics (= last
12
+ # value wins on duplicate indices, same contract as `put_along_axis`). For
13
+ # accumulate semantics, route to the `scatter_*!` family instead.
14
+
15
+ class CArray
16
+
17
+ # @overload gather_nd(indices)
18
+ # Returns elements (or sub-arrays) gathered from `self` at the
19
+ # N-D coordinates given by `indices`.
20
+ #
21
+ # `indices` accepts two equivalent forms:
22
+ #
23
+ # * **stacked** — a single CArray whose last axis enumerates a
24
+ # K-dimensional coordinate tuple into the first K axes of `self`.
25
+ # * **per-axis** — an `Array` of K coordinate CArrays (one per
26
+ # consumed axis). The per-axis arrays are broadcast together
27
+ # (via {CArray.broadcast}) and stacked along a new trailing axis,
28
+ # so `gather_nd([i, j])` == `gather_nd(CArray.stack([i, j], axis: -1))`.
29
+ # An Integer scalar is accepted for a constant axis and broadcast
30
+ # to the common shape. Entries must be CArray or Integer: Ruby
31
+ # Array literals are rejected (wrap them with `CA_INT64(...)`
32
+ # yourself), keeping this a copy-free gather path.
33
+ #
34
+ # The remaining `self.shape[K..-1]` axes (called `rest`) are carried
35
+ # through:
36
+ #
37
+ # self.shape = (D0, ..., D_{K-1}, *rest)
38
+ # indices.shape = (*outer, K) # stacked form
39
+ # result.shape = (*outer, *rest)
40
+ #
41
+ # In the fully-degenerate case where both `outer` and `rest` are
42
+ # empty (a single full coordinate via 1-D `indices` of shape `(K,)`
43
+ # with `K == ndim`), the result is a 1-element `(1,)` CArray,
44
+ # following CArray's scalar model (CScalar carries shape `[1]`),
45
+ # not a 0-dim array.
46
+ #
47
+ # The result is a fresh materialised CArray. Negative indices on
48
+ # each coordinate axis follow CArray's standard wrap rule
49
+ # (`-1` == last); out-of-range indices raise. Duplicate
50
+ # coordinates in `indices` are fine on gather: the same value is
51
+ # picked multiple times. See {#put_nd} for the duplicate-write
52
+ # story.
53
+ #
54
+ # @param indices [CArray, Array<CArray, Integer>] stacked integer
55
+ # CArray with `ndim >= 1` and last axis size `K` in `[1, ndim]`,
56
+ # or an Array of K per-axis coordinate CArrays (Integer scalars
57
+ # allowed per axis).
58
+ # @return [CArray] materialised result with shape `outer + rest`.
59
+ # @raise [ArgumentError] when `indices` is neither a CArray nor an
60
+ # Array, is 0-dim, is non-integer, or has a last-axis size
61
+ # outside `[1, ndim]`.
62
+ # @raise [IndexError] when a coordinate is out of range on any axis.
63
+ def gather_nd (indices)
64
+ flat_addr, outer, rest = gather_nd_flat_addr(indices, "gather_nd")
65
+ out_shape = outer + rest
66
+ # flatten + 1-D fancy indexing -> CAMapping view -> materialise via .copy.
67
+ result = self.flatten[flat_addr].copy
68
+ out_shape.empty? ? result : result.reshape(*out_shape)
69
+ end
70
+
71
+ # @overload put_nd(indices, values)
72
+ # Sets `self` at the N-D coordinates given by `indices` to
73
+ # `values`. Inverse of {#gather_nd}.
74
+ #
75
+ # self.shape = (D0, ..., D_{K-1}, *rest)
76
+ # indices.shape = (*outer, K)
77
+ # values broadcast to (*outer, *rest)
78
+ #
79
+ # Duplicate coordinates in `indices` use **last-write-wins**
80
+ # semantics, matching `put_along_axis`. Accumulate semantics
81
+ # (`+=`) are not provided here; route to the `scatter_*!`
82
+ # family instead (e.g. `self.flatten.scatter_add!(flat_addr, vals)`).
83
+ #
84
+ # @param indices [CArray, Array<CArray, Integer>] stacked integer
85
+ # CArray shaped `(*outer, K)`, or an Array of K per-axis
86
+ # coordinate arrays (same forms as {#gather_nd}).
87
+ # @param values [CArray, Numeric] values broadcastable to
88
+ # `(*outer, *rest)`.
89
+ # @return [self]
90
+ # @raise [ArgumentError] on the same conditions as {#gather_nd}.
91
+ # @raise [IndexError] when a coordinate is out of range on any axis.
92
+ def put_nd (indices, values)
93
+ flat_addr, _outer, _rest = gather_nd_flat_addr(indices, "put_nd")
94
+ self.flatten[flat_addr] = values
95
+ self
96
+ end
97
+
98
+ private
99
+
100
+ # Shared helper: compute the flat-address CArray (shape = outer + rest)
101
+ # for both `gather_nd` and `put_nd`. Returns `[flat_addr, outer, rest]`.
102
+ def gather_nd_flat_addr (indices, name)
103
+ # Per-axis form: an Array of K coordinate arrays. Broadcast them to a
104
+ # common outer shape and stack along a new trailing axis, yielding the
105
+ # (*outer, K) stacked form the rest of this helper already handles.
106
+ indices = gather_nd_stack_axes(indices, name) if indices.is_a?(Array)
107
+
108
+ unless indices.is_a?(CArray)
109
+ raise ArgumentError, "#{name}: indices must be a CArray or an Array of per-axis CArrays"
110
+ end
111
+ if indices.ndim == 0
112
+ raise ArgumentError, "#{name}: indices must have at least 1 dimension"
113
+ end
114
+ unless indices.integer?
115
+ raise ArgumentError,
116
+ "#{name}: indices must be an integer CArray (got #{indices.data_type})"
117
+ end
118
+
119
+ k = indices.shape[-1]
120
+ unless k.is_a?(Integer) && k >= 1 && k <= ndim
121
+ raise ArgumentError,
122
+ "#{name}: indices last-axis size #{k} out of [1, #{ndim}]"
123
+ end
124
+
125
+ outer = indices.shape[0..-2] # may be []
126
+ rest = (k < ndim) ? shape[k..-1] : [] # may be []
127
+ m = outer.empty? ? 1 : outer.inject(:*)
128
+ rest_size = rest.empty? ? 1 : rest.inject(:*)
129
+
130
+ # Stride per consumed axis i = product of params.shape[(i+1)..-1]
131
+ # (covers remaining consumed axes + all rest axes).
132
+ strides_a = (0...k).map { |i| shape[(i+1)..-1].inject(1, :*) }
133
+ strides = CA_INT64(strides_a).reshape(1, k)
134
+
135
+ # Per-axis dimension for negative-index normalize + OOB check.
136
+ dims = CA_INT64(shape[0...k]).reshape(1, k)
137
+
138
+ flat_idx_raw = indices.reshape(m, k)
139
+
140
+ # OOB check (per-axis): require -dim <= idx < dim on every coordinate.
141
+ if (flat_idx_raw >= dims).any
142
+ raise IndexError, "#{name}: coordinate >= dim on some axis"
143
+ end
144
+ if (flat_idx_raw < -dims).any
145
+ raise IndexError, "#{name}: coordinate < -dim on some axis"
146
+ end
147
+
148
+ # Wrap negatives: (-dim..-1) -> (0..dim-1) via (idx + dim) % dim.
149
+ # Safe under -dim <= idx < dim (just checked).
150
+ flat_idx = (flat_idx_raw + dims) % dims
151
+
152
+ base = (flat_idx * strides).sum(axis: 1).int64.reshape(m) # (M,)
153
+
154
+ flat_addr =
155
+ if rest.empty?
156
+ base # (M,)
157
+ else
158
+ offsets = CArray.int64(rest_size).seq
159
+ base.reshape(m, 1) + offsets.reshape(1, rest_size) # (M, rest_size)
160
+ end
161
+
162
+ [flat_addr, outer, rest]
163
+ end
164
+
165
+ # Convert the per-axis form (an Array of K coordinate arrays) into the
166
+ # stacked `(*outer, K)` CArray. Coordinate arrays are broadcast to a
167
+ # common `outer` shape (via CArray.broadcast, which rejects cross-ndim
168
+ # implicit align) and stacked along a new trailing axis. Integer
169
+ # scalars are accepted per axis and expand to the common shape; an
170
+ # all-scalar list yields the 1-D `(K,)` single-coordinate form.
171
+ def gather_nd_stack_axes (list, name)
172
+ if list.empty?
173
+ raise ArgumentError, "#{name}: coordinate list must not be empty"
174
+ end
175
+
176
+ # This is a high-performance gather path: per-axis entries must be
177
+ # CArray coordinate arrays (or a bare Integer scalar for a constant
178
+ # axis). Ruby Arrays are rejected on purpose -- coercing them via
179
+ # CA_INT64 on every call would defeat the point of the API; wrap them
180
+ # yourself once (CA_INT64(...)) if you have array literals.
181
+ list.each do |c|
182
+ unless c.is_a?(CArray) || c.is_a?(Integer)
183
+ raise ArgumentError,
184
+ "#{name}: per-axis coordinate must be a CArray or Integer (got #{c.class})"
185
+ end
186
+ end
187
+
188
+ # All scalars -> a single K-coordinate tuple (degenerate stacked form).
189
+ if list.none? { |c| c.is_a?(CArray) }
190
+ return CA_INT64(list)
191
+ end
192
+
193
+ coords = CArray.broadcast(*list, expand_scalar: true)
194
+ ref = coords.find { |c| c.is_a?(CArray) && !c.is_a?(CScalar) }
195
+ coords = coords.map do |c|
196
+ case c
197
+ when CArray
198
+ c
199
+ else # Integer scalar on this axis
200
+ CArray.int64(*ref.shape) { c }
201
+ end
202
+ end
203
+ CArray.stack(coords, axis: -1)
204
+ end
205
+
206
+ end