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,401 @@
1
+ # Apache Arrow "tensor IPC" reader / writer for CArray. [experimental]
2
+ #
3
+ # This surface is experimental: the method names, the module namespace, and
4
+ # the packaging (it may later be extracted into a standalone, array-library-
5
+ # neutral gem) are not yet frozen and may change in a future release.
6
+ #
7
+ # This is an interoperability layer, not an archival format: it exchanges
8
+ # a dense numeric array with the Arrow ecosystem (pyarrow.ipc.write_tensor /
9
+ # read_tensor and equivalents) using Arrow's encapsulated tensor message.
10
+ # For faithful, reproducible persistence of a CArray -- mask, attributes,
11
+ # data_class and all -- use CArray.save / CArray.load (the _CARRAY3 format).
12
+ #
13
+ # The on-disk shape is: a 0xFFFFFFFF continuation marker, an int32 metadata
14
+ # length, a FlatBuffers Message whose header is a Tensor (element type, shape,
15
+ # byte strides, and a Buffer descriptor), padding to an 8-byte boundary, then
16
+ # the raw contiguous element bytes. Only the numeric types Arrow tensors
17
+ # admit are supported (int8..int64, uint8..uint64, float32, float64); boolean,
18
+ # complex, fixlen and object arrays have no Arrow tensor representation and are
19
+ # rejected. A masked CArray is rejected too, mirroring the MemoryView policy:
20
+ # Arrow tensors carry no validity concept, so the caller must be explicit
21
+ # (ca.value to drop the mask, ca.unmask_copy(fill) to bake it in, or persist
22
+ # the mask separately through CArray.save).
23
+
24
+ require "stringio"
25
+
26
+ class CArray
27
+ # Reader / writer for Arrow's encapsulated tensor IPC message, for
28
+ # exchanging a dense numeric array with the Arrow ecosystem
29
+ # (`pyarrow.ipc.write_tensor` / `read_tensor` and equivalents).
30
+ #
31
+ # Experimental: the method names, this namespace and the packaging are not
32
+ # frozen, and the layer may later move to a standalone, array-library-neutral
33
+ # gem. It is an interoperability format, not an archival one — an Arrow
34
+ # tensor carries no mask, attributes or data_class, so `CArray.save` /
35
+ # `CArray.load` remain the way to persist a CArray faithfully.
36
+ #
37
+ # Only the numeric types an Arrow tensor admits are supported (int8..int64,
38
+ # uint8..uint64, float32, float64). Boolean, complex, fixlen and object
39
+ # arrays are rejected, as is a masked array — Arrow tensors have no validity
40
+ # concept, so the caller must be explicit (`ca.value` to drop the mask,
41
+ # `ca.unmask_copy(fill)` to bake it in, or save the mask separately).
42
+ module ArrowTensor
43
+
44
+ # CArray data_type symbol -> Arrow element-type descriptor.
45
+ # [:float, precision] precision: 1 = single, 2 = double
46
+ # [:int, bit_width, signed?]
47
+ DTYPE_TO_ARROW = {
48
+ float32: [:float, 1], float64: [:float, 2],
49
+ int8: [:int, 8, true], int16: [:int, 16, true],
50
+ int32: [:int, 32, true], int64: [:int, 64, true],
51
+ uint8: [:int, 8, false], uint16:[:int, 16, false],
52
+ uint32:[:int, 32, false], uint64:[:int, 64, false],
53
+ }.freeze
54
+
55
+ module_function
56
+
57
+ # Write +ca+ to +io+ as an Arrow tensor IPC message.
58
+ def write (ca, io)
59
+ if ca.has_mask?
60
+ raise ArgumentError,
61
+ "cannot write a masked CArray as an Arrow tensor " \
62
+ "(Arrow tensors have no validity concept); pass ca.value to " \
63
+ "drop the mask, ca.unmask_copy(fill) to fill it, or use " \
64
+ "CArray.save for a mask-preserving round-trip"
65
+ end
66
+ arrow = DTYPE_TO_ARROW[ca.data_type]
67
+ unless arrow
68
+ raise ArgumentError,
69
+ "cannot write a #{ca.data_type} CArray as an Arrow tensor " \
70
+ "(Arrow tensors are numeric only: int8..int64, uint8..uint64, " \
71
+ "float32, float64)"
72
+ end
73
+ raise ArgumentError, "cannot write a 0-dimensional array" if ca.ndim < 1
74
+
75
+ shape = ca.shape
76
+ elt = ca.bytes
77
+ strides = row_major_strides(shape, elt)
78
+
79
+ body = StringIO.new("".b)
80
+ ca.dump_binary(body) # raw contiguous element bytes, row-major
81
+ body = body.string
82
+
83
+ meta = build_metadata(arrow, shape, strides, body.bytesize)
84
+ pad = (8 - ((8 + meta.bytesize) % 8)) % 8
85
+ meta += "\0".b * pad
86
+
87
+ io.write([0xFFFFFFFF].pack("V"))
88
+ io.write([meta.bytesize].pack("l<"))
89
+ io.write(meta)
90
+ io.write(body)
91
+ ca
92
+ end
93
+
94
+ # Read an Arrow tensor IPC message from +io+ and return a new CArray.
95
+ def read (io)
96
+ b = io.respond_to?(:read) ? io.read : io.to_s
97
+ b = b.b
98
+ meta = parse_metadata(b)
99
+ type = meta[:data_type]
100
+ unless CArray.respond_to?(:data_type_code) && DTYPE_TO_ARROW.key?(type)
101
+ raise ArgumentError,
102
+ "Arrow tensor element type #{type.inspect} has no CArray " \
103
+ "counterpart"
104
+ end
105
+ build_carray(type, meta[:shape], meta[:strides], meta[:body])
106
+ end
107
+
108
+ # ---- helpers ----------------------------------------------------------
109
+
110
+ def row_major_strides (shape, elt)
111
+ s = Array.new(shape.length)
112
+ acc = elt
113
+ (shape.length - 1).downto(0) do |i|
114
+ s[i] = acc
115
+ acc *= shape[i]
116
+ end
117
+ s
118
+ end
119
+ private_class_method :row_major_strides
120
+
121
+ def build_carray (type, shape, strides, body)
122
+ elt = CArray.new(type, [1]).bytes
123
+ rmaj = row_major_strides(shape, elt)
124
+ if strides == rmaj
125
+ ca = CArray.new(type, shape)
126
+ ca.load_binary(StringIO.new(body))
127
+ return ca
128
+ end
129
+ cmaj = row_major_strides(shape.reverse, elt).reverse
130
+ if strides == cmaj
131
+ t = CArray.new(type, shape.reverse)
132
+ t.load_binary(StringIO.new(body))
133
+ return t.transpose(*(0...shape.length).to_a.reverse).to_ca
134
+ end
135
+ # General strided layout (rare): gather element by element.
136
+ gather_strided(type, shape, strides, body)
137
+ end
138
+ private_class_method :build_carray
139
+
140
+ def gather_strided (type, shape, strides, body)
141
+ elt = CArray.new(type, [1]).bytes
142
+ code = UNPACK[type]
143
+ ca = CArray.new(type, shape)
144
+ elements = shape.inject(1, :*)
145
+ idx = Array.new(shape.length, 0)
146
+ elements.times do
147
+ off = 0
148
+ idx.each_with_index { |v, k| off += v * strides[k] }
149
+ ca[*idx] = body[off, elt].unpack1(code)
150
+ k = shape.length - 1
151
+ while k >= 0
152
+ idx[k] += 1
153
+ break if idx[k] < shape[k]
154
+ idx[k] = 0
155
+ k -= 1
156
+ end
157
+ end
158
+ ca
159
+ end
160
+ private_class_method :gather_strided
161
+
162
+ # @!visibility private
163
+ UNPACK = {
164
+ float32:"e", float64:"E",
165
+ int8:"c", int16:"s<", int32:"l<", int64:"q<",
166
+ uint8:"C", uint16:"v", uint32:"V", uint64:"Q<",
167
+ }.freeze
168
+
169
+ # ---- FlatBuffers Message encoder (header = Tensor) --------------------
170
+
171
+ # Minimal back-to-front FlatBuffers builder, specialised to the fields the
172
+ # Tensor message uses (scalars, tables, vectors, one inline struct).
173
+ #
174
+ # @!visibility private
175
+ class Builder
176
+ def initialize
177
+ @buf = "".b
178
+ @minalign = 1
179
+ @vt = nil
180
+ @obj_start = nil
181
+ end
182
+
183
+ # @!visibility private
184
+ def offset; @buf.bytesize; end
185
+
186
+ # @!visibility private
187
+ def prep (size, additional)
188
+ @minalign = size if size > @minalign
189
+ align = ((~(@buf.bytesize + additional)) + 1) & (size - 1)
190
+ @buf.prepend("\0".b * align) if align > 0
191
+ end
192
+
193
+ # @!visibility private
194
+ def u8 (x); @buf.prepend([x].pack("C")); end
195
+ # @!visibility private
196
+ def i16 (x); @buf.prepend([x].pack("s<")); end
197
+ # @!visibility private
198
+ def u16 (x); @buf.prepend([x].pack("v")); end
199
+ # @!visibility private
200
+ def u32 (x); @buf.prepend([x].pack("V")); end
201
+ # @!visibility private
202
+ def i32 (x); @buf.prepend([x].pack("l<")); end
203
+ # @!visibility private
204
+ def i64 (x); @buf.prepend([x].pack("q<")); end
205
+
206
+ # @!visibility private
207
+ def uoffset (off)
208
+ prep(4, 0)
209
+ u32(offset() - off + 4)
210
+ end
211
+
212
+ # @!visibility private
213
+ def slot (v); @vt[v] = offset(); end
214
+
215
+ # @!visibility private
216
+ def slot_i16 (v, x); prep(2,0); i16(x); slot(v); end
217
+ # @!visibility private
218
+ def slot_u8 (v, x); prep(1,0); u8(x); slot(v); end
219
+ # @!visibility private
220
+ def slot_i32 (v, x); prep(4,0); i32(x); slot(v); end
221
+ # @!visibility private
222
+ def slot_i64 (v, x); prep(8,0); i64(x); slot(v); end
223
+ # @!visibility private
224
+ def slot_bool(v, x); prep(1,0); u8(x ? 1 : 0); slot(v); end
225
+ # @!visibility private
226
+ def slot_uoffset (v, off); uoffset(off); slot(v); end
227
+
228
+ # @!visibility private
229
+ def start_object (n); @vt = Array.new(n, 0); @obj_start = offset(); end
230
+
231
+ # @!visibility private
232
+ def end_object
233
+ prep(4, 0)
234
+ i32(0) # soffset placeholder
235
+ obj = offset()
236
+ n = @vt.length
237
+ n -= 1 while n > 0 && @vt[n-1] == 0
238
+ (n-1).downto(0) do |i|
239
+ u16(@vt[i] == 0 ? 0 : (obj - @vt[i]))
240
+ end
241
+ u16(obj - @obj_start) # referenced table size
242
+ u16((n + 2) * 2) # vtable size
243
+ soff = offset() - obj
244
+ @buf[@buf.bytesize - obj, 4] = [soff].pack("l<")
245
+ obj
246
+ end
247
+
248
+ # @!visibility private
249
+ def start_vector (elem, count, align); prep(4, elem*count); prep(align, elem*count); end
250
+ # @!visibility private
251
+ def end_vector (count); u32(count); offset(); end
252
+
253
+ # @!visibility private
254
+ def finish (root)
255
+ prep(@minalign, 4)
256
+ uoffset(root)
257
+ @buf
258
+ end
259
+ end
260
+
261
+ def build_metadata (arrow, shape, strides, body_len)
262
+ fb = Builder.new
263
+ tag = arrow[0] == :float ? 3 : 2
264
+
265
+ type_off =
266
+ if arrow[0] == :float
267
+ fb.start_object(1)
268
+ fb.slot_i16(0, arrow[1]) # precision
269
+ fb.end_object
270
+ else
271
+ fb.start_object(2)
272
+ fb.slot_i32(0, arrow[1]) # bitWidth
273
+ fb.slot_bool(1, arrow[2]) # is_signed
274
+ fb.end_object
275
+ end
276
+
277
+ dim_offs = shape.map do |sz|
278
+ fb.start_object(2)
279
+ fb.slot_i64(0, sz) # TensorDim.size (name omitted)
280
+ fb.end_object
281
+ end
282
+ fb.start_vector(4, dim_offs.length, 4)
283
+ dim_offs.reverse_each { |o| fb.uoffset(o) }
284
+ shape_vec = fb.end_vector(dim_offs.length)
285
+
286
+ fb.start_vector(8, strides.length, 8)
287
+ strides.reverse_each { |s| fb.i64(s) }
288
+ strides_vec = fb.end_vector(strides.length)
289
+
290
+ fb.start_object(5) # Tensor: type_type,type,shape,strides,data
291
+ fb.prep(8, 16) # data: inline Buffer struct
292
+ fb.i64(body_len) # length
293
+ fb.i64(0) # offset
294
+ fb.slot(4)
295
+ fb.slot_uoffset(3, strides_vec)
296
+ fb.slot_uoffset(2, shape_vec)
297
+ fb.slot_uoffset(1, type_off)
298
+ fb.slot_u8(0, tag) # type_type
299
+ tensor_off = fb.end_object
300
+
301
+ fb.start_object(5) # Message: version,header_type,header,bodyLength,custom
302
+ fb.slot_i64(3, body_len)
303
+ fb.slot_uoffset(2, tensor_off)
304
+ fb.slot_u8(1, 4) # header_type = Tensor
305
+ fb.slot_i16(0, 4) # metadata version V5
306
+ msg_off = fb.end_object
307
+
308
+ fb.finish(msg_off)
309
+ end
310
+ private_class_method :build_metadata
311
+
312
+ # ---- FlatBuffers Message decoder --------------------------------------
313
+
314
+ def parse_metadata (b)
315
+ u16 = ->(o){ b[o,2].unpack1("v") }
316
+ i32 = ->(o){ b[o,4].unpack1("l<") }
317
+ u32 = ->(o){ b[o,4].unpack1("V") }
318
+ i64 = ->(o){ b[o,8].unpack1("q<") }
319
+ field = ->(t,f){ vt = t - i32.(t); vts = u16.(vt); slot = 4 + f*2
320
+ next 0 if slot >= vts; vo = u16.(vt+slot); vo==0 ? 0 : t+vo }
321
+ deref = ->(pos){ pos + u32.(pos) }
322
+
323
+ raise ArgumentError, "not an Arrow IPC message" unless u32.(0) == 0xffffffff
324
+ meta_len = i32.(4)
325
+ fb = 8
326
+ body_off = 8 + meta_len
327
+
328
+ msg = fb + i32.(fb)
329
+ htag = (p = field.(msg,1); p == 0 ? 0 : b.getbyte(p))
330
+ raise ArgumentError, "not a tensor message" unless htag == 4
331
+ hpos = deref.(field.(msg,2))
332
+
333
+ type_tag = b.getbyte(field.(hpos,0))
334
+ type_tbl = deref.(field.(hpos,1))
335
+ shp_vec = deref.(field.(hpos,2)); n = u32.(shp_vec)
336
+ shape = (0...n).map { |i| dp = deref.(shp_vec + 4 + i*4); i64.(field.(dp,0)) }
337
+ sv_slot = field.(hpos,3)
338
+ strides =
339
+ if sv_slot == 0
340
+ row_major_strides(shape, CArray.new(arrow_symbol(type_tag, type_tbl, u16, i32, b), [1]).bytes)
341
+ else
342
+ sv = deref.(sv_slot); m = u32.(sv); (0...m).map { |i| i64.(sv + 4 + i*8) }
343
+ end
344
+ dpos = field.(hpos,4)
345
+ data_off = i64.(dpos); data_len = i64.(dpos+8)
346
+
347
+ { data_type: arrow_symbol(type_tag, type_tbl, u16, i32, b),
348
+ shape: shape, strides: strides,
349
+ body: b[body_off + data_off, data_len] }
350
+ end
351
+ private_class_method :parse_metadata
352
+
353
+ def arrow_symbol (tag, tbl, u16, i32, b)
354
+ case tag
355
+ when 3
356
+ { 0=>:float16, 1=>:float32, 2=>:float64 }[u16.(tbl_field(tbl, 0, i32, u16))] ||
357
+ (raise ArgumentError, "unsupported Arrow float precision")
358
+ when 2
359
+ bw = i32.(tbl_field(tbl, 0, i32, u16))
360
+ sf = tbl_field(tbl, 1, i32, u16)
361
+ signed = sf == 0 ? false : (b.getbyte(sf) != 0) # is_signed FlatBuffers default = false
362
+ (signed ? "int#{bw}" : "uint#{bw}").to_sym
363
+ else
364
+ raise ArgumentError, "unsupported Arrow tensor element type (tag=#{tag})"
365
+ end
366
+ end
367
+ private_class_method :arrow_symbol
368
+
369
+ def tbl_field (t, f, i32, u16)
370
+ vt = t - i32.(t); vts = u16.(vt); slot = 4 + f*2
371
+ return 0 if slot >= vts
372
+ vo = u16.(vt + slot); vo == 0 ? 0 : t + vo
373
+ end
374
+ private_class_method :tbl_field
375
+
376
+ end
377
+
378
+ # Reads an Arrow tensor IPC file and returns it as a new CArray.
379
+ #
380
+ # Experimental, and the name is provisional.
381
+ #
382
+ # @param filename [String] path to the message.
383
+ # @return [CArray]
384
+ # @see ArrowTensor
385
+ def self.load_arrow_tensor (filename)
386
+ File.open(filename, "rb") { |io| ArrowTensor.read(io) }
387
+ end
388
+
389
+ # Writes `self` to `filename` as an Arrow tensor IPC message.
390
+ #
391
+ # Experimental, and the name is provisional. Rejects a masked or
392
+ # non-numeric array; see {ArrowTensor} for the type policy.
393
+ #
394
+ # @param filename [String] path to write.
395
+ # @return [self]
396
+ # @see ArrowTensor
397
+ def save_arrow_tensor (filename)
398
+ File.open(filename, "wb") { |io| ArrowTensor.write(self, io) }
399
+ self
400
+ end
401
+ end
@@ -0,0 +1,166 @@
1
+ # User-defined arbitrary key/value metadata attached to a CArray
2
+ # instance.
3
+ #
4
+ # Surface:
5
+ # ca.attr(:unit) # => getter; nil if absent
6
+ # ca.set_attr(:unit, "m/s") # => setter; writes to self's @attr
7
+ # ca.attrs # => frozen shallow clone of the full Hash
8
+ # ca.has_attr? # => any attribute present?
9
+ # ca.has_attr?(:unit) # => specific key present?
10
+ #
11
+ # Key: accepts Symbol or String on input; stored as String.
12
+ # Value: JSON-compatible plus non-finite Floats (String / Numeric,
13
+ # including Infinity / NaN / -Infinity / true / false / nil /
14
+ # Array / Hash<String|Symbol, ...>). Symbol values are
15
+ # coerced to String on store. Disallowed types raise
16
+ # TypeError on set. Non-finite Floats are accepted so
17
+ # imported metadata (e.g. a NetCDF _FillValue of Infinity)
18
+ # is not rejected; the serialize trailer is YAML, whose
19
+ # .inf / .nan literals round-trip them (serialize.rb).
20
+ #
21
+ # View semantics (per-key fallback):
22
+ # Getter side (attr / has_attr?) walks the parent chain
23
+ # *per key*: if the receiver's @attr has the key, return it;
24
+ # otherwise walk up. Each view shadows its parents one key at
25
+ # a time, so writing :slice on a view does not hide :unit
26
+ # inherited from the parent entity.
27
+ # `attrs` returns the effective merged Hash (deeper writes
28
+ # override shallower ones), frozen.
29
+ # Setter side always writes to self's @attr.
30
+
31
+ class CArray
32
+
33
+ # @!group Attributes
34
+
35
+ # @overload attr(key)
36
+ # Returns the value of the attribute `key`, or `nil` when the key
37
+ # is absent. Walks the parent chain per key: the deepest view
38
+ # that has an entry for `key` wins.
39
+ # @param key [Symbol, String] attribute key.
40
+ # @return [Object, nil]
41
+ def attr (key)
42
+ k = attr_normalize_key(key)
43
+ attr_each_chain do |h|
44
+ return h[k] if h.key?(k)
45
+ end
46
+ nil
47
+ end
48
+
49
+ # @overload set_attr(key, value)
50
+ # Sets attribute `key` to `value` on `self` (not on any parent).
51
+ # Values are validated as JSON-compatible plus non-finite Floats
52
+ # (String / Numeric incl. Infinity / NaN / true / false / nil /
53
+ # Symbol / Array / Hash); Symbol values are coerced to String on
54
+ # store.
55
+ # @param key [Symbol, String] attribute key.
56
+ # @param value [Object] JSON-compatible value.
57
+ # @return [Object] the coerced stored value.
58
+ # @raise [TypeError] when `key` or `value` is not accepted.
59
+ def set_attr (key, value)
60
+ attr_validate_value(value)
61
+ (@attr ||= {})[attr_normalize_key(key)] = attr_coerce_value(value)
62
+ end
63
+
64
+ # @overload attrs
65
+ # Returns a frozen shallow Hash of all attributes visible on
66
+ # `self`, merged along the parent chain (deeper writes shadow
67
+ # shallower ones on a per-key basis).
68
+ # @return [Hash{String => Object}]
69
+ def attrs
70
+ merged = nil
71
+ attr_each_chain do |h|
72
+ merged ||= {}
73
+ h.each { |k, v| merged[k] = v unless merged.key?(k) }
74
+ end
75
+ (merged || {}).freeze
76
+ end
77
+
78
+ # @overload has_attr?
79
+ # Returns whether `self` (or any parent view) has any attribute
80
+ # set.
81
+ # @return [Boolean]
82
+ # @overload has_attr?(key)
83
+ # Returns whether `self` (or any parent view) has attribute `key`
84
+ # set.
85
+ # @param key [Symbol, String] attribute key.
86
+ # @return [Boolean]
87
+ def has_attr? (key = nil)
88
+ if key.nil?
89
+ attr_each_chain do |h|
90
+ return true unless h.empty?
91
+ end
92
+ false
93
+ else
94
+ k = attr_normalize_key(key)
95
+ attr_each_chain do |h|
96
+ return true if h.key?(k)
97
+ end
98
+ false
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ # Yield each non-nil @attr Hash along the parent chain (self first).
105
+ # Bounded to MAX_CHAIN_DEPTH for paranoia against unexpected cycles;
106
+ # real CArray view chains rarely exceed a handful of levels.
107
+ MAX_CHAIN_DEPTH = 64
108
+ private_constant :MAX_CHAIN_DEPTH
109
+
110
+ def attr_each_chain
111
+ ca = self
112
+ MAX_CHAIN_DEPTH.times do
113
+ if ca.instance_variable_defined?(:@attr)
114
+ h = ca.instance_variable_get(:@attr)
115
+ yield(h) if h
116
+ end
117
+ parent = ca.respond_to?(:parent) ? ca.parent : nil
118
+ return nil if parent.nil? || parent.equal?(ca)
119
+ ca = parent
120
+ end
121
+ nil
122
+ end
123
+
124
+ def attr_normalize_key (key)
125
+ case key
126
+ when String then key
127
+ when Symbol then key.to_s
128
+ else
129
+ raise TypeError, "attribute key must be String or Symbol (got #{key.class})"
130
+ end
131
+ end
132
+
133
+ def attr_validate_value (v, depth = 0)
134
+ raise ArgumentError, "attribute value nests too deep" if depth > 32
135
+ case v
136
+ when String, Integer, Float, true, false, nil, Symbol
137
+ # ok (Symbol will be coerced to String on store)
138
+ when Array
139
+ v.each { |e| attr_validate_value(e, depth + 1) }
140
+ when Hash
141
+ v.each do |k, vv|
142
+ unless k.is_a?(String) || k.is_a?(Symbol)
143
+ raise TypeError, "nested Hash keys must be String or Symbol (got #{k.class})"
144
+ end
145
+ attr_validate_value(vv, depth + 1)
146
+ end
147
+ else
148
+ raise TypeError, "attribute value must be JSON-compatible " \
149
+ "(String/Numeric/true/false/nil/Symbol/Array/Hash); got #{v.class}"
150
+ end
151
+ end
152
+
153
+ def attr_coerce_value (v)
154
+ case v
155
+ when Symbol then v.to_s
156
+ when Array then v.map { |e| attr_coerce_value(e) }
157
+ when Hash
158
+ v.each_with_object({}) do |(k, vv), h|
159
+ h[k.is_a?(Symbol) ? k.to_s : k] = attr_coerce_value(vv)
160
+ end
161
+ else
162
+ v
163
+ end
164
+ end
165
+
166
+ end