carray 2.0.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -25
  3. data/CHANGELOG.md +338 -0
  4. data/{NEWS.md → CHANGELOG.v1.md} +3 -0
  5. data/LICENSE +1 -1
  6. data/README.md +120 -36
  7. data/carray.gemspec +32 -30
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1244 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +124 -0
  15. data/ext/ca_binop_dispatch.h +152 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +239 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4367 -0
  26. data/ext/ca_kernel_iterator.h +2596 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +839 -0
  33. data/ext/ca_obj_binop.c +948 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +967 -0
  39. data/ext/ca_obj_face.c +750 -0
  40. data/ext/ca_obj_face.h +279 -0
  41. data/ext/ca_obj_fake.c +239 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +308 -0
  45. data/ext/ca_obj_grid.c +866 -440
  46. data/ext/ca_obj_meld.c +1039 -0
  47. data/ext/ca_obj_moncmp.c +588 -0
  48. data/ext/ca_obj_monop.c +1123 -0
  49. data/ext/ca_obj_object.c +866 -296
  50. data/ext/ca_obj_record.c +470 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +593 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +624 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1306 -0
  58. data/ext/ca_obj_shift.c +231 -793
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2584 -0
  62. data/ext/ca_obj_string.c +270 -0
  63. data/ext/ca_obj_tile.c +622 -0
  64. data/ext/ca_obj_time.c +548 -0
  65. data/ext/ca_obj_timedelta.c +437 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +785 -0
  68. data/ext/ca_obj_window.c +1202 -565
  69. data/ext/ca_op_byte_swap.c +175 -0
  70. data/ext/ca_op_cmplx64.h +123 -0
  71. data/ext/ca_op_ipower.c +316 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +473 -0
  75. data/ext/ca_sweep_engine.h +166 -0
  76. data/ext/ca_transform_common.c +235 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +810 -420
  80. data/ext/carray_access.c +873 -731
  81. data/ext/carray_attribute.c +98 -329
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +376 -0
  84. data/ext/carray_build_flags.h +3 -0
  85. data/ext/carray_call_cfunc.c +2897 -874
  86. data/ext/carray_call_cfunc.h +313 -0
  87. data/ext/carray_cast.c +1264 -315
  88. data/ext/carray_cast_func.rb +81 -40
  89. data/ext/carray_class.c +53 -63
  90. data/ext/carray_config.h +28 -0
  91. data/ext/carray_conversion.c +350 -346
  92. data/ext/carray_copy.c +168 -270
  93. data/ext/carray_core.c +1396 -206
  94. data/ext/carray_count.c +312 -0
  95. data/ext/carray_data_type.c +43 -19
  96. data/ext/carray_element.c +585 -213
  97. data/ext/carray_factorize.c +2542 -0
  98. data/ext/carray_generate.c +230 -559
  99. data/ext/carray_histogram.c +490 -0
  100. data/ext/carray_hold.c +228 -0
  101. data/ext/carray_index_classifier.c +1021 -0
  102. data/ext/carray_index_classifier.h +27 -0
  103. data/ext/carray_internal.h +136 -0
  104. data/ext/carray_kernels_bincmp.c +4446 -0
  105. data/ext/carray_kernels_binop.c +11001 -0
  106. data/ext/carray_kernels_init.c +1131 -0
  107. data/ext/carray_kernels_map.c +3467 -0
  108. data/ext/carray_kernels_moncmp.c +2097 -0
  109. data/ext/carray_kernels_monop.c +18313 -0
  110. data/ext/carray_kernels_reduce_aggregate.c +25837 -0
  111. data/ext/carray_kernels_reduce_boolean.c +330 -0
  112. data/ext/carray_kernels_reduce_cumulative.c +14593 -0
  113. data/ext/carray_kernels_reduce_extreme.c +16948 -0
  114. data/ext/carray_kernels_reduce_variance.c +3910 -0
  115. data/ext/carray_kernels_scan.c +3693 -0
  116. data/ext/carray_kernels_search.c +32138 -0
  117. data/ext/carray_kernels_sort.c +10626 -0
  118. data/ext/carray_kernels_triop.c +1392 -0
  119. data/ext/carray_lazy.c +737 -0
  120. data/ext/carray_loop.c +88 -200
  121. data/ext/carray_mask.c +853 -158
  122. data/ext/carray_math_kernel.h +120 -0
  123. data/ext/carray_mathfunc.c +10 -241
  124. data/ext/carray_median_percentile.c +1257 -0
  125. data/ext/carray_memory_view.c +1650 -0
  126. data/ext/carray_operator.c +1525 -320
  127. data/ext/carray_order.c +664 -1394
  128. data/ext/carray_partition.c +416 -0
  129. data/ext/carray_random.c +518 -0
  130. data/ext/carray_scatter.c +357 -0
  131. data/ext/carray_slab.c +1219 -0
  132. data/ext/carray_slab.h +84 -0
  133. data/ext/carray_sort.c +829 -0
  134. data/ext/carray_sort_kernel.c +620 -0
  135. data/ext/carray_struct.c +695 -0
  136. data/ext/carray_test.c +343 -229
  137. data/ext/carray_undef.c +34 -17
  138. data/ext/carray_utils.c +175 -74
  139. data/ext/extconf.rb +234 -55
  140. data/ext/mk_call_cfunc.rb +671 -0
  141. data/ext/mkkernel.rb +9096 -0
  142. data/ext/ruby_carray.c +211 -108
  143. data/ext/version.h +4 -14
  144. data/ext/version.rb +5 -13
  145. data/lib/carray/arrow_tensor.rb +401 -0
  146. data/lib/carray/attribute.rb +166 -0
  147. data/lib/carray/autoload_carray.rb +239 -0
  148. data/lib/carray/autoload_method_extension.rb +45 -0
  149. data/lib/carray/axis_group.rb +711 -0
  150. data/lib/carray/basics.rb +481 -0
  151. data/lib/carray/bincount_nd.rb +358 -0
  152. data/lib/carray/block_iterator.rb +614 -0
  153. data/lib/carray/boolean_reduce.rb +109 -0
  154. data/lib/carray/categorical.rb +561 -0
  155. data/lib/carray/categorical_iterator.rb +1084 -0
  156. data/lib/carray/complex.rb +150 -0
  157. data/lib/carray/conditional.rb +216 -0
  158. data/lib/carray/const_string.rb +228 -0
  159. data/lib/carray/construct.rb +160 -328
  160. data/lib/carray/core_extensions.rb +297 -0
  161. data/lib/carray/data_type_extension.rb +250 -0
  162. data/lib/carray/fixlen_string.rb +95 -0
  163. data/lib/carray/frame/concat.rb +132 -0
  164. data/lib/carray/frame/convert.rb +95 -0
  165. data/lib/carray/frame/csv_parser.rb +211 -0
  166. data/lib/carray/frame/frame.rb +642 -0
  167. data/lib/carray/frame/group.rb +186 -0
  168. data/lib/carray/frame/io.rb +321 -0
  169. data/lib/carray/frame/join.rb +248 -0
  170. data/lib/carray/frame/records.rb +99 -0
  171. data/lib/carray/frame/sort.rb +113 -0
  172. data/lib/carray/frame/verbs.rb +316 -0
  173. data/lib/carray/frame.rb +16 -0
  174. data/lib/carray/fuse_source.rb +123 -0
  175. data/lib/carray/fusion.rb +218 -0
  176. data/lib/carray/histogram.rb +512 -0
  177. data/lib/carray/inspect.rb +37 -26
  178. data/lib/carray/iterator.rb +58 -349
  179. data/lib/carray/lazy.rb +941 -0
  180. data/lib/carray/mask_gap_fill.rb +200 -0
  181. data/lib/carray/math.rb +78 -342
  182. data/lib/carray/meld_reduce.rb +289 -0
  183. data/lib/carray/methods/align_addr.rb +116 -0
  184. data/lib/carray/methods/bin.rb +128 -0
  185. data/lib/carray/methods/bincount.rb +87 -0
  186. data/lib/carray/methods/bit_string.rb +92 -0
  187. data/lib/carray/methods/broadcast.rb +63 -0
  188. data/lib/carray/methods/choose.rb +39 -0
  189. data/lib/carray/methods/composition.rb +280 -0
  190. data/lib/carray/methods/gather_nd.rb +206 -0
  191. data/lib/carray/methods/index.rb +39 -0
  192. data/lib/carray/methods/insert_block.rb +99 -0
  193. data/lib/carray/methods/is_in.rb +141 -0
  194. data/lib/carray/methods/join.rb +90 -0
  195. data/lib/carray/methods/locate_addr.rb +52 -0
  196. data/lib/carray/methods/mask_duplicates.rb +41 -0
  197. data/lib/carray/methods/meshgrid.rb +90 -0
  198. data/lib/carray/methods/mode.rb +126 -0
  199. data/lib/carray/methods/nunique.rb +46 -0
  200. data/lib/carray/methods/resize.rb +56 -0
  201. data/lib/carray/methods/snap.rb +161 -0
  202. data/lib/carray/methods/string_format.rb +57 -0
  203. data/lib/carray/methods/unique.rb +47 -0
  204. data/lib/carray/methods/value_counts.rb +71 -0
  205. data/lib/carray/mkmf.rb +124 -101
  206. data/lib/carray/runtime.rb +89 -0
  207. data/lib/carray/serialize.rb +478 -167
  208. data/lib/carray/slab_iterator.rb +305 -0
  209. data/lib/carray/stack.rb +291 -0
  210. data/lib/carray/string.rb +56 -180
  211. data/lib/carray/string_operation_extension.rb +289 -0
  212. data/lib/carray/struct.rb +335 -323
  213. data/lib/carray/struct_builder.rb +697 -0
  214. data/lib/carray/table.rb +41 -2
  215. data/lib/carray/time.rb +2654 -38
  216. data/lib/carray/window_iterator.rb +927 -0
  217. data/lib/carray.rb +55 -57
  218. data/yard-stubs/ca_obj_array.rb +385 -0
  219. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  220. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  221. data/yard-stubs/ca_obj_block.rb +73 -0
  222. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  223. data/yard-stubs/ca_obj_fake.rb +31 -0
  224. data/yard-stubs/ca_obj_farray.rb +32 -0
  225. data/yard-stubs/ca_obj_field.rb +45 -0
  226. data/yard-stubs/ca_obj_grid.rb +35 -0
  227. data/yard-stubs/ca_obj_refer.rb +72 -0
  228. data/yard-stubs/ca_obj_roll.rb +45 -0
  229. data/yard-stubs/ca_obj_shift.rb +43 -0
  230. data/yard-stubs/ca_obj_stride.rb +181 -0
  231. data/yard-stubs/ca_obj_tile.rb +29 -0
  232. data/yard-stubs/ca_obj_transpose.rb +40 -0
  233. data/yard-stubs/ca_obj_window.rb +49 -0
  234. data/yard-stubs/carray_access.rb +131 -0
  235. data/yard-stubs/carray_attribute.rb +246 -0
  236. data/yard-stubs/carray_broadcast.rb +37 -0
  237. data/yard-stubs/carray_cast.rb +489 -0
  238. data/yard-stubs/carray_class.rb +65 -0
  239. data/yard-stubs/carray_conversion.rb +76 -0
  240. data/yard-stubs/carray_copy.rb +79 -0
  241. data/yard-stubs/carray_core.rb +114 -0
  242. data/yard-stubs/carray_count.rb +79 -0
  243. data/yard-stubs/carray_element.rb +108 -0
  244. data/yard-stubs/carray_generate.rb +66 -0
  245. data/yard-stubs/carray_lazy.rb +23 -0
  246. data/yard-stubs/carray_loop.rb +140 -0
  247. data/yard-stubs/carray_mask.rb +259 -0
  248. data/yard-stubs/carray_math.rb +132 -0
  249. data/yard-stubs/carray_mathfunc.rb +45 -0
  250. data/yard-stubs/carray_median_percentile.rb +89 -0
  251. data/yard-stubs/carray_memory_view.rb +163 -0
  252. data/yard-stubs/carray_order.rb +312 -0
  253. data/yard-stubs/carray_random.rb +89 -0
  254. data/yard-stubs/carray_scatter.rb +106 -0
  255. data/yard-stubs/carray_slab.rb +57 -0
  256. data/yard-stubs/carray_sort.rb +163 -0
  257. data/yard-stubs/carray_test.rb +85 -0
  258. data/yard-stubs/carray_undef.rb +64 -0
  259. data/yard-stubs/carray_utils.rb +97 -0
  260. data/yard-stubs/ruby_carray.rb +193 -0
  261. metadata +220 -138
  262. data/Rakefile +0 -51
  263. data/TODO.md +0 -18
  264. data/ext/ca_iter_block.c +0 -257
  265. data/ext/ca_iter_dimension.c +0 -299
  266. data/ext/ca_iter_window.c +0 -214
  267. data/ext/ca_obj_mapping.c +0 -644
  268. data/ext/ca_obj_unbound_repeat.c +0 -529
  269. data/ext/carray_iterator.c +0 -641
  270. data/ext/carray_math.rb +0 -850
  271. data/ext/carray_numeric.c +0 -259
  272. data/ext/carray_sort_addr.c +0 -254
  273. data/ext/carray_stat.c +0 -2100
  274. data/ext/carray_stat_proc.rb +0 -1999
  275. data/ext/mkmath.rb +0 -741
  276. data/ext/ruby_ccomplex.c +0 -509
  277. data/ext/ruby_float_func.c +0 -86
  278. data/lib/carray/array.rb +0 -8
  279. data/lib/carray/autoload/autoload_base.rb +0 -19
  280. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  281. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  282. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  283. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  284. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  285. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  286. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  287. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  288. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  289. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  290. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  291. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  292. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  293. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  294. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  295. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  296. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  297. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  298. data/lib/carray/autoload.rb +0 -141
  299. data/lib/carray/basic.rb +0 -191
  300. data/lib/carray/broadcast.rb +0 -101
  301. data/lib/carray/compose.rb +0 -315
  302. data/lib/carray/convert.rb +0 -115
  303. data/lib/carray/info.rb +0 -110
  304. data/lib/carray/io/imagemagick.rb +0 -235
  305. data/lib/carray/mask.rb +0 -102
  306. data/lib/carray/math/histogram.rb +0 -177
  307. data/lib/carray/math/recurrence.rb +0 -93
  308. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  309. data/lib/carray/object/ca_obj_link.rb +0 -50
  310. data/lib/carray/object/ca_obj_pack.rb +0 -99
  311. data/lib/carray/obsolete.rb +0 -256
  312. data/lib/carray/ordering.rb +0 -181
  313. data/lib/carray/testing.rb +0 -51
  314. data/lib/carray/transform.rb +0 -109
  315. data/mailmap +0 -1
  316. data/misc/Methods.ja.md +0 -182
  317. data/misc/NOTE +0 -51
  318. data/spec/Classes/CABitfield_spec.rb +0 -58
  319. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  320. data/spec/Classes/CABlock_spec.rb +0 -205
  321. data/spec/Classes/CAField_spec.rb +0 -39
  322. data/spec/Classes/CAGrid_spec.rb +0 -75
  323. data/spec/Classes/CAMap_spec.rb +0 -0
  324. data/spec/Classes/CAMapping_spec.rb +0 -105
  325. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  326. data/spec/Classes/CAObject_spec.rb +0 -33
  327. data/spec/Classes/CARefer_spec.rb +0 -93
  328. data/spec/Classes/CARepeat_spec.rb +0 -65
  329. data/spec/Classes/CASelect_spec.rb +0 -22
  330. data/spec/Classes/CAShift_spec.rb +0 -16
  331. data/spec/Classes/CAStruct_spec.rb +0 -71
  332. data/spec/Classes/CATranspose_spec.rb +0 -60
  333. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  334. data/spec/Classes/CAWindow_spec.rb +0 -54
  335. data/spec/Classes/CAWrap_spec.rb +0 -8
  336. data/spec/Classes/CArray_spec.rb +0 -184
  337. data/spec/Classes/CScalar_spec.rb +0 -55
  338. data/spec/Features/feature_130_spec.rb +0 -19
  339. data/spec/Features/feature_attributes_spec.rb +0 -280
  340. data/spec/Features/feature_boolean_spec.rb +0 -98
  341. data/spec/Features/feature_broadcast.rb +0 -116
  342. data/spec/Features/feature_cast_function.rb +0 -19
  343. data/spec/Features/feature_cast_spec.rb +0 -33
  344. data/spec/Features/feature_class_spec.rb +0 -84
  345. data/spec/Features/feature_complex_spec.rb +0 -42
  346. data/spec/Features/feature_composite_spec.rb +0 -124
  347. data/spec/Features/feature_convert_spec.rb +0 -46
  348. data/spec/Features/feature_copy_spec.rb +0 -123
  349. data/spec/Features/feature_creation_spec.rb +0 -84
  350. data/spec/Features/feature_element_spec.rb +0 -144
  351. data/spec/Features/feature_extream_spec.rb +0 -54
  352. data/spec/Features/feature_generate_spec.rb +0 -74
  353. data/spec/Features/feature_index_spec.rb +0 -69
  354. data/spec/Features/feature_mask_spec.rb +0 -580
  355. data/spec/Features/feature_math_spec.rb +0 -97
  356. data/spec/Features/feature_order_spec.rb +0 -146
  357. data/spec/Features/feature_ref_store_spec.rb +0 -209
  358. data/spec/Features/feature_serialization_spec.rb +0 -125
  359. data/spec/Features/feature_stat_spec.rb +0 -397
  360. data/spec/Features/feature_virtual_spec.rb +0 -48
  361. data/spec/Features/method_eq_spec.rb +0 -81
  362. data/spec/Features/method_is_nan_spec.rb +0 -12
  363. data/spec/Features/method_map_spec.rb +0 -54
  364. data/spec/Features/method_max_with.rb +0 -20
  365. data/spec/Features/method_min_with.rb +0 -19
  366. data/spec/Features/method_ne_spec.rb +0 -18
  367. data/spec/Features/method_project_spec.rb +0 -188
  368. data/spec/Features/method_ref_spec.rb +0 -27
  369. data/spec/Features/method_round_spec.rb +0 -11
  370. data/spec/Features/method_s_linspace_spec.rb +0 -48
  371. data/spec/Features/method_s_span_spec.rb +0 -14
  372. data/spec/Features/method_seq_spec.rb +0 -47
  373. data/spec/Features/method_sort_with.rb +0 -43
  374. data/spec/Features/method_sorted_with.rb +0 -29
  375. data/spec/Features/method_span_spec.rb +0 -42
  376. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  377. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  378. data/spec/spec_all.rb +0 -10
  379. data/utils/ca_ase.rb +0 -21
  380. data/utils/ca_methods.rb +0 -15
  381. data/utils/cast_checker.rb +0 -30
  382. data/utils/convert_test.rb +0 -73
  383. data/utils/extract_yard.rb +0 -22
  384. data/utils/guess_shape.rb +0 -76
  385. data/utils/monkey_patch_methods.rb +0 -62
  386. data/utils/remove_resource_fork.sh +0 -5
