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,941 @@
1
+ # ---------------------------------------------------------------------------
2
+ #
3
+ # Per-op lazy-or-eager method redefinition on CArray.
4
+ #
5
+ # For each of the monop/monfunc methods, alias the existing eager
6
+ # implementation as `__<name>_eager__` and redefine `<name>` to dispatch:
7
+ #
8
+ # - if self.__lazy_view__? → CAMonOp.__build__(self, OP_<NAME>)
9
+ # (cast node insertion handled in C if data_type mismatch)
10
+ # - else → __<name>_eager__
11
+ #
12
+ # CAREFUL: the redefinition must run once at load time AFTER the eager
13
+ # methods are bound by Init_carray_math. carray_ext.bundle Init order
14
+ # guarantees this file loads after ext-level binding.
15
+ #
16
+ # The lazy branch sits at the entry of CArray#<op> and does only a cheap
17
+ # type check, so the pure-eager path is essentially unaffected.
18
+ # ---------------------------------------------------------------------------
19
+
20
+ class CArray
21
+ # op_name => CAMonOp::OP_<NAME>
22
+ LAZY_MONOP_OP_IDS = {
23
+ # Preserve-data_type monop (8)
24
+ zero: CAMonOp::OP_ZERO,
25
+ one: CAMonOp::OP_ONE,
26
+ frac: CAMonOp::OP_FRAC,
27
+ neg: CAMonOp::OP_NEG,
28
+ bit_neg: CAMonOp::OP_BIT_NEG,
29
+ abs_i: CAMonOp::OP_ABS_I,
30
+ conj: CAMonOp::OP_CONJ,
31
+ not: CAMonOp::OP_NOT,
32
+
33
+ # Preserve-data_type monfunc (4)
34
+ ceil: CAMonOp::OP_CEIL,
35
+ floor: CAMonOp::OP_FLOOR,
36
+ round: CAMonOp::OP_ROUND,
37
+ rcp: CAMonOp::OP_RCP,
38
+
39
+ # Widening monfunc (22)
40
+ rad: CAMonOp::OP_RAD,
41
+ deg: CAMonOp::OP_DEG,
42
+ sqrt: CAMonOp::OP_SQRT,
43
+ exp: CAMonOp::OP_EXP,
44
+ exp2: CAMonOp::OP_EXP2,
45
+ exp10: CAMonOp::OP_EXP10,
46
+ log: CAMonOp::OP_LOG,
47
+ log10: CAMonOp::OP_LOG10,
48
+ log2: CAMonOp::OP_LOG2,
49
+ logb: CAMonOp::OP_LOGB,
50
+ sin: CAMonOp::OP_SIN,
51
+ cos: CAMonOp::OP_COS,
52
+ tan: CAMonOp::OP_TAN,
53
+ asin: CAMonOp::OP_ASIN,
54
+ acos: CAMonOp::OP_ACOS,
55
+ atan: CAMonOp::OP_ATAN,
56
+ sinh: CAMonOp::OP_SINH,
57
+ cosh: CAMonOp::OP_COSH,
58
+ tanh: CAMonOp::OP_TANH,
59
+ asinh: CAMonOp::OP_ASINH,
60
+ acosh: CAMonOp::OP_ACOSH,
61
+ atanh: CAMonOp::OP_ATANH,
62
+
63
+ # Additional monfunc
64
+ expm1: CAMonOp::OP_EXPM1,
65
+ log1p: CAMonOp::OP_LOG1P,
66
+ rsqrt: CAMonOp::OP_RSQRT,
67
+ trunc: CAMonOp::OP_TRUNC,
68
+ square: CAMonOp::OP_SQUARE,
69
+
70
+ # Angle normalisation
71
+ deg_360: CAMonOp::OP_DEG_360,
72
+ deg_180: CAMonOp::OP_DEG_180,
73
+ rad_2pi: CAMonOp::OP_RAD_2PI,
74
+ rad_pi: CAMonOp::OP_RAD_PI,
75
+
76
+ # Sign function (preserves the data type). bool/uint → 0/1, sint → -1/0/1,
77
+ # float → -1/0/1 NaN-preserving, complex → unit vector or 0.
78
+ sign: CAMonOp::OP_SIGN,
79
+
80
+ # imag_i: type-preserving primitive (0 for numeric, cimag for complex
81
+ # in the real slot). Primarily consumed by the `imag` special case
82
+ # below but also directly callable via `a.lazy.imag_i`; entry here
83
+ # so the direct call fuses instead of falling to eager.
84
+ imag_i: CAMonOp::OP_IMAG_I,
85
+ }.freeze
86
+
87
+ LAZY_MONOP_OP_IDS.each do |name, op_id|
88
+ eager_alias = :"__#{name}_eager__"
89
+ # Preserve the existing eager method under a hidden alias.
90
+ alias_method(eager_alias, name)
91
+ # Capture the eager UnboundMethod for fast direct dispatch (avoids
92
+ # Ruby method-lookup overhead of `__send__` on the hot eager path).
93
+ eager_um = instance_method(eager_alias)
94
+ # Redefine as lazy-or-eager dispatch.
95
+ define_method(name) do
96
+ if __lazy_view__?
97
+ CAMonOp.__build__(self, op_id)
98
+ else
99
+ eager_um.bind_call(self)
100
+ end
101
+ end
102
+ end
103
+
104
+ # Operator aliases (`~` -> bit_neg, `-@` -> neg) were installed by
105
+ # rb_define_alias at Init time (see MkKernel.alias_monop in
106
+ # ext/carray_kernels_monop.c) BEFORE this file redefines the target
107
+ # methods. rb_define_alias snapshots the method entry, so the operator
108
+ # form still points at the original eager C function and skips the
109
+ # lazy dispatch above — `~a` / `-a` on a lazy view materialise instead
110
+ # of building a CAMonOp node. Re-alias here so the operator form
111
+ # follows the redefined lazy-or-eager dispatch.
112
+ alias_method :"~", :bit_neg
113
+ alias_method :"-@", :neg
114
+
115
+ # ---------------------------------------------------------------------------
116
+ # abs lazy fuse (post-IC follow-up): the hand-written rb_ca_abs in
117
+ # ext/carray_math.c is NOT in LAZY_MONOP_OP_IDS because it does
118
+ # complex-specific dispatch (= magnitude as float). Re-express that
119
+ # dispatch via chain composition over existing lazy ops so abs rides
120
+ # the lazy substrate fully:
121
+ # numeric parent: CAMonOp(OP_ABS_I) (1 node)
122
+ # complex parent: CAMonOp(cast_<float>) ∘ CAMonOp(OP_ABS_I) (2 nodes)
123
+ # The complex chain works because ca_monop_abs_i stores |z| in the
124
+ # real slot with imag=0, then the cast picks up the real part —
125
+ # semantically identical to the eager abs. The cast target is the
126
+ # real component width of the operand (cmplx64 -> f32, cmplx128 ->
127
+ # f64), matching the eager kernel's `complex: :real_of_source`.
128
+ # No new C kernel, no new op_id required.
129
+ # ---------------------------------------------------------------------------
130
+
131
+ # @private
132
+ alias_method :__abs_eager__, :abs
133
+ __abs_eager_um__ = instance_method(:__abs_eager__)
134
+ define_method(:abs) do
135
+ if __lazy_view__?
136
+ abs_i_node = CAMonOp.__build__(self, CAMonOp::OP_ABS_I)
137
+ if complex?
138
+ # float_dt is a Symbol; convert via data_type_code so the cast
139
+ # op_id can be computed by Integer arithmetic.
140
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
141
+ CAMonOp.__build__(abs_i_node,
142
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
143
+ else
144
+ abs_i_node
145
+ end
146
+ else
147
+ __abs_eager_um__.bind_call(self)
148
+ end
149
+ end
150
+
151
+ # ---------------------------------------------------------------------------
152
+ # real / imag lazy fuse (post-IC follow-up):
153
+ #
154
+ # Eager `real`/`imag` in lib/carray/math.rb return CAField views
155
+ # (zero-copy, mutable byte-offset access into complex storage) or
156
+ # fresh template entities for non-complex. Both break a lazy chain
157
+ # when applied to a lazy parent (CAField materialises the parent into
158
+ # an entity to byte-offset into). For lazy parents we re-express via
159
+ # chain composition so the chain stays fused:
160
+ #
161
+ # complex parent .real → CAMonOp(cast_<float>)
162
+ # (cmplx128→f64 / cmplx64→f32 casts pick up the real part —
163
+ # verified equivalent to existing CAField path for read-only
164
+ # consumption)
165
+ #
166
+ # complex parent .imag → CAMonOp(cast_<float>) ∘ CAMonOp(imag_i)
167
+ # (imag_i puts cimag in the real component of a same-data_type slot,
168
+ # cast then extracts it as float — same trick as abs)
169
+ #
170
+ # non-complex parent .real → self (= pass-through, chain unchanged)
171
+ #
172
+ # non-complex parent .imag → CAMonOp(imag_i) (= same-shape lazy zero)
173
+ #
174
+ # Eager parents fall through to the existing math.rb implementation
175
+ # (= CAField for complex, CARefer/template for non-complex) so the
176
+ # mutable-setter use cases (`.real = val` / `.imag = val`) remain
177
+ # supported. Lazy views are inherently read-only so the loss of
178
+ # mutability on the lazy path is not a regression.
179
+ # ---------------------------------------------------------------------------
180
+
181
+ # @private
182
+ alias_method :__real_eager__, :real
183
+ __real_eager_um__ = instance_method(:__real_eager__)
184
+ define_method(:real) do
185
+ if __lazy_view__?
186
+ if complex?
187
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
188
+ # float_dt is a Symbol; convert via data_type_code for kernel
189
+ # op_id arithmetic.
190
+ CAMonOp.__build__(self,
191
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
192
+ else
193
+ # `real` of a real array is the array itself; preserve the lazy
194
+ # chain unchanged (eager returns self[] = CARefer wrap, but for
195
+ # lazy chain consumption returning self keeps streaming intact).
196
+ self
197
+ end
198
+ else
199
+ __real_eager_um__.bind_call(self)
200
+ end
201
+ end
202
+
203
+ # @!visibility private
204
+ alias_method :__imag_eager__, :imag
205
+ __imag_eager_um__ = instance_method(:__imag_eager__)
206
+ define_method(:imag) do
207
+ if __lazy_view__?
208
+ imag_i_node = CAMonOp.__build__(self, CAMonOp::OP_IMAG_I)
209
+ if complex?
210
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
211
+ # float_dt is a Symbol; convert via data_type_code for kernel
212
+ # op_id arithmetic.
213
+ CAMonOp.__build__(imag_i_node,
214
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
215
+ else
216
+ imag_i_node
217
+ end
218
+ else
219
+ __imag_eager_um__.bind_call(self)
220
+ end
221
+ end
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # arg lazy fuse: eager `arg` is a data_type-changing monop (see
225
+ # MkKernel.monop :arg output rule).
226
+ # CAMonOp's cast-before invariant requires input data type == output data type
227
+ # at each in-place step, so `arg` can't sit directly in the substrate.
228
+ # Chain compose via the type-preserving `arg_i` primitive:
229
+ #
230
+ # integer / bool parent → cast_f64 ∘ arg_i (result f64)
231
+ # float parent → arg_i (result same float)
232
+ # complex parent → cast_<float> ∘ arg_i (arg_i writes real
233
+ # component of complex slot; cast extracts
234
+ # the real part, same trick as abs / imag)
235
+ #
236
+ # Both paths keep the operand's float / complex width (f32 → f32,
237
+ # cmplx64 → f32), and both send integers to f64 since pi does not fit
238
+ # an integer slot. That agrees with the general lazy-substrate rule
239
+ # "scalar keeps operand's precision" (see ca_lazy_wrap_scalar header).
240
+ # ---------------------------------------------------------------------------
241
+
242
+ # @private
243
+ alias_method :__arg_eager__, :arg
244
+ __arg_eager_um__ = instance_method(:__arg_eager__)
245
+ define_method(:arg) do
246
+ if __lazy_view__?
247
+ parent = self
248
+ if integer? || boolean?
249
+ parent = CAMonOp.__build__(parent,
250
+ CAMonOp::CAST_BASE +
251
+ CArray.data_type_code(CA_FLOAT64))
252
+ end
253
+ arg_i_node = CAMonOp.__build__(parent, CAMonOp::OP_ARG_I)
254
+ if complex?
255
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
256
+ CAMonOp.__build__(arg_i_node,
257
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
258
+ else
259
+ arg_i_node
260
+ end
261
+ else
262
+ __arg_eager_um__.bind_call(self)
263
+ end
264
+ end
265
+
266
+ # ---------------------------------------------------------------------------
267
+ # Binop dispatch. Operator entries are redefined so a lazy operand on
268
+ # either side routes into CABinOp.__build__. Op scope:
269
+ # - 5 arithmetic: + - * / **
270
+ # - 3 bitwise: & | ^
271
+ # - 2 shifts: << >>
272
+ # - 2 misc: %, rcp_mul
273
+ # ---------------------------------------------------------------------------
274
+
275
+ LAZY_BINOP_OP_IDS = {
276
+ :+ => CABinOp::OP_ADD,
277
+ :- => CABinOp::OP_SUB,
278
+ :* => CABinOp::OP_MUL,
279
+ :/ => CABinOp::OP_DIV,
280
+ :** => CABinOp::OP_POW,
281
+ :& => CABinOp::OP_BIT_AND,
282
+ :| => CABinOp::OP_BIT_OR,
283
+ :^ => CABinOp::OP_BIT_XOR,
284
+ :<< => CABinOp::OP_BIT_LSHIFT,
285
+ :>> => CABinOp::OP_BIT_RSHIFT,
286
+ :% => CABinOp::OP_MOD,
287
+ :rcp_mul => CABinOp::OP_RCP_MUL,
288
+
289
+ # Float-only binops registered eagerly by mkkernel; the lazy entries
290
+ # here pick them up so `a.lazy.hypot(b)` etc. ride the substrate.
291
+ :copysign => CABinOp::OP_COPYSIGN,
292
+ :logaddexp => CABinOp::OP_LOGADDEXP,
293
+ :nextafter => CABinOp::OP_NEXTAFTER,
294
+ :fmod => CABinOp::OP_FMOD,
295
+ :atan2 => CABinOp::OP_ATAN2,
296
+ :hypot => CABinOp::OP_HYPOT,
297
+
298
+ # Pair-wise max / min (NaN-skip via C99 fmax/fmin on float branch).
299
+ :pmax => CABinOp::OP_PMAX,
300
+ :pmin => CABinOp::OP_PMIN,
301
+
302
+ # Pair-wise max / min, NaN-propagate variant.
303
+ :maximum => CABinOp::OP_MAXIMUM,
304
+ :minimum => CABinOp::OP_MINIMUM,
305
+
306
+ # Boolean word forms (bool + object; plain mask propagation, no
307
+ # Kleene fixup — see the CA_BINOP_AND note in ca_binop_dispatch.h).
308
+ :and => CABinOp::OP_AND,
309
+ :or => CABinOp::OP_OR,
310
+ :xor => CABinOp::OP_XOR,
311
+
312
+ # IEEE 754 remainder (distinct semantics from `%` / `mod`: float
313
+ # branch uses C99 `remainder`, round-half-to-even).
314
+ }.freeze
315
+
316
+ LAZY_BINOP_OP_IDS.each do |method_name, op_id|
317
+ eager_alias = :"__#{method_name}_eager__"
318
+ alias_method(eager_alias, method_name)
319
+ eager_um = instance_method(eager_alias)
320
+ define_method(method_name) do |other|
321
+ if __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
322
+ CABinOp.__build__(self, other, op_id)
323
+ else
324
+ eager_um.bind_call(self, other)
325
+ end
326
+ end
327
+ end
328
+
329
+ # Binop word aliases (`add` -> `+`, `mul` -> `*`, `bit_and` -> `&`, …) were
330
+ # installed by rb_define_alias at Init time (see MkKernel.alias_binop in
331
+ # ext/carray_kernels_*.c) BEFORE the redefinitions above. The C alias
332
+ # snapshots the original eager method entry, so the word form still points
333
+ # at the original C function and skips lazy dispatch — `a.lazy.add(1)`
334
+ # materialises instead of building a CABinOp node. Re-alias here so the
335
+ # word form follows the redefined operator method.
336
+ {
337
+ add: :+,
338
+ sub: :-,
339
+ mul: :*,
340
+ div: :/,
341
+ mod: :%,
342
+ bit_and: :&,
343
+ bit_or: :|,
344
+ bit_xor: :^,
345
+ bit_lshift: :<<,
346
+ bit_rshift: :>>,
347
+ }.each do |word, op|
348
+ alias_method(word, op)
349
+ end
350
+
351
+ # `pow` is the canonical C method (defined by rb_define_method in
352
+ # ext/ca_op_ipower.c) that `**` is rb_define_alias'd to. The `**`
353
+ # override in this file (with the ipower Integer-exponent fast path)
354
+ # does not propagate to `pow` through the C alias, so `a.lazy.pow(2)`
355
+ # falls to eager. Re-alias so `pow` follows the redefined `**`.
356
+ alias_method :pow, :**
357
+
358
+ # `fmax` / `fmin` are rb_define_alias'd to `pmax` / `pmin` at Init
359
+ # time (same C-alias-snapshot trap). Re-alias so the C99-familiar
360
+ # spellings follow the redefined lazy dispatch above.
361
+ alias_method :fmax, :pmax
362
+ alias_method :fmin, :pmin
363
+
364
+ # ---------------------------------------------------------------------------
365
+ # Triop dispatch (fma / fms / clip). CATriOp is the CABinOp analog for
366
+ # three-operand element-wise ops. Each Ruby method redefined below
367
+ # dispatches to CATriOp.__build__ when any of self / op2 / op3 is a
368
+ # lazy view, and falls to the eager C method otherwise.
369
+ #
370
+ # `clip` is the strict-clamp entry (`__clip_ki__`, called by the
371
+ # lib/carray/basics.rb `clip` wrapper's both-bounds-present path). The
372
+ # nil-bound one-sided cases route through the wrapper's `pmax` / `pmin`
373
+ # calls, which themselves lazy-fuse via LAZY_BINOP_OP_IDS above — so
374
+ # `a.lazy.clip(nil, hi)` and `a.lazy.clip(lo, nil)` fuse without a
375
+ # dedicated triop entry.
376
+ # ---------------------------------------------------------------------------
377
+
378
+ LAZY_TRIOP_OP_IDS = {
379
+ fma: CATriOp::OP_FMA,
380
+ fms: CATriOp::OP_FMS,
381
+ __clip_ki__: CATriOp::OP_CLIP,
382
+ }.freeze
383
+
384
+ LAZY_TRIOP_OP_IDS.each do |method_name, op_id|
385
+ eager_alias = :"__#{method_name}_eager_triop__"
386
+ alias_method(eager_alias, method_name)
387
+ eager_um = instance_method(eager_alias)
388
+ define_method(method_name) do |op2, op3|
389
+ lazy_receiver = __lazy_view__? ||
390
+ (op2.is_a?(CArray) && op2.__lazy_view__?) ||
391
+ (op3.is_a?(CArray) && op3.__lazy_view__?)
392
+ if lazy_receiver
393
+ CATriOp.__build__(self, op2, op3, op_id)
394
+ else
395
+ eager_um.bind_call(self, op2, op3)
396
+ end
397
+ end
398
+ end
399
+
400
+ # `**` integer-exponent lazy fast path.
401
+ #
402
+ # The default OP_POW path above goes through the `power` binop kernel,
403
+ # which uses `pow(x, p)` / `cpow(x, p)` (transcendental, ~20-50 ns/cell)
404
+ # for float / complex parents. When `other` is an Integer, the eager
405
+ # `rb_ca_pow` dispatches to `rb_ca_ipower` (= binary exponentiation via
406
+ # `op_powi_<type>`, ~1-3 mul/cell, 3-5x faster than the transcendental
407
+ # form). Mirror that dispatch here so `a.lazy ** 2` rides the same
408
+ # fast path.
409
+ #
410
+ # Routing rule (matches eager `rb_ca_pow`):
411
+ # - lazy parent + Integer exponent + Float/Complex parent data_type
412
+ # -> wrap exponent as CScalar.int64 + CABinOp(OP_IPOWER)
413
+ # - lazy parent (other cases) -> CABinOp(OP_POW) [existing]
414
+ # - else -> eager (which itself dispatches to ipower internally)
415
+ #
416
+ # Integer parents intentionally stay on OP_POW: the mkkernel-generated
417
+ # `power` binop kernel already uses `op_powi_<type>` for integer data_types
418
+ # via the `int:` expr in MkKernel.binop :power, so OP_POW + int parent
419
+ # is already fast. The lazy-substrate gap was Float/Complex only.
420
+ __pow_eager_um__ = instance_method(:"__**_eager__")
421
+ define_method(:**) do |other|
422
+ if other.is_a?(Integer) && (float? || complex?) &&
423
+ (__lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?))
424
+ CABinOp.__build__(self, CScalar.int64.tap { |s| s[0] = other },
425
+ CABinOp::OP_IPOWER)
426
+ elsif __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
427
+ CABinOp.__build__(self, other, CABinOp::OP_POW)
428
+ else
429
+ __pow_eager_um__.bind_call(self, other)
430
+ end
431
+ end
432
+
433
+ # coerce: when self is a lazy view and the scalar appears on the LEFT
434
+ # (e.g. `2 * a.lazy`), Ruby's Numeric#* calls a.lazy.coerce(2). The
435
+ # default coerce would unwrap the lazy-ness via eager scalar promotion;
436
+ # here we keep it lazy by returning [scalar_as_cscalar, self], so the
437
+ # subsequent operator call ends up with a lazy receiver and triggers
438
+ # the CABinOp builder.
439
+ # ---------------------------------------------------------------------------
440
+ # bincmp / moncmp dispatch.
441
+ #
442
+ # Comparison output is always boolean8_t, so it cannot reuse the CABinOp
443
+ # in-place trick. CABinCmp pulls both operands into operand-data_type
444
+ # scratches and writes boolean to the output buffer. Integer is_nan /
445
+ # is_inf / is_finite use existing per-data_type kernels (which handle the
446
+ # integer const-false/true result and mask skip).
447
+ #
448
+ # Scope: 7 bincmp + 3 moncmp + operator aliases (`<` / `>` / `<=` / `>=`)
449
+ # for canonical Ruby comparison syntax. Note: `==` / `eql?` are NOT
450
+ # comparison ops — eager `CArray#==` (rb_ca_equal) is array-level
451
+ # equality returning bool, not element-wise. Element-wise equality is
452
+ # `eq` / `feq`.
453
+ #
454
+ # `feq` is arity 1 in eager (compile-time FLT_EPSILON / DBL_EPSILON);
455
+ # we mirror that here. Runtime eps is a future extension (struct field
456
+ # already reserved).
457
+ # ---------------------------------------------------------------------------
458
+
459
+ LAZY_BINCMP_OP_IDS = {
460
+ # Canonical method names + operator aliases (rb_define_alias in C
461
+ # creates separate dispatch entries, so we override both).
462
+ :lt => CABinCmp::OP_LT,
463
+ :< => CABinCmp::OP_LT,
464
+ :gt => CABinCmp::OP_GT,
465
+ :> => CABinCmp::OP_GT,
466
+ :le => CABinCmp::OP_LE,
467
+ :<= => CABinCmp::OP_LE,
468
+ :ge => CABinCmp::OP_GE,
469
+ :>= => CABinCmp::OP_GE,
470
+ :eq => CABinCmp::OP_EQ,
471
+ :ne => CABinCmp::OP_NE,
472
+ :feq => CABinCmp::OP_FEQ,
473
+ }.freeze
474
+
475
+ LAZY_BINCMP_OP_IDS.each do |method_name, op_id|
476
+ eager_alias = :"__#{method_name}_eager_bincmp__"
477
+ alias_method(eager_alias, method_name)
478
+ eager_um = instance_method(eager_alias)
479
+ define_method(method_name) do |other|
480
+ if __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
481
+ CABinCmp.__build__(self, other, op_id)
482
+ else
483
+ eager_um.bind_call(self, other)
484
+ end
485
+ end
486
+ end
487
+
488
+ # tolerance-bearing bincmp ops (= is_close / is_equiv) use the same
489
+ # CABinCmp dispatch but with a 2nd `tol` positional arg. Kept in a
490
+ # separate dict because the LAZY_BINCMP_OP_IDS define_method block above
491
+ # uses arity 1 (|other|); these need arity 2 (|other, tol|).
492
+ LAZY_BINCMP_TOL_OP_IDS = {
493
+ :is_close => CABinCmp::OP_IS_CLOSE,
494
+ :is_equiv => CABinCmp::OP_IS_EQUIV,
495
+ }.freeze
496
+
497
+ LAZY_BINCMP_TOL_OP_IDS.each do |method_name, op_id|
498
+ eager_alias = :"__#{method_name}_eager_bincmp_tol__"
499
+ alias_method(eager_alias, method_name)
500
+ eager_um = instance_method(eager_alias)
501
+ define_method(method_name) do |other, tol|
502
+ if __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
503
+ CABinCmp.__build__(self, other, op_id, tol.to_f.abs)
504
+ else
505
+ eager_um.bind_call(self, other, tol)
506
+ end
507
+ end
508
+ end
509
+
510
+ # @!visibility private
511
+ LAZY_MONCMP_OP_IDS = {
512
+ is_nan: CAMonCmp::OP_IS_NAN,
513
+ is_inf: CAMonCmp::OP_IS_INF,
514
+ is_finite: CAMonCmp::OP_IS_FINITE,
515
+ is_invalid: CAMonCmp::OP_IS_INVALID,
516
+ signbit: CAMonCmp::OP_SIGNBIT,
517
+ }.freeze
518
+
519
+ LAZY_MONCMP_OP_IDS.each do |name, op_id|
520
+ eager_alias = :"__#{name}_eager_moncmp__"
521
+ alias_method(eager_alias, name)
522
+ eager_um = instance_method(eager_alias)
523
+ define_method(name) do
524
+ if __lazy_view__?
525
+ CAMonCmp.__build__(self, op_id)
526
+ else
527
+ eager_um.bind_call(self)
528
+ end
529
+ end
530
+ end
531
+
532
+ # @!visibility private
533
+ alias_method :__coerce_eager__, :coerce
534
+ coerce_um = instance_method(:__coerce_eager__)
535
+ define_method(:coerce) do |other|
536
+ if __lazy_view__?
537
+ dt_name = data_type_name
538
+ if CScalar.respond_to?(dt_name)
539
+ s = CScalar.public_send(dt_name)
540
+ s[0] = other
541
+ [s, self]
542
+ else
543
+ coerce_um.bind_call(self, other)
544
+ end
545
+ else
546
+ coerce_um.bind_call(self, other)
547
+ end
548
+ end
549
+ end
550
+
551
+ # ---------------------------------------------------------------------------
552
+ # CAMonOp / CALazyMarker surface methods.
553
+ #
554
+ # Materialise-on-first-touch for Enumerable receivers, MV export reject,
555
+ # inspect/dump_tree summary that does NOT materialise.
556
+ # ---------------------------------------------------------------------------
557
+
558
+ class CAMonOp
559
+ # op_id => :name reverse lookup
560
+ OP_NAMES = CArray::LAZY_MONOP_OP_IDS.invert.freeze
561
+
562
+ # --- inspect / dump_tree (no materialise) ----------------------------
563
+
564
+ def op_label
565
+ op_id = __op_id__
566
+ if op_id >= CAMonOp::CAST_BASE
567
+ "cast_#{CArray.data_type_name(op_id - CAMonOp::CAST_BASE)}"
568
+ else
569
+ (OP_NAMES[op_id] || "op_#{op_id}").to_s
570
+ end
571
+ end
572
+ private :op_label
573
+
574
+ # @overload inspect
575
+ # Returns a short summary of the lazy chain without
576
+ # materialising.
577
+ # @return [String]
578
+ def inspect
579
+ "#<CAMonOp #{op_label}(#{parent.inspect})>"
580
+ end
581
+
582
+ # @overload to_s
583
+ # Alias of {#inspect}.
584
+ # @return [String]
585
+ def to_s
586
+ inspect
587
+ end
588
+
589
+ # @overload dump_tree(indent = 0)
590
+ # Returns an ASCII summary of the lazy expression tree.
591
+ # Does not materialise.
592
+ # @param indent [Integer] leading indentation depth.
593
+ # @return [String]
594
+ def dump_tree(indent = 0)
595
+ pad = " " * indent
596
+ out = +"#{pad}#{op_label}\n"
597
+ out << if parent.is_a?(CAMonOp)
598
+ parent.dump_tree(indent + 1)
599
+ else
600
+ "#{" " * (indent + 1)}#{parent.class}(#{parent.data_type_name}, #{parent.shape.inspect})\n"
601
+ end
602
+ out
603
+ end
604
+
605
+ # @overload each { |value| ... }
606
+ # Materialises `self` and yields each element of the resulting
607
+ # entity.
608
+ # @yieldparam value [Object]
609
+ # @return [Object]
610
+ def each(&block)
611
+ to_ca.each(&block)
612
+ end
613
+
614
+ # @overload to_a
615
+ # Materialises `self` and returns the resulting Array.
616
+ # @return [Array]
617
+ def to_a
618
+ to_ca.to_a
619
+ end
620
+
621
+ # @overload sort(*args, **kwargs)
622
+ # Materialises `self` into a copy and returns
623
+ # `entity.sort(*args, **kwargs)`.
624
+ # @return [CArray]
625
+ def sort(*args, **kwargs)
626
+ copy.sort(*args, **kwargs)
627
+ end
628
+
629
+ # --- MemoryView export → TypeError + .copy recommendation -----------
630
+
631
+ # bulk-memory-view / Numo / Apache Arrow consumers go through the MV
632
+ # protocol, which requires a contiguous backing buffer. Lazy views
633
+ # have no backing buffer until materialised; raise with a clear hint.
634
+ #
635
+ # MemoryView is consulted via to_memory_view_internal on the receiver
636
+ # in some setups; the safest cross-cutting hook is the to_memory_view
637
+ # method (defined by carray_memory_view producer). We override to
638
+ # raise. Importers that probe via `rb_memory_view_get` will hit the
639
+ # producer slot, which we DON'T currently override at C-level — but
640
+ # the MV producer for CAView rejects backing-bufferless views by
641
+ # default; tests below verify both Ruby-level and C-level reject.
642
+ # @overload to_memory_view
643
+ # Not supported: a lazy view has no backing buffer.
644
+ # @return [void]
645
+ # @raise [TypeError] always; call `.copy` first.
646
+ def to_memory_view
647
+ raise TypeError,
648
+ "lazy view (CAMonOp) has no backing buffer; call `.copy` " \
649
+ "first to materialise into an entity before exporting via " \
650
+ "MemoryView."
651
+ end
652
+ end
653
+
654
+ # Lazy element-wise view of a binary operation on two operands. Nothing is
655
+ # computed until the chain is forced, so a composed expression evaluates in
656
+ # one pass instead of materialising an intermediate per operator.
657
+ #
658
+ # Produced by an operator on `.lazy` operands or inside `CArray.fuse`, not
659
+ # constructed directly.
660
+ class CABinOp
661
+ # op_id => :name reverse lookup
662
+ OP_NAMES = CArray::LAZY_BINOP_OP_IDS.invert.merge(
663
+ OP_IPOWER => :ipow,
664
+ ).freeze
665
+
666
+ def op_label
667
+ op_id = __op_id__
668
+ (OP_NAMES[op_id] || "op_#{op_id}").to_s
669
+ end
670
+ private :op_label
671
+
672
+ # @overload inspect
673
+ # Returns a summary of the binop chain without materialising.
674
+ # @return [String]
675
+ def inspect
676
+ "#<CABinOp #{op_label}(#{parent.inspect}, #{__binop_right__.inspect})>"
677
+ end
678
+
679
+ # @overload to_s
680
+ # Alias of {#inspect}.
681
+ # @return [String]
682
+ def to_s
683
+ inspect
684
+ end
685
+
686
+ # @overload dump_tree(indent = 0)
687
+ # Returns an ASCII summary of the lazy expression tree, walking
688
+ # both children. Does not materialise.
689
+ # @return [String]
690
+ def dump_tree(indent = 0)
691
+ pad = " " * indent
692
+ out = +"#{pad}#{op_label}\n"
693
+ [parent, __binop_right__].each do |child|
694
+ out << if child.is_a?(CAMonOp) || child.is_a?(CABinOp)
695
+ child.dump_tree(indent + 1)
696
+ else
697
+ "#{" " * (indent + 1)}#{child.class}(#{child.data_type_name}, #{child.shape.inspect})\n"
698
+ end
699
+ end
700
+ out
701
+ end
702
+
703
+ # @overload each { |value| ... }
704
+ # Materialises `self` and yields each element.
705
+ # @return [Object]
706
+ def each(&block)
707
+ to_ca.each(&block)
708
+ end
709
+
710
+ # @overload to_a
711
+ # Materialises `self` and returns the resulting Array.
712
+ # @return [Array]
713
+ def to_a
714
+ to_ca.to_a
715
+ end
716
+
717
+ # @overload sort(*args, **kwargs)
718
+ # Materialises `self` and sorts the resulting entity.
719
+ # @return [CArray]
720
+ def sort(*args, **kwargs)
721
+ to_ca.sort(*args, **kwargs)
722
+ end
723
+
724
+ # @overload to_memory_view
725
+ # Not supported for lazy binops.
726
+ # @raise [TypeError] always.
727
+ def to_memory_view
728
+ raise TypeError,
729
+ "lazy view (CABinOp) has no backing buffer; call `.to_ca` " \
730
+ "first to materialise into an entity before exporting via " \
731
+ "MemoryView."
732
+ end
733
+ end
734
+
735
+ class CALazyMarker
736
+ # @overload inspect
737
+ # Returns a short summary showing the wrapped entity.
738
+ # @return [String]
739
+ def inspect
740
+ "#<CALazyMarker over #{parent.inspect}>"
741
+ end
742
+
743
+ # @overload to_s
744
+ # Alias of {#inspect}.
745
+ # @return [String]
746
+ def to_s
747
+ inspect
748
+ end
749
+
750
+ # @overload dump_tree(indent = 0)
751
+ # Returns an ASCII summary of the marker and its parent entity.
752
+ # @return [String]
753
+ def dump_tree(indent = 0)
754
+ pad = " " * indent
755
+ "#{pad}lazy(marker)\n" \
756
+ "#{" " * (indent + 1)}#{parent.class}(#{parent.data_type_name}, #{parent.shape.inspect})\n"
757
+ end
758
+
759
+ # @overload each { |value| ... }
760
+ # Yields each element of the wrapped entity (no materialise
761
+ # needed).
762
+ # @return [Object]
763
+ def each(&block)
764
+ parent.each(&block)
765
+ end
766
+
767
+ # @overload to_a
768
+ # Returns the wrapped entity's Array.
769
+ # @return [Array]
770
+ def to_a
771
+ parent.to_a
772
+ end
773
+
774
+ # @overload to_memory_view
775
+ # Not supported: markers do not own a backing buffer.
776
+ # @raise [TypeError] always; call `.copy` first.
777
+ def to_memory_view
778
+ raise TypeError,
779
+ "lazy marker (CALazyMarker) has no backing buffer; call " \
780
+ "`.copy` first to materialise into an entity before " \
781
+ "exporting via MemoryView."
782
+ end
783
+ end
784
+
785
+ # ---------------------------------------------------------------------------
786
+ # Lazy views override `to_ca` to force evaluation.
787
+ #
788
+ # `CArray#to_ca` returns `self` (a concrete-backed CArray — entity or data
789
+ # view — is already a CArray, no copy needed). A lazy view has no data until
790
+ # evaluated, so `to_ca` must produce it: it materialises into a new entity,
791
+ # matching the Ruby `Enumerable#to_a` / lazy `force` convention. (`copy` is
792
+ # the materialise path; on a lazy view it evaluates the expression.)
793
+ #
794
+ # That entity is detached from the operands, so writes to it reach nothing:
795
+ # `writable: true` is refused rather than answered with a result that would
796
+ # swallow them.
797
+ # ---------------------------------------------------------------------------
798
+ [CAMonOp, CABinOp, CATriOp, CAMonCmp, CABinCmp, CALazyMarker].each do |klass|
799
+ klass.class_eval do
800
+ def to_ca(writable: false)
801
+ if writable
802
+ raise "#{self.class}#to_ca evaluates into a new entity; " \
803
+ "it can't satisfy `writable: true'"
804
+ end
805
+ copy
806
+ end
807
+
808
+ # Where something is registered to compute an expression -- a compiler,
809
+ # say -- it is asked here, and the walk below is what happens when it
810
+ # declines, when nothing is registered, or when the array is small
811
+ # enough that walking is the faster answer.
812
+ def copy
813
+ return super unless CArray.expression_evaluator
814
+ CArray::Fusion.evaluate(self) || super
815
+ end
816
+ end
817
+ end
818
+
819
+ # ---------------------------------------------------------------------------
820
+ # CArray.fuse
821
+ #
822
+ # Transient lazy-fusion scope: wraps each CArray argument with `.lazy`,
823
+ # yields the wrappers (and any non-CArray args pass-through unchanged)
824
+ # to the block, then on block exit auto-materialises a bare lazy view
825
+ # return value into an entity. Non-lazy return values (Numeric, plain
826
+ # CArray, Array, nil, etc.) pass through as-is.
827
+ #
828
+ # Design notes:
829
+ # - Non-CArray args pass through eager (= polymorphic numeric helper
830
+ # idiom: `magnus(25.0)` → Float and `magnus(arr)` → CArray carried by
831
+ # a single helper definition, combined with the `CArray::CoreExtensions`
832
+ # refinement which gives Float/Integer/Rational the same postfix math
833
+ # API as CArray instances).
834
+ # - Shadow args are read-only (CA_FLAG_READ_ONLY via `.lazy`), so
835
+ # destructive ops on them raise naturally.
836
+ # - A nested fuse materialises at its own inner exit.
837
+ # - A bare lazy return auto-materialises; escaping the lazy view (e.g.
838
+ # stashing it in an Array) is the user's responsibility.
839
+ # ---------------------------------------------------------------------------
840
+ # CArray.fuse
841
+ #
842
+ # Builds an expression instead of evaluating it, so that it is computed in
843
+ # one pass with no array standing for a step along the way. The block is
844
+ # read rather than called; see CArray::FuseSource for why, and for what
845
+ # happens where its source cannot be read.
846
+ # ---------------------------------------------------------------------------
847
+ class << CArray
848
+ # The object asked to compute an expression, or nil. CArray can always
849
+ # walk one, so nothing has to be registered and nothing changes when
850
+ # nothing is; what a registered evaluator adds is a second way to arrive
851
+ # at the same answer, and it may decline.
852
+ #
853
+ # It is called as `call(plan, out)` -- see {CArray::Fusion} for what a
854
+ # plan holds -- and answers by filling `out` and returning something
855
+ # true, or by returning something false having written nothing. Raising
856
+ # is not an answer: one that raises is dropped, with a warning, and
857
+ # expressions are walked from there on.
858
+ #
859
+ # @return [#call, nil]
860
+ attr_reader :expression_evaluator
861
+
862
+ # @param object [#call, nil] see {#expression_evaluator}
863
+ # @raise [ArgumentError] when it cannot be called.
864
+ def expression_evaluator= (object)
865
+ if object && ! object.respond_to?(:call)
866
+ raise ArgumentError,
867
+ "an expression evaluator must respond to #call(plan, out)"
868
+ end
869
+ @expression_evaluator = object
870
+ end
871
+
872
+ # @overload fuse { <expression> }
873
+ # Builds the expression rather than evaluating it, so that it is
874
+ # computed in one pass with no array standing for a step along the way.
875
+ #
876
+ # The block is not called. Its source is read, every name in it that
877
+ # holds a CArray is given `.lazy`, and the result is evaluated back in
878
+ # the block's own binding -- so `self`, instance variables, methods and
879
+ # constants are what they were where it was written.
880
+ #
881
+ # out[] = CArray.fuse { (a + b) * (c - a) }
882
+ # total = CArray.fuse { a * weight }.sum
883
+ #
884
+ # What comes back is the expression, not an array: it is computed where
885
+ # it is used -- stored into an array, reduced, or asked for one with
886
+ # `to_ca`. A block holding anything but an expression over arrays comes
887
+ # back as whatever it evaluated to.
888
+ #
889
+ # @return [Object] the expression, as a lazy view
890
+ # @raise [LocalJumpError] when no block is given.
891
+ # @raise [ArgumentError] when the block's source cannot be read -- in
892
+ # irb, in `eval`, or from a file that is gone. Write `.lazy` on the
893
+ # operands there: `a.lazy + b.lazy`.
894
+ def fuse (*args, &block)
895
+ raise LocalJumpError, "CArray.fuse requires a block" unless block
896
+ unless args.empty?
897
+ raise ArgumentError,
898
+ "CArray.fuse takes no arguments: write the expression itself, " \
899
+ "`CArray.fuse { a + b }`, and the names in it are read from " \
900
+ "where the block was written"
901
+ end
902
+ CArray::FuseSource.evaluate(block)
903
+ end
904
+
905
+ # @overload jit_for (*extents) { |i, j, ...| ... }
906
+ # Runs a block over an index space, so that a cell may reach the ones
907
+ # around it -- a recurrence, a stencil. The block is compiled, and the
908
+ # compiler is the carray-jit gem.
909
+ # @raise [NotImplementedError] when that gem is not installed.
910
+ def jit_for (*extents, **options)
911
+ raise NotImplementedError, no_compiler("jit_for")
912
+ end
913
+
914
+ # @overload jit_each { ... }
915
+ # Runs a block over the cells of arrays at once, naming each cell by the
916
+ # array it came from, and writing what it computes into arrays of yours:
917
+ # `CArray.jit_each { out = a + b * c }`. The block is compiled, and the
918
+ # compiler is the carray-jit gem.
919
+ # @raise [NotImplementedError] when that gem is not installed.
920
+ def jit_each (&block)
921
+ raise NotImplementedError, no_compiler("jit_each")
922
+ end
923
+
924
+ # @overload jit_map { ... }
925
+ # The same block with its value asked for: the last statement is what
926
+ # every cell of the result gets, and the result comes back. The block is
927
+ # compiled, and the compiler is the carray-jit gem.
928
+ # @raise [NotImplementedError] when that gem is not installed.
929
+ def jit_map (&block)
930
+ raise NotImplementedError, no_compiler("jit_map")
931
+ end
932
+
933
+ private
934
+
935
+ def no_compiler (name)
936
+ "CArray.#{name} compiles its block, and the compiler is the carray-jit " \
937
+ "gem, which is not installed. An expression over whole arrays can be " \
938
+ "written as `CArray.fuse { ... }` instead, which needs no compiler; " \
939
+ "reaching a neighbouring cell, or writing the loop itself, does."
940
+ end
941
+ end