@@ -0,0 +1,316 @@
1
+ # CAFrame column verbs (memo §11.4, §12-A, §13.2).
2
+ #
3
+ # Each verb touches this frame's own columns Hash (per-frame, §12-B) and
4
+ # returns self so calls chain. Column data is still shared with any parent
5
+ # (view-frame), but membership — which columns this frame names — is local:
6
+ # append/drop/rename here never change a parent's column set.
7
+
8
+ class CAFrame
9
+ # Add (or replace) a column, returning a new frame — the column set changes,
10
+ # so the result is a different table (memo §3.8). The column is shared by
11
+ # reference; the envelope is cheap. Chain or reassign: +df = df.append(...)+.
12
+ def append(name, col)
13
+ key = name.to_s
14
+ ca = coerce_column(col)
15
+ rebuild(@columns.merge(key => ca))
16
+ end
17
+
18
+ # Remove one or more columns, returning a new frame (column set changes,
19
+ # memo §3.8). Column data is untouched and shared with the original.
20
+ def drop(*names)
21
+ cols = @columns.dup
22
+ names.each do |name|
23
+ key = name.to_s
24
+ raise KeyError, "no column #{key.inspect}" unless cols.key?(key)
25
+ cols.delete(key)
26
+ end
27
+ rebuild(cols)
28
+ end
29
+
30
+ # Rename columns, returning a new frame (column names change, memo §3.8).
31
+ # Column order is preserved (memo §13.2 "column order = insertion order");
32
+ # columns are shared by reference.
33
+ def rename(mapping)
34
+ norm = {}
35
+ mapping.each do |old, new|
36
+ o = old.to_s
37
+ n = new.to_s
38
+ raise KeyError, "no column #{o.inspect}" unless @columns.key?(o)
39
+ if n != o && @columns.key?(n)
40
+ raise ArgumentError, "rename target #{n.inspect} already exists"
41
+ end
42
+ norm[o] = n
43
+ end
44
+ rebuilt = {}
45
+ @columns.each { |k, v| rebuilt[norm[k] || k] = v }
46
+ rebuild(rebuilt)
47
+ end
48
+
49
+ # Mask cells of a column that equal +value+ (sentinel -> mask, memo §11.4).
50
+ # In-place on the column (write-through, §4.3): numeric/object columns
51
+ # mask in place; a categorical column's codes are read-only (§13.4) so this
52
+ # raises — recode by rebuilding and rebinding instead.
53
+ def mask_eq(name, value)
54
+ self[name][:eq, value] = UNDEF
55
+ self
56
+ end
57
+
58
+ # Cast columns to a data type and rebind them (memo §11.4). Uses to_type,
59
+ # so parse failures on string columns become UNDEF (parse-mask, §6-2).
60
+ # Three call shapes, disambiguated by the fact that column names are always
61
+ # Strings and types always Symbols (§3.7):
62
+ #
63
+ # cast("temp", :float64) # one column (chains)
64
+ # cast("temp" => :float64, "rh" => :int32) # name => type map
65
+ # cast(["temp", "rh"] => :float64) # names sharing one type
66
+ #
67
+ # A map key may be a single name or an Array of names; the value is the
68
+ # target type. Returns self so calls chain.
69
+ def cast(name_or_map, type = nil)
70
+ if name_or_map.is_a?(Hash)
71
+ unless type.nil?
72
+ raise ArgumentError, "cast(map) takes no positional type argument"
73
+ end
74
+ name_or_map.each do |names, t|
75
+ Array(names).each { |name| cast_one(name, t) }
76
+ end
77
+ else
78
+ cast_one(name_or_map, type)
79
+ end
80
+ self
81
+ end
82
+
83
+ # Bring every column to one common data type and rebind them (memo §11.4).
84
+ # The frame-level counterpart of +CArray.promote_list+: where +cast+ forces
85
+ # named columns to a type, +promote+ widens the whole table until it has a
86
+ # single type. Returns self so calls chain (§3.8 -- a type-interpretation
87
+ # change), and like +cast+ it allocates fresh columns, so a parent frame
88
+ # sharing the old ones is untouched. The index is not a column and is left
89
+ # alone.
90
+ #
91
+ # df.promote # the common type CArray.result_type would pick
92
+ # df.promote(:object) # force the widest type: anything fits
93
+ #
94
+ # Without an argument the common type is whatever +promote_list+ picks --
95
+ # the same decision +to_ca+ / +CArray.stack+ make internally, so
96
+ # +df.promote+ is exactly "make this frame stackable". Columns that are
97
+ # already uniform (including a uniformly Face-typed frame) are left as they
98
+ # are.
99
+ #
100
+ # With an argument the type must be a widening for every column: promoting
101
+ # is not the place to lose values, so a narrowing target raises and points
102
+ # at +cast+, which is the verb that forces. +:object+ is the widest type
103
+ # and therefore always accepted -- it is the way a frame mixing text,
104
+ # Face-typed and numeric columns becomes a single-type table (and so the
105
+ # way +to_ca+ can hand back a matrix for it).
106
+ def promote(type = nil)
107
+ return self if @columns.empty?
108
+ type.nil? ? promote_to_common : promote_to_type(type)
109
+ self
110
+ end
111
+
112
+ # Parse a string column into a time column and rebind it (memo §11.2).
113
+ # The column must be string-bearing (an object CArray of Strings, or a
114
+ # CAString / CAConstString / CAFixlenString) — this is the "text -> time"
115
+ # mode, distinct from the integer-serial mode of +to_time+. Delegates to
116
+ # +CArray.time(col, on_error: :mask)+: +format+ picks strptime parsing
117
+ # (auto-detect when nil), +unit+ the storage resolution; masked / nil and
118
+ # unparseable cells become UNDEF (bulk column parse tolerates bad cells).
119
+ # Make it the index with +set_index+ afterward.
120
+ #
121
+ # df.parse_to_time("time").set_index("time")
122
+ def parse_to_time(name, format = nil, unit: :s)
123
+ key = name.to_s
124
+ col = @columns.fetch(key) { raise KeyError, "no column #{key.inspect}" }
125
+ unless string_column?(col)
126
+ raise ArgumentError,
127
+ "parse_to_time needs a string column (object / CAString / " \
128
+ "CAConstString / CAFixlenString); #{key.inspect} is #{col.data_type}"
129
+ end
130
+ @columns[key] = CArray.time(col, format: format, unit: unit, on_error: :mask)
131
+ self
132
+ end
133
+
134
+ # Reinterpret an integer column as time serial counts and rebind it (memo
135
+ # §11.2). This is the "serial -> time" mode: each value is a count of
136
+ # +unit+ resolution since +epoch+ (default the Unix epoch, 1970-01-01 UTC).
137
+ # +epoch+ takes any time literal (String / Time / Integer), so a column
138
+ # measured from another origin — a netCDF "hours since 1990-01-01" axis, an
139
+ # Excel serial date (epoch "1899-12-30", unit :D) — converts directly.
140
+ #
141
+ # A float column is accepted only when every value is whole (no fractional
142
+ # part); a fractional serial has sub-unit precision that a finer +unit+ should
143
+ # carry, so it raises rather than silently truncate. Make it the index with
144
+ # +set_index+ afterward.
145
+ #
146
+ # A +CATime::Grid+ carries the same (unit, epoch) pair as one value, so a
147
+ # netCDF +units+ attribute goes straight in. It also carries a phase the
148
+ # keyword form cannot: the keyword +epoch+ is read on the +unit+ grid, so
149
+ # an epoch off that grid ("days since 1980-01-01 12:00") loses its
150
+ # time-of-day, while a grid resolves the finer storage that holds it.
151
+ #
152
+ # df.to_time("time", unit: :h, epoch: "1990-01-01").set_index("time")
153
+ # df.to_time("time", CATime::Grid.parse("hours since 1990-01-01"))
154
+ # df.to_time("time", CATime::Grid.parse("days since 1980-01-01 12:00"))
155
+ def to_time(name, grid = nil, unit: :s, epoch: nil)
156
+ key = name.to_s
157
+ col = @columns.fetch(key) { raise KeyError, "no column #{key.inspect}" }
158
+ raw = integer_serial_column(col, key)
159
+ grid = unit if unit.is_a?(CATime::Grid)
160
+ if grid.is_a?(CATime::Grid)
161
+ @columns[key] = grid.at(raw)
162
+ return self
163
+ end
164
+ unless grid.nil?
165
+ raise ArgumentError,
166
+ "the positional argument must be a CATime::Grid (got #{grid.class})"
167
+ end
168
+ if epoch
169
+ raw = raw + CArray.time(epoch, unit: unit).ticks[0]
170
+ end
171
+ @columns[key] = raw.time(unit: unit)
172
+ self
173
+ end
174
+
175
+ # Fill masked cells of a column (memo §6, §11.8). A Symbol selects a
176
+ # scan method; anything else is a constant fill value. The fill is
177
+ # write-through: it edits the live shared column rather than rebinding a
178
+ # filled copy (memo §3.8). Returns self so calls chain.
179
+ #
180
+ # fill("temp", :ffill) # forward hold (carry last valid value)
181
+ # fill("temp", :bfill) # backward hold (carry next valid value)
182
+ # fill("temp", :linear) # linear interpolation; x = this frame's index
183
+ # # coordinate when present, else the cell position
184
+ # fill("temp", 0.0) # constant fill
185
+ #
186
+ # :ffill / :bfill work for any writable column data_type (numeric, time,
187
+ # object, fixlen, …); :linear needs a numeric or time (CATime /
188
+ # CATimedelta) column. A categorical column's codes are read-only
189
+ # (memo §13.4), so any fill on it raises — rebind a filled copy instead:
190
+ # `df.append(name, df[name].strip_mask(method: :forward))`.
191
+ def fill(name, method_or_value)
192
+ key = name.to_s
193
+ raise KeyError, "no column #{key.inspect}" unless @columns.key?(key)
194
+ col = @columns[key]
195
+ case method_or_value
196
+ when :ffill then col.unmask(method: :forward)
197
+ when :bfill then col.unmask(method: :backward)
198
+ when :linear then fill_linear_column(key, col)
199
+ when Symbol
200
+ raise ArgumentError,
201
+ "fill: unknown method #{method_or_value.inspect} " \
202
+ "(:ffill | :bfill | :linear, or a constant fill value)"
203
+ else
204
+ col.unmask(method_or_value) # constant fill, in place
205
+ end
206
+ self
207
+ end
208
+
209
+ private def fill_linear_column(key, col)
210
+ # A time column interpolates through CATime#linear_fetch /
211
+ # CATimedelta#linear_fetch, which keep the Face and its unit.
212
+ time_face = col.is_a?(CATime) || col.is_a?(CATimedelta)
213
+ unless col.numeric? || time_face
214
+ raise ArgumentError,
215
+ "fill(#{key.inspect}, :linear): numeric or time column required " \
216
+ "(got #{col.data_type_name})"
217
+ end
218
+ # Without an index coordinate x is the cell position, which is exactly what
219
+ # the core scan already interpolates against -- time columns included.
220
+ return col.unmask(method: :linear) if @index.nil?
221
+ present = col.is_not_masked
222
+ return if present.count(true) < 2
223
+ addr = @index[present].linear_section(@index)
224
+ if time_face
225
+ filled = col[present].linear_fetch(addr) # UNDEF beyond the valid span
226
+ # Write through the storage: a bulk store into the Face itself would try
227
+ # to cast int64 ticks to its fixlen surface. Both sides carry the same
228
+ # unit (selection preserves it), so the ticks land exactly, mask included.
229
+ col.parent[] = filled.parent
230
+ return
231
+ end
232
+ yvalid = col.value.float64[present]
233
+ col[] = yvalid.linear_fetch(addr).to_type(col.data_type).mask_invalid # write-through
234
+ end
235
+
236
+ private def cast_one(name, type)
237
+ key = name.to_s
238
+ raise KeyError, "no column #{key.inspect}" unless @columns.key?(key)
239
+ @columns[key] = @columns[key].to_type(type)
240
+ end
241
+
242
+ # +promote+ with no target: let +promote_list+ decide the common type --
243
+ # the same call +to_ca+ / +CArray.stack+ make -- then materialise each
244
+ # column it had to coerce. +promote_list+ hands a column back untouched
245
+ # when it needs no coercion, so identity says which ones to rebind.
246
+ private def promote_to_common
247
+ cols = @columns.values
248
+ begin
249
+ promoted = CArray.promote_list(cols)
250
+ rescue ArgumentError, RuntimeError => e
251
+ raise ArgumentError,
252
+ "#{e.message} -- promote(:object) brings every column to its " \
253
+ "surface values, which any column set can share; a numeric target " \
254
+ "works too when every Face column declares #to_numeric"
255
+ end
256
+ @columns.keys.each_with_index do |key, i|
257
+ coerced = promoted[i]
258
+ next if coerced.equal?(cols[i])
259
+ @columns[key] = cols[i].to_type(coerced.data_type)
260
+ end
261
+ end
262
+
263
+ # +promote+ with a target: every column must widen into it.
264
+ private def promote_to_type(type)
265
+ unless type.is_a?(Symbol)
266
+ raise ArgumentError,
267
+ "promote takes a data type Symbol (got #{type.class}); " \
268
+ "class-shaped targets are not promotion destinations"
269
+ end
270
+ @columns.each_key do |key|
271
+ col = @columns[key]
272
+ # A Face column answers for itself: :object is its surface values, a
273
+ # numeric target is whatever it declares in #to_numeric (and a TypeError
274
+ # naming that method when it declares nothing). result_type has nothing
275
+ # to say about a surface it cannot read, so the widening check -- which
276
+ # is about primitive promotion -- applies to plain columns only.
277
+ refuse_narrowing(key, col, type) unless col.face?
278
+ @columns[key] = col.to_type(type)
279
+ end
280
+ end
281
+
282
+ private def refuse_narrowing(key, col, type)
283
+ common = begin
284
+ CArray.result_type(col, type)
285
+ rescue StandardError => e
286
+ raise ArgumentError,
287
+ "promote: column #{key.inspect} (#{col.data_type}) has no common " \
288
+ "type with #{type.inspect} (#{e.message})"
289
+ end
290
+ return if common == type
291
+ raise ArgumentError,
292
+ "promote widens: column #{key.inspect} (#{col.data_type}) would " \
293
+ "narrow to #{type.inspect} -- cast is the verb that forces a " \
294
+ "lossy change"
295
+ end
296
+
297
+ private def string_column?(col)
298
+ col.is_a?(CArray::StringOperationMixin) || col.data_type == :object
299
+ end
300
+
301
+ private def integer_serial_column(col, key)
302
+ if INTEGER_TYPES.include?(col.data_type)
303
+ col.to_type(:int64)
304
+ elsif col.data_type == :float32 || col.data_type == :float64
305
+ unless col.floor.eq(col).all
306
+ raise ArgumentError,
307
+ "to_time: float column #{key.inspect} has fractional values; " \
308
+ "use a finer unit: or convert to integer counts explicitly"
309
+ end
310
+ col.to_type(:int64)
311
+ else
312
+ raise ArgumentError,
313
+ "to_time needs an integer serial column; #{key.inspect} is #{col.data_type}"
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,16 @@
1
+ # CAFrame — a DataFrame whose columns are real CArrays.
2
+ #
3
+ # Thin loader. The frame is a small Ruby layer over CArray: columns are
4
+ # genuine CArrays, so every CArray operation (mask / view / Face /
5
+ # memory_view) is reachable by escaping to a column. See
6
+ # devel/MEMO_DATAFRAME_ON_CARRAY.md for the design.
7
+
8
+ require 'carray/frame/frame'
9
+ require 'carray/frame/verbs'
10
+ require 'carray/frame/convert'
11
+ require 'carray/frame/io'
12
+ require 'carray/frame/records'
13
+ require 'carray/frame/group'
14
+ require 'carray/frame/join'
15
+ require 'carray/frame/concat'
16
+ require 'carray/frame/sort'
@@ -0,0 +1,123 @@
1
+ # ---------------------------------------------------------------------------
2
+ # Reading `CArray.fuse { a + b * c }`.
3
+ #
4
+ # The block is not called. Its `a` is the array itself, so calling it would
5
+ # evaluate the expression eagerly -- which is the thing fuse exists to avoid.
6
+ # The source is read instead, every name that holds a CArray is given `.lazy`,
7
+ # and the result is evaluated back in the block's own binding, so `self`,
8
+ # instance variables, methods and constants are what they were.
9
+ #
10
+ # Ruby has no macro, so the alternative was to pass the arrays in and take
11
+ # shadows back -- `fuse(a, b) { |x, y| ... }` -- which names each of them
12
+ # twice. Julia writes `@.` for the same reason and does the same thing to
13
+ # the expression underneath.
14
+ # ---------------------------------------------------------------------------
15
+
16
+ require "prism"
17
+
18
+ class CArray
19
+
20
+ module FuseSource
21
+
22
+ # Runtime coercion, so the rewrite never has to work out what a name
23
+ # holds: anything that is not an array passes through untouched.
24
+ def self.shadow (value)
25
+ value.is_a?(CArray) ? value.lazy : value
26
+ end
27
+
28
+ def self.evaluate (block)
29
+ result = eval(rewrite(body_source(block)), block.binding,
30
+ *block.source_location)
31
+ # An expression that is just an array is that array; the shadow put
32
+ # around it has nothing to fuse.
33
+ result.is_a?(CALazyMarker) ? result.parent : result
34
+ end
35
+
36
+ # -- the block's own text ---------------------------------------------
37
+
38
+ def self.body_source (block)
39
+ text = extract(block)
40
+ wrapped = "proc " + text
41
+ node = Prism.parse(wrapped).value
42
+ .breadth_first_search { |n| n.is_a?(Prism::BlockNode) }
43
+ inner = node && node.body
44
+ unless inner
45
+ raise ArgumentError,
46
+ "CArray.fuse could not read an expression out of this block"
47
+ end
48
+ wrapped.byteslice(inner.location.start_offset...inner.location.end_offset)
49
+ end
50
+
51
+ def self.extract (block)
52
+ sequence = RubyVM::InstructionSequence.of(block) rescue nil
53
+ location = sequence && sequence.to_a[4][:code_location]
54
+ path = sequence && (sequence.absolute_path || sequence.path)
55
+ unless location && path && File.readable?(path)
56
+ raise ArgumentError,
57
+ "CArray.fuse cannot read this block's source (defined in irb, " \
58
+ "eval, or a file that is no longer there). Write `.lazy` on " \
59
+ "the operands instead: `a.lazy + b.lazy`."
60
+ end
61
+ lines = File.readlines(path)
62
+ first_line, first_column, last_line, last_column = location
63
+ # The columns count bytes, not characters, so a line with anything
64
+ # multi-byte on it slices in the wrong place unless this does too.
65
+ if first_line == last_line
66
+ lines[first_line - 1].byteslice(first_column...last_column)
67
+ else
68
+ [lines[first_line - 1].byteslice(first_column..),
69
+ *lines[first_line...(last_line - 1)],
70
+ lines[last_line - 1].byteslice(0...last_column)].join
71
+ end
72
+ end
73
+
74
+ # -- the rewrite -------------------------------------------------------
75
+
76
+ # The leaves are the names being read. Everything else keeps its shape:
77
+ # calls are inserted around leaves and the expression they sit in is
78
+ # left alone.
79
+ class Leaves < Prism::Visitor
80
+ attr_reader :spots
81
+
82
+ def initialize
83
+ @spots = []
84
+ end
85
+
86
+ def visit_local_variable_read_node (node) = mark(node)
87
+ def visit_instance_variable_read_node (node) = mark(node)
88
+ def visit_constant_read_node (node) = mark(node)
89
+
90
+ # `Math::PI` is one name, not `Math` with something after it.
91
+ def visit_constant_path_node (node)
92
+ mark(node)
93
+ end
94
+
95
+ def visit_call_node (node)
96
+ if node.name == :[] || node.name == :[]=
97
+ # An index is a position, not a value to fuse: `a[i]` shadows `a`
98
+ # and leaves `i` alone.
99
+ visit(node.receiver)
100
+ return
101
+ end
102
+ mark(node) if node.receiver.nil? && node.arguments.nil? && node.block.nil?
103
+ super
104
+ end
105
+
106
+ private
107
+
108
+ def mark (node)
109
+ @spots << [node.location.start_offset, node.location.end_offset]
110
+ end
111
+ end
112
+
113
+ def self.rewrite (source)
114
+ visitor = Leaves.new
115
+ Prism.parse(source).value.accept(visitor)
116
+ out = source.dup
117
+ visitor.spots.sort_by { |start, _| -start }.each do |start, stop|
118
+ out[start...stop] = "::CArray::FuseSource.shadow(#{source[start...stop]})"
119
+ end
120
+ out
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,218 @@
1
+ # ---------------------------------------------------------------------------
2
+ # Turning a lazy expression into a plan a compiler can read.
3
+ #
4
+ # A lazy view is already a typed, closed expression graph, and the kernels
5
+ # already carry the C that computes each operation (CArray.__kernel_body__).
6
+ # What is missing between them is the reading: which operation each node is,
7
+ # what its mask does, and where the leaves are. That is what a plan holds.
8
+ #
9
+ # Nothing here compiles anything. A plan is plain data, and CArray itself
10
+ # never needs one -- it can always walk the view. What a plan is for is a
11
+ # second evaluator: hand it to one, and the answer must be the same.
12
+ # ---------------------------------------------------------------------------
13
+
14
+ class CArray
15
+
16
+ module Fusion
17
+
18
+ # One value per node, in evaluation order; the last is the result.
19
+ #
20
+ # Op an operation, reading the nodes named in `args`
21
+ # Leaf an array, the `index`-th of plan.leaves
22
+ # Const a scalar written into the expression
23
+ Op = Struct.new(:kind, :name, :data_type, :args, :body, :mask, :trapping)
24
+ Leaf = Struct.new(:index, :data_type, :masked)
25
+ Const = Struct.new(:value, :data_type)
26
+
27
+ Plan = Struct.new(:nodes, :leaves, :data_type, :dim, :masked, :signature)
28
+
29
+ LAZY_CLASSES = [CAMonOp, CABinOp, CATriOp, CAMonCmp, CABinCmp, CALazyMarker]
30
+
31
+ # A lazy node names its operation by an id and the kernels name it by a
32
+ # symbol. These are the same operations, spelled the way each side
33
+ # spells them; the rest are spelled alike.
34
+ BINOP_NAMES = {
35
+ :+ => :add, :- => :sub, :* => :mul,
36
+ :/ => :div, :** => :power, :% => :mod,
37
+ :& => :bit_and_i, :| => :bit_or_i, :^ => :bit_xor_i,
38
+ :<< => :bit_lshift, :>> => :bit_rshift,
39
+ }.freeze
40
+ TRIOP_NAMES = { :__clip_ki__ => :clip }.freeze
41
+
42
+ MONOP_BY_ID = CArray::LAZY_MONOP_OP_IDS.invert.freeze
43
+ BINOP_BY_ID = CArray::LAZY_BINOP_OP_IDS.invert.freeze
44
+ TRIOP_BY_ID = CArray::LAZY_TRIOP_OP_IDS.invert.freeze
45
+
46
+ # Integer division and its relatives raise on a zero divisor, so a cell
47
+ # the mask excludes must not be computed at all -- the divisor there is
48
+ # nobody's business (ca_binop_dispatch.c).
49
+ TRAPPING = %i[div mod quo_i fmod].freeze
50
+ INTEGERS = %i[int8 int16 int32 int64 uint8 uint16 uint32 uint64].freeze
51
+
52
+ class Refused < StandardError; end
53
+
54
+ # ---- who computes a plan --------------------------------------------
55
+ #
56
+ # CArray can always walk the expression, so nothing has to be registered
57
+ # and nothing changes when nothing is. What a registered evaluator adds
58
+ # is a second way to arrive at the same answer; it is asked, and it may
59
+ # decline. The dispatch point stays on CArray's side, which is what
60
+ # keeps the threshold below a decision about CArray's own walk rather
61
+ # than one that moves with whatever is installed.
62
+ #
63
+ # The evaluator itself is held by CArray (see carray/lazy.rb), so that
64
+ # materialising an expression need not reach for this file at all until
65
+ # something has been registered.
66
+
67
+ # Reaching a compiled kernel costs about the same whatever the array's
68
+ # size, and what it buys is the passes the walk would make. Below this
69
+ # the walk is the faster answer. The crossing moves with how wide the
70
+ # expression is -- measured, thirty thousand cells at one operation, six
71
+ # thousand at six -- and this brackets those: a one-operation expression
72
+ # loses a couple of microseconds here, a six-operation one wins ten.
73
+ THRESHOLD = 10_000
74
+
75
+ # Returns the array, or nil where nothing computed it.
76
+ def self.evaluate (view)
77
+ return nil unless askable?(view)
78
+ out = CArray.__alloc_uninit__(view.data_type, view.dim)
79
+ evaluate_into(view, out) ? out : nil
80
+ end
81
+
82
+ # Fills an array the caller already has. Called from the store as well,
83
+ # where making one and copying it over would be most of the work.
84
+ # Returns true when something computed it.
85
+ def self.evaluate_into (view, out)
86
+ evaluator = CArray.expression_evaluator or return false
87
+ return false unless askable?(view)
88
+ plan = plan(view) or return false
89
+ # A marker over an array, or anything else with nothing to compute,
90
+ # is not worth handing over.
91
+ return false unless plan.nodes.any? { |n| n.is_a?(Op) }
92
+ out.mask = 0 if plan.masked && ! out.has_mask?
93
+ evaluator.call(plan, out) ? true : false
94
+ rescue StandardError => error
95
+ CArray.expression_evaluator = nil
96
+ warn "CArray: the registered expression evaluator raised " \
97
+ "(#{error.class}: #{error.message}); expressions will be walked " \
98
+ "from here on"
99
+ false
100
+ end
101
+
102
+ def self.askable? (view)
103
+ ! CArray.expression_evaluator.nil? && view.elements >= THRESHOLD
104
+ end
105
+
106
+ # Returns a Plan, or nil where the expression holds something a plan
107
+ # cannot describe. Refusing is ordinary: the caller walks instead.
108
+ def self.plan (view)
109
+ build(view)
110
+ rescue Refused
111
+ nil
112
+ end
113
+
114
+ def self.build (view)
115
+ raise Refused, "not a lazy expression" unless lazy?(view)
116
+ w = Walk.new
117
+ w.visit(view)
118
+ Plan.new(w.nodes, w.leaves, view.data_type, view.dim,
119
+ w.leaves.any? { |a| a.has_mask? }, w.signature)
120
+ end
121
+
122
+ def self.lazy? (x)
123
+ LAZY_CLASSES.any? { |k| x.is_a?(k) }
124
+ end
125
+
126
+ # ---- the walk -------------------------------------------------------
127
+
128
+ class Walk
129
+ attr_reader :nodes, :leaves, :signature
130
+
131
+ def initialize
132
+ @nodes = []
133
+ @leaves = []
134
+ @seen = {}
135
+ @signature = +""
136
+ end
137
+
138
+ def visit (n)
139
+ @seen[n.object_id] ||= build(n)
140
+ end
141
+
142
+ private
143
+
144
+ def build (n)
145
+ case n
146
+ when CALazyMarker then visit(n.parent)
147
+ when CAMonOp then unary(n)
148
+ when CABinOp then binary(n)
149
+ when CATriOp then ternary(n)
150
+ when CScalar then constant(n)
151
+ when CArray then leaf(n)
152
+ else raise Refused, "#{n.class} in an expression"
153
+ end
154
+ end
155
+
156
+ def unary (n)
157
+ name = spell(MONOP_BY_ID, n.__op_id__, {})
158
+ args = [visit(n.parent)]
159
+ # A view over one array is masked exactly where that array is
160
+ # (ca_obj_monop.c).
161
+ op(:monop, name, n.data_type, args, :pass)
162
+ end
163
+
164
+ def binary (n)
165
+ name = spell(BINOP_BY_ID, n.__op_id__, BINOP_NAMES)
166
+ args = [visit(n.parent), visit(n.__binop_right__)]
167
+ # Boolean `&` and `|` are three-valued: a masked cell whose known
168
+ # side settles the answer comes back unmasked (ca_obj_binop.c).
169
+ rule = if n.data_type == :boolean && name == :bit_or_i then :kleene_or
170
+ elsif n.data_type == :boolean && name == :bit_and_i then :kleene_and
171
+ else :union
172
+ end
173
+ op(:binop, name, n.data_type, args, rule)
174
+ end
175
+
176
+ def ternary (n)
177
+ name = spell(TRIOP_BY_ID, n.__op_id__, TRIOP_NAMES)
178
+ args = [visit(n.parent), visit(n.__triop_op2__), visit(n.__triop_op3__)]
179
+ op(:triop, name, n.data_type, args, :union)
180
+ end
181
+
182
+ def op (kind, name, type, args, mask)
183
+ body = CArray.__kernel_body__(kind, name, type) or
184
+ raise Refused, "#{kind} #{name} has no body at #{type}"
185
+ note(kind.to_s[0], name, type)
186
+ push Op.new(kind, name, type, args, body, mask,
187
+ TRAPPING.include?(name) && INTEGERS.include?(type))
188
+ end
189
+
190
+ def leaf (n)
191
+ note("a", n.data_type, n.has_mask? ? 1 : 0)
192
+ @leaves << n
193
+ push Leaf.new(@leaves.size - 1, n.data_type, n.has_mask?)
194
+ end
195
+
196
+ def constant (n)
197
+ note("k", n[0], n.data_type)
198
+ push Const.new(n[0], n.data_type)
199
+ end
200
+
201
+ def push (node)
202
+ @nodes << node
203
+ @nodes.size - 1
204
+ end
205
+
206
+ def spell (table, id, renames)
207
+ ruby = table[id] or raise Refused, "operation id #{id}"
208
+ renames.fetch(ruby, ruby)
209
+ end
210
+
211
+ # Two expressions of the same shape compute alike, whatever arrays
212
+ # they are over, so a consumer can keep one compiled kernel for both.
213
+ def note (*parts)
214
+ @signature << parts.join(":") << ";"
215
+ end
216
+ end
217
+ end
218
+ end