carray 2.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -25
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +1 -1
  5. data/NEWS.md +3 -0
  6. data/README.md +128 -44
  7. data/carray.gemspec +22 -24
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1208 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +125 -0
  15. data/ext/ca_binop_dispatch.h +159 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +241 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4321 -0
  26. data/ext/ca_kernel_iterator.h +2603 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +809 -0
  33. data/ext/ca_obj_binop.c +892 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +965 -0
  39. data/ext/ca_obj_face.c +670 -0
  40. data/ext/ca_obj_face.h +247 -0
  41. data/ext/ca_obj_fake.c +228 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +306 -0
  45. data/ext/ca_obj_grid.c +858 -440
  46. data/ext/ca_obj_meld.c +1034 -0
  47. data/ext/ca_obj_moncmp.c +569 -0
  48. data/ext/ca_obj_monop.c +1111 -0
  49. data/ext/ca_obj_object.c +772 -297
  50. data/ext/ca_obj_record.c +468 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +569 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +616 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1296 -0
  58. data/ext/ca_obj_shift.c +229 -791
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2501 -0
  62. data/ext/ca_obj_string.c +268 -0
  63. data/ext/ca_obj_tile.c +614 -0
  64. data/ext/ca_obj_time.c +546 -0
  65. data/ext/ca_obj_timedelta.c +435 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +746 -0
  68. data/ext/ca_obj_unbound_repeat.c +207 -240
  69. data/ext/ca_obj_window.c +1131 -563
  70. data/ext/ca_op_byte_swap.c +175 -0
  71. data/ext/ca_op_ipower.c +319 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +430 -0
  75. data/ext/ca_sweep_engine.h +157 -0
  76. data/ext/ca_transform_common.c +228 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +795 -402
  80. data/ext/carray_access.c +831 -711
  81. data/ext/carray_attribute.c +98 -329
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +283 -0
  84. data/ext/carray_call_cfunc.c +1360 -828
  85. data/ext/carray_call_cfunc.h +160 -0
  86. data/ext/carray_cast.c +1212 -301
  87. data/ext/carray_cast_func.rb +81 -40
  88. data/ext/carray_class.c +53 -63
  89. data/ext/carray_config.h +28 -0
  90. data/ext/carray_conversion.c +350 -346
  91. data/ext/carray_copy.c +156 -268
  92. data/ext/carray_core.c +1341 -198
  93. data/ext/carray_count.c +312 -0
  94. data/ext/carray_data_type.c +43 -19
  95. data/ext/carray_element.c +585 -213
  96. data/ext/carray_factorize.c +2542 -0
  97. data/ext/carray_generate.c +230 -559
  98. data/ext/carray_histogram.c +490 -0
  99. data/ext/carray_hold.c +228 -0
  100. data/ext/carray_index_classifier.c +1035 -0
  101. data/ext/carray_index_classifier.h +27 -0
  102. data/ext/carray_internal.h +120 -0
  103. data/ext/carray_kernels_bincmp.c +4445 -0
  104. data/ext/carray_kernels_binop.c +10979 -0
  105. data/ext/carray_kernels_init.c +36 -0
  106. data/ext/carray_kernels_map.c +3466 -0
  107. data/ext/carray_kernels_moncmp.c +2096 -0
  108. data/ext/carray_kernels_monop.c +18312 -0
  109. data/ext/carray_kernels_reduce_aggregate.c +25836 -0
  110. data/ext/carray_kernels_reduce_boolean.c +329 -0
  111. data/ext/carray_kernels_reduce_cumulative.c +14592 -0
  112. data/ext/carray_kernels_reduce_extreme.c +16947 -0
  113. data/ext/carray_kernels_reduce_variance.c +3909 -0
  114. data/ext/carray_kernels_scan.c +3692 -0
  115. data/ext/carray_kernels_search.c +32137 -0
  116. data/ext/carray_kernels_sort.c +10625 -0
  117. data/ext/carray_kernels_triop.c +1391 -0
  118. data/ext/carray_lazy.c +567 -0
  119. data/ext/carray_loop.c +88 -200
  120. data/ext/carray_mask.c +848 -154
  121. data/ext/carray_math_kernel.h +120 -0
  122. data/ext/carray_mathfunc.c +10 -241
  123. data/ext/carray_median_percentile.c +1257 -0
  124. data/ext/carray_memory_view.c +1625 -0
  125. data/ext/carray_operator.c +1526 -318
  126. data/ext/carray_order.c +664 -1394
  127. data/ext/carray_partition.c +416 -0
  128. data/ext/carray_random.c +518 -0
  129. data/ext/carray_scatter.c +357 -0
  130. data/ext/carray_slab.c +1219 -0
  131. data/ext/carray_slab.h +84 -0
  132. data/ext/carray_sort.c +829 -0
  133. data/ext/carray_sort_kernel.c +620 -0
  134. data/ext/carray_struct.c +695 -0
  135. data/ext/carray_test.c +343 -229
  136. data/ext/carray_undef.c +34 -17
  137. data/ext/carray_utils.c +175 -74
  138. data/ext/extconf.rb +216 -55
  139. data/ext/mk_call_cfunc.rb +480 -0
  140. data/ext/mkkernel.rb +8842 -0
  141. data/ext/ruby_carray.c +202 -101
  142. data/ext/version.h +5 -15
  143. data/ext/version.rb +5 -13
  144. data/lib/carray/arrow_tensor.rb +401 -0
  145. data/lib/carray/attribute.rb +166 -0
  146. data/lib/carray/autoload_carray.rb +220 -0
  147. data/lib/carray/autoload_method_extension.rb +44 -0
  148. data/lib/carray/axis_group.rb +711 -0
  149. data/lib/carray/basics.rb +481 -0
  150. data/lib/carray/bincount_nd.rb +358 -0
  151. data/lib/carray/block_iterator.rb +604 -0
  152. data/lib/carray/boolean_reduce.rb +109 -0
  153. data/lib/carray/categorical.rb +561 -0
  154. data/lib/carray/categorical_iterator.rb +1062 -0
  155. data/lib/carray/complex.rb +150 -0
  156. data/lib/carray/conditional.rb +216 -0
  157. data/lib/carray/const_string.rb +228 -0
  158. data/lib/carray/construct.rb +139 -328
  159. data/lib/carray/core_extensions.rb +240 -0
  160. data/lib/carray/data_type_extension.rb +233 -0
  161. data/lib/carray/fixlen_string.rb +95 -0
  162. data/lib/carray/frame/concat.rb +132 -0
  163. data/lib/carray/frame/convert.rb +95 -0
  164. data/lib/carray/frame/csv_parser.rb +211 -0
  165. data/lib/carray/frame/frame.rb +649 -0
  166. data/lib/carray/frame/group.rb +186 -0
  167. data/lib/carray/frame/io.rb +164 -0
  168. data/lib/carray/frame/join.rb +248 -0
  169. data/lib/carray/frame/records.rb +99 -0
  170. data/lib/carray/frame/sort.rb +113 -0
  171. data/lib/carray/frame/verbs.rb +299 -0
  172. data/lib/carray/frame.rb +16 -0
  173. data/lib/carray/histogram.rb +512 -0
  174. data/lib/carray/inspect.rb +37 -20
  175. data/lib/carray/iterator.rb +57 -349
  176. data/lib/carray/lazy.rb +889 -0
  177. data/lib/carray/mask_gap_fill.rb +200 -0
  178. data/lib/carray/math.rb +78 -342
  179. data/lib/carray/meld_reduce.rb +289 -0
  180. data/lib/carray/methods/align_addr.rb +116 -0
  181. data/lib/carray/methods/bin.rb +128 -0
  182. data/lib/carray/methods/bincount.rb +87 -0
  183. data/lib/carray/methods/bit_string.rb +92 -0
  184. data/lib/carray/methods/broadcast.rb +63 -0
  185. data/lib/carray/methods/choose.rb +39 -0
  186. data/lib/carray/methods/composition.rb +280 -0
  187. data/lib/carray/methods/gather_nd.rb +206 -0
  188. data/lib/carray/methods/index.rb +39 -0
  189. data/lib/carray/methods/insert_block.rb +99 -0
  190. data/lib/carray/methods/is_in.rb +141 -0
  191. data/lib/carray/methods/join.rb +90 -0
  192. data/lib/carray/methods/locate_addr.rb +47 -0
  193. data/lib/carray/methods/mask_duplicates.rb +41 -0
  194. data/lib/carray/methods/meshgrid.rb +91 -0
  195. data/lib/carray/methods/mode.rb +126 -0
  196. data/lib/carray/methods/nunique.rb +46 -0
  197. data/lib/carray/methods/resize.rb +56 -0
  198. data/lib/carray/methods/snap.rb +156 -0
  199. data/lib/carray/methods/string_format.rb +57 -0
  200. data/lib/carray/methods/unique.rb +47 -0
  201. data/lib/carray/methods/value_counts.rb +71 -0
  202. data/lib/carray/mkmf.rb +124 -101
  203. data/lib/carray/runtime.rb +108 -0
  204. data/lib/carray/serialize.rb +478 -167
  205. data/lib/carray/slab_iterator.rb +292 -0
  206. data/lib/carray/stack.rb +291 -0
  207. data/lib/carray/string.rb +56 -180
  208. data/lib/carray/string_operation_extension.rb +289 -0
  209. data/lib/carray/struct.rb +335 -323
  210. data/lib/carray/struct_builder.rb +697 -0
  211. data/lib/carray/table.rb +41 -2
  212. data/lib/carray/time.rb +2255 -38
  213. data/lib/carray/window_iterator.rb +655 -0
  214. data/lib/carray.rb +55 -57
  215. metadata +163 -130
  216. data/Rakefile +0 -51
  217. data/TODO.md +0 -18
  218. data/ext/ca_iter_block.c +0 -257
  219. data/ext/ca_iter_dimension.c +0 -299
  220. data/ext/ca_iter_window.c +0 -214
  221. data/ext/ca_obj_mapping.c +0 -644
  222. data/ext/carray_iterator.c +0 -641
  223. data/ext/carray_math.rb +0 -850
  224. data/ext/carray_numeric.c +0 -259
  225. data/ext/carray_sort_addr.c +0 -254
  226. data/ext/carray_stat.c +0 -2100
  227. data/ext/carray_stat_proc.rb +0 -1999
  228. data/ext/mkmath.rb +0 -741
  229. data/ext/ruby_ccomplex.c +0 -509
  230. data/ext/ruby_float_func.c +0 -86
  231. data/lib/carray/array.rb +0 -8
  232. data/lib/carray/autoload/autoload_base.rb +0 -19
  233. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  234. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  235. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  236. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  237. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  238. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  239. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  240. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  241. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  242. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  243. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  244. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  245. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  246. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  247. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  248. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  249. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  250. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  251. data/lib/carray/autoload.rb +0 -141
  252. data/lib/carray/basic.rb +0 -191
  253. data/lib/carray/broadcast.rb +0 -101
  254. data/lib/carray/compose.rb +0 -315
  255. data/lib/carray/convert.rb +0 -115
  256. data/lib/carray/info.rb +0 -110
  257. data/lib/carray/io/imagemagick.rb +0 -235
  258. data/lib/carray/mask.rb +0 -102
  259. data/lib/carray/math/histogram.rb +0 -177
  260. data/lib/carray/math/recurrence.rb +0 -93
  261. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  262. data/lib/carray/object/ca_obj_link.rb +0 -50
  263. data/lib/carray/object/ca_obj_pack.rb +0 -99
  264. data/lib/carray/obsolete.rb +0 -256
  265. data/lib/carray/ordering.rb +0 -181
  266. data/lib/carray/testing.rb +0 -51
  267. data/lib/carray/transform.rb +0 -109
  268. data/mailmap +0 -1
  269. data/misc/Methods.ja.md +0 -182
  270. data/misc/NOTE +0 -51
  271. data/spec/Classes/CABitfield_spec.rb +0 -58
  272. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  273. data/spec/Classes/CABlock_spec.rb +0 -205
  274. data/spec/Classes/CAField_spec.rb +0 -39
  275. data/spec/Classes/CAGrid_spec.rb +0 -75
  276. data/spec/Classes/CAMap_spec.rb +0 -0
  277. data/spec/Classes/CAMapping_spec.rb +0 -105
  278. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  279. data/spec/Classes/CAObject_spec.rb +0 -33
  280. data/spec/Classes/CARefer_spec.rb +0 -93
  281. data/spec/Classes/CARepeat_spec.rb +0 -65
  282. data/spec/Classes/CASelect_spec.rb +0 -22
  283. data/spec/Classes/CAShift_spec.rb +0 -16
  284. data/spec/Classes/CAStruct_spec.rb +0 -71
  285. data/spec/Classes/CATranspose_spec.rb +0 -60
  286. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  287. data/spec/Classes/CAWindow_spec.rb +0 -54
  288. data/spec/Classes/CAWrap_spec.rb +0 -8
  289. data/spec/Classes/CArray_spec.rb +0 -184
  290. data/spec/Classes/CScalar_spec.rb +0 -55
  291. data/spec/Features/feature_130_spec.rb +0 -19
  292. data/spec/Features/feature_attributes_spec.rb +0 -280
  293. data/spec/Features/feature_boolean_spec.rb +0 -98
  294. data/spec/Features/feature_broadcast.rb +0 -116
  295. data/spec/Features/feature_cast_function.rb +0 -19
  296. data/spec/Features/feature_cast_spec.rb +0 -33
  297. data/spec/Features/feature_class_spec.rb +0 -84
  298. data/spec/Features/feature_complex_spec.rb +0 -42
  299. data/spec/Features/feature_composite_spec.rb +0 -124
  300. data/spec/Features/feature_convert_spec.rb +0 -46
  301. data/spec/Features/feature_copy_spec.rb +0 -123
  302. data/spec/Features/feature_creation_spec.rb +0 -84
  303. data/spec/Features/feature_element_spec.rb +0 -144
  304. data/spec/Features/feature_extream_spec.rb +0 -54
  305. data/spec/Features/feature_generate_spec.rb +0 -74
  306. data/spec/Features/feature_index_spec.rb +0 -69
  307. data/spec/Features/feature_mask_spec.rb +0 -580
  308. data/spec/Features/feature_math_spec.rb +0 -97
  309. data/spec/Features/feature_order_spec.rb +0 -146
  310. data/spec/Features/feature_ref_store_spec.rb +0 -209
  311. data/spec/Features/feature_serialization_spec.rb +0 -125
  312. data/spec/Features/feature_stat_spec.rb +0 -397
  313. data/spec/Features/feature_virtual_spec.rb +0 -48
  314. data/spec/Features/method_eq_spec.rb +0 -81
  315. data/spec/Features/method_is_nan_spec.rb +0 -12
  316. data/spec/Features/method_map_spec.rb +0 -54
  317. data/spec/Features/method_max_with.rb +0 -20
  318. data/spec/Features/method_min_with.rb +0 -19
  319. data/spec/Features/method_ne_spec.rb +0 -18
  320. data/spec/Features/method_project_spec.rb +0 -188
  321. data/spec/Features/method_ref_spec.rb +0 -27
  322. data/spec/Features/method_round_spec.rb +0 -11
  323. data/spec/Features/method_s_linspace_spec.rb +0 -48
  324. data/spec/Features/method_s_span_spec.rb +0 -14
  325. data/spec/Features/method_seq_spec.rb +0 -47
  326. data/spec/Features/method_sort_with.rb +0 -43
  327. data/spec/Features/method_sorted_with.rb +0 -29
  328. data/spec/Features/method_span_spec.rb +0 -42
  329. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  330. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  331. data/spec/spec_all.rb +0 -10
  332. data/utils/ca_ase.rb +0 -21
  333. data/utils/ca_methods.rb +0 -15
  334. data/utils/cast_checker.rb +0 -30
  335. data/utils/convert_test.rb +0 -73
  336. data/utils/extract_yard.rb +0 -22
  337. data/utils/guess_shape.rb +0 -76
  338. data/utils/monkey_patch_methods.rb +0 -62
  339. data/utils/remove_resource_fork.sh +0 -5
@@ -0,0 +1,889 @@
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 (preserve dtype). 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: preserve-dtype 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_f64) ∘ CAMonOp(OP_ABS_I) (2 nodes)
123
+ # The complex chain works because ca_monop_abs_i for cmplx128 stores
124
+ # |z| in the real slot with imag=0, then cast cmplx128->f64 picks up
125
+ # the real part — semantically identical to rb_ca_abs (= abs_i + .real
126
+ # + copy). No new C kernel, no new op_id required.
127
+ # ---------------------------------------------------------------------------
128
+
129
+ alias_method :__abs_eager__, :abs
130
+ __abs_eager_um__ = instance_method(:__abs_eager__)
131
+ define_method(:abs) do
132
+ if __lazy_view__?
133
+ abs_i_node = CAMonOp.__build__(self, CAMonOp::OP_ABS_I)
134
+ if complex?
135
+ # CA_FLOAT64 is a Symbol; convert via data_type_code so the cast
136
+ # op_id can be computed by Integer arithmetic.
137
+ CAMonOp.__build__(abs_i_node,
138
+ CAMonOp::CAST_BASE + CArray.data_type_code(CA_FLOAT64))
139
+ else
140
+ abs_i_node
141
+ end
142
+ else
143
+ __abs_eager_um__.bind_call(self)
144
+ end
145
+ end
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # real / imag lazy fuse (post-IC follow-up):
149
+ #
150
+ # Eager `real`/`imag` in lib/carray/math.rb return CAField views
151
+ # (zero-copy, mutable byte-offset access into complex storage) or
152
+ # fresh template entities for non-complex. Both break a lazy chain
153
+ # when applied to a lazy parent (CAField materialises the parent into
154
+ # an entity to byte-offset into). For lazy parents we re-express via
155
+ # chain composition so the chain stays fused:
156
+ #
157
+ # complex parent .real → CAMonOp(cast_<float>)
158
+ # (cmplx128→f64 / cmplx64→f32 casts pick up the real part —
159
+ # verified equivalent to existing CAField path for read-only
160
+ # consumption)
161
+ #
162
+ # complex parent .imag → CAMonOp(cast_<float>) ∘ CAMonOp(imag_i)
163
+ # (imag_i puts cimag in the real component of a same-data_type slot,
164
+ # cast then extracts it as float — same trick as abs)
165
+ #
166
+ # non-complex parent .real → self (= pass-through, chain unchanged)
167
+ #
168
+ # non-complex parent .imag → CAMonOp(imag_i) (= same-shape lazy zero)
169
+ #
170
+ # Eager parents fall through to the existing math.rb implementation
171
+ # (= CAField for complex, CARefer/template for non-complex) so the
172
+ # mutable-setter use cases (`.real = val` / `.imag = val`) remain
173
+ # supported. Lazy views are inherently read-only so the loss of
174
+ # mutability on the lazy path is not a regression.
175
+ # ---------------------------------------------------------------------------
176
+
177
+ alias_method :__real_eager__, :real
178
+ __real_eager_um__ = instance_method(:__real_eager__)
179
+ define_method(:real) do
180
+ if __lazy_view__?
181
+ if complex?
182
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
183
+ # float_dt is a Symbol; convert via data_type_code for kernel
184
+ # op_id arithmetic.
185
+ CAMonOp.__build__(self,
186
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
187
+ else
188
+ # `real` of a real array is the array itself; preserve the lazy
189
+ # chain unchanged (eager returns self[] = CARefer wrap, but for
190
+ # lazy chain consumption returning self keeps streaming intact).
191
+ self
192
+ end
193
+ else
194
+ __real_eager_um__.bind_call(self)
195
+ end
196
+ end
197
+
198
+ # @!visibility private
199
+ alias_method :__imag_eager__, :imag
200
+ __imag_eager_um__ = instance_method(:__imag_eager__)
201
+ define_method(:imag) do
202
+ if __lazy_view__?
203
+ imag_i_node = CAMonOp.__build__(self, CAMonOp::OP_IMAG_I)
204
+ if complex?
205
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
206
+ # float_dt is a Symbol; convert via data_type_code for kernel
207
+ # op_id arithmetic.
208
+ CAMonOp.__build__(imag_i_node,
209
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
210
+ else
211
+ imag_i_node
212
+ end
213
+ else
214
+ __imag_eager_um__.bind_call(self)
215
+ end
216
+ end
217
+
218
+ # ---------------------------------------------------------------------------
219
+ # arg lazy fuse: eager `arg` always returns f64 regardless of input
220
+ # dtype (data_type-changing monop; see MkKernel.monop :arg output rule).
221
+ # CAMonOp's cast-before invariant requires input dtype == output dtype
222
+ # at each in-place step, so `arg` can't sit directly in the substrate.
223
+ # Chain compose via the preserve-dtype `arg_i` primitive:
224
+ #
225
+ # integer / bool parent → cast_f64 ∘ arg_i (result f64)
226
+ # float parent → arg_i (result same float)
227
+ # complex parent → cast_<float> ∘ arg_i (arg_i writes real
228
+ # component of complex slot; cast extracts
229
+ # the real part, same trick as abs / imag)
230
+ #
231
+ # Note: eager `arg` always widens to f64. The lazy path preserves
232
+ # the operand's float / complex width (f32 → f32, cmplx64 → f32)
233
+ # rather than always going to f64 — matches the general lazy-substrate
234
+ # rule "scalar keeps operand's precision" (see ca_lazy_wrap_scalar
235
+ # header). A user needing exact eager-parity can wrap with
236
+ # `.to_type(:float64)` before / after.
237
+ # ---------------------------------------------------------------------------
238
+
239
+ alias_method :__arg_eager__, :arg
240
+ __arg_eager_um__ = instance_method(:__arg_eager__)
241
+ define_method(:arg) do
242
+ if __lazy_view__?
243
+ parent = self
244
+ if integer? || boolean?
245
+ parent = CAMonOp.__build__(parent,
246
+ CAMonOp::CAST_BASE +
247
+ CArray.data_type_code(CA_FLOAT64))
248
+ end
249
+ arg_i_node = CAMonOp.__build__(parent, CAMonOp::OP_ARG_I)
250
+ if complex?
251
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
252
+ CAMonOp.__build__(arg_i_node,
253
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
254
+ else
255
+ arg_i_node
256
+ end
257
+ else
258
+ __arg_eager_um__.bind_call(self)
259
+ end
260
+ end
261
+
262
+ # ---------------------------------------------------------------------------
263
+ # Binop dispatch. Operator entries are redefined so a lazy operand on
264
+ # either side routes into CABinOp.__build__. Op scope:
265
+ # - 5 arithmetic: + - * / **
266
+ # - 3 bitwise: & | ^
267
+ # - 2 shifts: << >>
268
+ # - 2 misc: %, rcp_mul
269
+ # ---------------------------------------------------------------------------
270
+
271
+ LAZY_BINOP_OP_IDS = {
272
+ :+ => CABinOp::OP_ADD,
273
+ :- => CABinOp::OP_SUB,
274
+ :* => CABinOp::OP_MUL,
275
+ :/ => CABinOp::OP_DIV,
276
+ :** => CABinOp::OP_POW,
277
+ :& => CABinOp::OP_BIT_AND,
278
+ :| => CABinOp::OP_BIT_OR,
279
+ :^ => CABinOp::OP_BIT_XOR,
280
+ :<< => CABinOp::OP_BIT_LSHIFT,
281
+ :>> => CABinOp::OP_BIT_RSHIFT,
282
+ :% => CABinOp::OP_MOD,
283
+ :rcp_mul => CABinOp::OP_RCP_MUL,
284
+
285
+ # Float-only binops registered eagerly by mkkernel; the lazy entries
286
+ # here pick them up so `a.lazy.hypot(b)` etc. ride the substrate.
287
+ :copysign => CABinOp::OP_COPYSIGN,
288
+ :logaddexp => CABinOp::OP_LOGADDEXP,
289
+ :nextafter => CABinOp::OP_NEXTAFTER,
290
+ :fmod => CABinOp::OP_FMOD,
291
+ :atan2 => CABinOp::OP_ATAN2,
292
+ :hypot => CABinOp::OP_HYPOT,
293
+
294
+ # Pair-wise max / min (NaN-skip via C99 fmax/fmin on float branch).
295
+ :pmax => CABinOp::OP_PMAX,
296
+ :pmin => CABinOp::OP_PMIN,
297
+
298
+ # Pair-wise max / min, NaN-propagate variant.
299
+ :maximum => CABinOp::OP_MAXIMUM,
300
+ :minimum => CABinOp::OP_MINIMUM,
301
+
302
+ # Boolean word forms (bool + object; plain mask propagation, no
303
+ # Kleene fixup — see the CA_BINOP_AND note in ca_binop_dispatch.h).
304
+ :and => CABinOp::OP_AND,
305
+ :or => CABinOp::OP_OR,
306
+ :xor => CABinOp::OP_XOR,
307
+
308
+ # IEEE 754 remainder (distinct semantics from `%` / `mod`: float
309
+ # branch uses C99 `remainder`, round-half-to-even).
310
+ :reminder => CABinOp::OP_REMINDER,
311
+ }.freeze
312
+
313
+ LAZY_BINOP_OP_IDS.each do |method_name, op_id|
314
+ eager_alias = :"__#{method_name}_eager__"
315
+ alias_method(eager_alias, method_name)
316
+ eager_um = instance_method(eager_alias)
317
+ define_method(method_name) do |other|
318
+ if __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
319
+ CABinOp.__build__(self, other, op_id)
320
+ else
321
+ eager_um.bind_call(self, other)
322
+ end
323
+ end
324
+ end
325
+
326
+ # Binop word aliases (`add` -> `+`, `mul` -> `*`, `bit_and` -> `&`, …) were
327
+ # installed by rb_define_alias at Init time (see MkKernel.alias_binop in
328
+ # ext/carray_kernels_*.c) BEFORE the redefinitions above. The C alias
329
+ # snapshots the original eager method entry, so the word form still points
330
+ # at the original C function and skips lazy dispatch — `a.lazy.add(1)`
331
+ # materialises instead of building a CABinOp node. Re-alias here so the
332
+ # word form follows the redefined operator method.
333
+ {
334
+ add: :+,
335
+ sub: :-,
336
+ mul: :*,
337
+ div: :/,
338
+ mod: :%,
339
+ bit_and: :&,
340
+ bit_or: :|,
341
+ bit_xor: :^,
342
+ bit_lshift: :<<,
343
+ bit_rshift: :>>,
344
+ }.each do |word, op|
345
+ alias_method(word, op)
346
+ end
347
+
348
+ # `pow` is the canonical C method (defined by rb_define_method in
349
+ # ext/ca_op_ipower.c) that `**` is rb_define_alias'd to. The `**`
350
+ # override in this file (with the ipower Integer-exponent fast path)
351
+ # does not propagate to `pow` through the C alias, so `a.lazy.pow(2)`
352
+ # falls to eager. Re-alias so `pow` follows the redefined `**`.
353
+ alias_method :pow, :**
354
+
355
+ # `fmax` / `fmin` are rb_define_alias'd to `pmax` / `pmin` at Init
356
+ # time (same C-alias-snapshot trap). Re-alias so the C99-familiar
357
+ # spellings follow the redefined lazy dispatch above.
358
+ alias_method :fmax, :pmax
359
+ alias_method :fmin, :pmin
360
+
361
+ # ---------------------------------------------------------------------------
362
+ # Triop dispatch (fma / fms / clip). CATriOp is the CABinOp analog for
363
+ # three-operand element-wise ops. Each Ruby method redefined below
364
+ # dispatches to CATriOp.__build__ when any of self / op2 / op3 is a
365
+ # lazy view, and falls to the eager C method otherwise.
366
+ #
367
+ # `clip` is the strict-clamp entry (`__clip_ki__`, called by the
368
+ # lib/carray/basics.rb `clip` wrapper's both-bounds-present path). The
369
+ # nil-bound one-sided cases route through the wrapper's `pmax` / `pmin`
370
+ # calls, which themselves lazy-fuse via LAZY_BINOP_OP_IDS above — so
371
+ # `a.lazy.clip(nil, hi)` and `a.lazy.clip(lo, nil)` fuse without a
372
+ # dedicated triop entry.
373
+ # ---------------------------------------------------------------------------
374
+
375
+ LAZY_TRIOP_OP_IDS = {
376
+ fma: CATriOp::OP_FMA,
377
+ fms: CATriOp::OP_FMS,
378
+ __clip_ki__: CATriOp::OP_CLIP,
379
+ }.freeze
380
+
381
+ LAZY_TRIOP_OP_IDS.each do |method_name, op_id|
382
+ eager_alias = :"__#{method_name}_eager_triop__"
383
+ alias_method(eager_alias, method_name)
384
+ eager_um = instance_method(eager_alias)
385
+ define_method(method_name) do |op2, op3|
386
+ lazy_receiver = __lazy_view__? ||
387
+ (op2.is_a?(CArray) && op2.__lazy_view__?) ||
388
+ (op3.is_a?(CArray) && op3.__lazy_view__?)
389
+ if lazy_receiver
390
+ CATriOp.__build__(self, op2, op3, op_id)
391
+ else
392
+ eager_um.bind_call(self, op2, op3)
393
+ end
394
+ end
395
+ end
396
+
397
+ # `**` integer-exponent lazy fast path.
398
+ #
399
+ # The default OP_POW path above goes through the `power` binop kernel,
400
+ # which uses `pow(x, p)` / `cpow(x, p)` (transcendental, ~20-50 ns/cell)
401
+ # for float / complex parents. When `other` is an Integer, the eager
402
+ # `rb_ca_pow` dispatches to `rb_ca_ipower` (= binary exponentiation via
403
+ # `op_powi_<type>`, ~1-3 mul/cell, 3-5x faster than the transcendental
404
+ # form). Mirror that dispatch here so `a.lazy ** 2` rides the same
405
+ # fast path.
406
+ #
407
+ # Routing rule (matches eager `rb_ca_pow`):
408
+ # - lazy parent + Integer exponent + Float/Complex parent data_type
409
+ # -> wrap exponent as CScalar.int64 + CABinOp(OP_IPOWER)
410
+ # - lazy parent (other cases) -> CABinOp(OP_POW) [existing]
411
+ # - else -> eager (which itself dispatches to ipower internally)
412
+ #
413
+ # Integer parents intentionally stay on OP_POW: the mkkernel-generated
414
+ # `power` binop kernel already uses `op_powi_<type>` for integer data_types
415
+ # via the `int:` expr in MkKernel.binop :power, so OP_POW + int parent
416
+ # is already fast. The lazy-substrate gap was Float/Complex only.
417
+ __pow_eager_um__ = instance_method(:"__**_eager__")
418
+ define_method(:**) do |other|
419
+ if other.is_a?(Integer) && (float? || complex?) &&
420
+ (__lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?))
421
+ CABinOp.__build__(self, CScalar.int64.tap { |s| s[0] = other },
422
+ CABinOp::OP_IPOWER)
423
+ elsif __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
424
+ CABinOp.__build__(self, other, CABinOp::OP_POW)
425
+ else
426
+ __pow_eager_um__.bind_call(self, other)
427
+ end
428
+ end
429
+
430
+ # coerce: when self is a lazy view and the scalar appears on the LEFT
431
+ # (e.g. `2 * a.lazy`), Ruby's Numeric#* calls a.lazy.coerce(2). The
432
+ # default coerce would unwrap the lazy-ness via eager scalar promotion;
433
+ # here we keep it lazy by returning [scalar_as_cscalar, self], so the
434
+ # subsequent operator call ends up with a lazy receiver and triggers
435
+ # the CABinOp builder.
436
+ # ---------------------------------------------------------------------------
437
+ # bincmp / moncmp dispatch.
438
+ #
439
+ # Comparison output is always boolean8_t, so it cannot reuse the CABinOp
440
+ # in-place trick. CABinCmp pulls both operands into operand-data_type
441
+ # scratches and writes boolean to the output buffer. Integer is_nan /
442
+ # is_inf / is_finite use existing per-data_type kernels (which handle the
443
+ # integer const-false/true result and mask skip).
444
+ #
445
+ # Scope: 7 bincmp + 3 moncmp + operator aliases (`<` / `>` / `<=` / `>=`)
446
+ # for canonical Ruby comparison syntax. Note: `==` / `eql?` are NOT
447
+ # comparison ops — eager `CArray#==` (rb_ca_equal) is array-level
448
+ # equality returning bool, not element-wise. Element-wise equality is
449
+ # `eq` / `feq`.
450
+ #
451
+ # `feq` is arity 1 in eager (compile-time FLT_EPSILON / DBL_EPSILON);
452
+ # we mirror that here. Runtime eps is a future extension (struct field
453
+ # already reserved).
454
+ # ---------------------------------------------------------------------------
455
+
456
+ LAZY_BINCMP_OP_IDS = {
457
+ # Canonical method names + operator aliases (rb_define_alias in C
458
+ # creates separate dispatch entries, so we override both).
459
+ :lt => CABinCmp::OP_LT,
460
+ :< => CABinCmp::OP_LT,
461
+ :gt => CABinCmp::OP_GT,
462
+ :> => CABinCmp::OP_GT,
463
+ :le => CABinCmp::OP_LE,
464
+ :<= => CABinCmp::OP_LE,
465
+ :ge => CABinCmp::OP_GE,
466
+ :>= => CABinCmp::OP_GE,
467
+ :eq => CABinCmp::OP_EQ,
468
+ :ne => CABinCmp::OP_NE,
469
+ :feq => CABinCmp::OP_FEQ,
470
+ }.freeze
471
+
472
+ LAZY_BINCMP_OP_IDS.each do |method_name, op_id|
473
+ eager_alias = :"__#{method_name}_eager_bincmp__"
474
+ alias_method(eager_alias, method_name)
475
+ eager_um = instance_method(eager_alias)
476
+ define_method(method_name) do |other|
477
+ if __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
478
+ CABinCmp.__build__(self, other, op_id)
479
+ else
480
+ eager_um.bind_call(self, other)
481
+ end
482
+ end
483
+ end
484
+
485
+ # tolerance-bearing bincmp ops (= is_close / is_equiv) use the same
486
+ # CABinCmp dispatch but with a 2nd `tol` positional arg. Kept in a
487
+ # separate dict because the LAZY_BINCMP_OP_IDS define_method block above
488
+ # uses arity 1 (|other|); these need arity 2 (|other, tol|).
489
+ LAZY_BINCMP_TOL_OP_IDS = {
490
+ :is_close => CABinCmp::OP_IS_CLOSE,
491
+ :is_equiv => CABinCmp::OP_IS_EQUIV,
492
+ }.freeze
493
+
494
+ LAZY_BINCMP_TOL_OP_IDS.each do |method_name, op_id|
495
+ eager_alias = :"__#{method_name}_eager_bincmp_tol__"
496
+ alias_method(eager_alias, method_name)
497
+ eager_um = instance_method(eager_alias)
498
+ define_method(method_name) do |other, tol|
499
+ if __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
500
+ CABinCmp.__build__(self, other, op_id, tol.to_f.abs)
501
+ else
502
+ eager_um.bind_call(self, other, tol)
503
+ end
504
+ end
505
+ end
506
+
507
+ # @!visibility private
508
+ LAZY_MONCMP_OP_IDS = {
509
+ is_nan: CAMonCmp::OP_IS_NAN,
510
+ is_inf: CAMonCmp::OP_IS_INF,
511
+ is_finite: CAMonCmp::OP_IS_FINITE,
512
+ is_invalid: CAMonCmp::OP_IS_INVALID,
513
+ signbit: CAMonCmp::OP_SIGNBIT,
514
+ }.freeze
515
+
516
+ LAZY_MONCMP_OP_IDS.each do |name, op_id|
517
+ eager_alias = :"__#{name}_eager_moncmp__"
518
+ alias_method(eager_alias, name)
519
+ eager_um = instance_method(eager_alias)
520
+ define_method(name) do
521
+ if __lazy_view__?
522
+ CAMonCmp.__build__(self, op_id)
523
+ else
524
+ eager_um.bind_call(self)
525
+ end
526
+ end
527
+ end
528
+
529
+ # @!visibility private
530
+ alias_method :__coerce_eager__, :coerce
531
+ coerce_um = instance_method(:__coerce_eager__)
532
+ define_method(:coerce) do |other|
533
+ if __lazy_view__?
534
+ dt_name = data_type_name
535
+ if CScalar.respond_to?(dt_name)
536
+ s = CScalar.send(dt_name)
537
+ s[0] = other
538
+ [s, self]
539
+ else
540
+ coerce_um.bind_call(self, other)
541
+ end
542
+ else
543
+ coerce_um.bind_call(self, other)
544
+ end
545
+ end
546
+ end
547
+
548
+ # ---------------------------------------------------------------------------
549
+ # CAMonOp / CALazyMarker surface methods.
550
+ #
551
+ # Materialise-on-first-touch for Enumerable receivers, MV export reject,
552
+ # inspect/dump_tree summary that does NOT materialise.
553
+ # ---------------------------------------------------------------------------
554
+
555
+ class CAMonOp
556
+ # op_id => :name reverse lookup
557
+ OP_NAMES = CArray::LAZY_MONOP_OP_IDS.invert.freeze
558
+
559
+ # --- inspect / dump_tree (no materialise) ----------------------------
560
+
561
+ def op_label
562
+ op_id = __op_id__
563
+ if op_id >= CAMonOp::CAST_BASE
564
+ "cast_#{CArray.data_type_name(op_id - CAMonOp::CAST_BASE)}"
565
+ else
566
+ (OP_NAMES[op_id] || "op_#{op_id}").to_s
567
+ end
568
+ end
569
+ private :op_label
570
+
571
+ # @overload inspect
572
+ # Returns a short summary of the lazy chain without
573
+ # materialising.
574
+ # @return [String]
575
+ def inspect
576
+ "#<CAMonOp #{op_label}(#{parent.inspect})>"
577
+ end
578
+
579
+ # @overload to_s
580
+ # Alias of {#inspect}.
581
+ # @return [String]
582
+ def to_s
583
+ inspect
584
+ end
585
+
586
+ # @overload dump_tree(indent = 0)
587
+ # Returns an ASCII summary of the lazy expression tree.
588
+ # Does not materialise.
589
+ # @param indent [Integer] leading indentation depth.
590
+ # @return [String]
591
+ def dump_tree(indent = 0)
592
+ pad = " " * indent
593
+ out = +"#{pad}#{op_label}\n"
594
+ out << if parent.is_a?(CAMonOp)
595
+ parent.dump_tree(indent + 1)
596
+ else
597
+ "#{" " * (indent + 1)}#{parent.class}(#{parent.data_type_name}, #{parent.shape.inspect})\n"
598
+ end
599
+ out
600
+ end
601
+
602
+ # @overload each { |value| ... }
603
+ # Materialises `self` and yields each element of the resulting
604
+ # entity.
605
+ # @yieldparam value [Object]
606
+ # @return [Object]
607
+ def each(&block)
608
+ to_ca.each(&block)
609
+ end
610
+
611
+ # @overload to_a
612
+ # Materialises `self` and returns the resulting Array.
613
+ # @return [Array]
614
+ def to_a
615
+ to_ca.to_a
616
+ end
617
+
618
+ # @overload sort(*args, **kwargs)
619
+ # Materialises `self` into a copy and returns
620
+ # `entity.sort(*args, **kwargs)`.
621
+ # @return [CArray]
622
+ def sort(*args, **kwargs)
623
+ copy.sort(*args, **kwargs)
624
+ end
625
+
626
+ # --- MemoryView export → TypeError + .copy recommendation -----------
627
+
628
+ # bulk-memory-view / Numo / Apache Arrow consumers go through the MV
629
+ # protocol, which requires a contiguous backing buffer. Lazy views
630
+ # have no backing buffer until materialised; raise with a clear hint.
631
+ #
632
+ # MemoryView is consulted via to_memory_view_internal on the receiver
633
+ # in some setups; the safest cross-cutting hook is the to_memory_view
634
+ # method (defined by carray_memory_view producer). We override to
635
+ # raise. Importers that probe via `rb_memory_view_get` will hit the
636
+ # producer slot, which we DON'T currently override at C-level — but
637
+ # the MV producer for CAView rejects backing-bufferless views by
638
+ # default; tests below verify both Ruby-level and C-level reject.
639
+ # @overload to_memory_view
640
+ # Not supported: a lazy view has no backing buffer.
641
+ # @return [void]
642
+ # @raise [TypeError] always; call `.copy` first.
643
+ def to_memory_view
644
+ raise TypeError,
645
+ "lazy view (CAMonOp) has no backing buffer; call `.copy` " \
646
+ "first to materialise into an entity before exporting via " \
647
+ "MemoryView."
648
+ end
649
+ end
650
+
651
+ # Lazy element-wise view of a binary operation on two operands. Nothing is
652
+ # computed until the chain is forced, so a composed expression evaluates in
653
+ # one pass instead of materialising an intermediate per operator.
654
+ #
655
+ # Produced by an operator on `.lazy` operands or inside `CArray.fuse`, not
656
+ # constructed directly.
657
+ class CABinOp
658
+ # op_id => :name reverse lookup
659
+ OP_NAMES = CArray::LAZY_BINOP_OP_IDS.invert.merge(
660
+ OP_IPOWER => :ipow,
661
+ ).freeze
662
+
663
+ def op_label
664
+ op_id = __op_id__
665
+ (OP_NAMES[op_id] || "op_#{op_id}").to_s
666
+ end
667
+ private :op_label
668
+
669
+ # @overload inspect
670
+ # Returns a summary of the binop chain without materialising.
671
+ # @return [String]
672
+ def inspect
673
+ "#<CABinOp #{op_label}(#{parent.inspect}, #{__binop_right__.inspect})>"
674
+ end
675
+
676
+ # @overload to_s
677
+ # Alias of {#inspect}.
678
+ # @return [String]
679
+ def to_s
680
+ inspect
681
+ end
682
+
683
+ # @overload dump_tree(indent = 0)
684
+ # Returns an ASCII summary of the lazy expression tree, walking
685
+ # both children. Does not materialise.
686
+ # @return [String]
687
+ def dump_tree(indent = 0)
688
+ pad = " " * indent
689
+ out = +"#{pad}#{op_label}\n"
690
+ [parent, __binop_right__].each do |child|
691
+ out << if child.is_a?(CAMonOp) || child.is_a?(CABinOp)
692
+ child.dump_tree(indent + 1)
693
+ else
694
+ "#{" " * (indent + 1)}#{child.class}(#{child.data_type_name}, #{child.shape.inspect})\n"
695
+ end
696
+ end
697
+ out
698
+ end
699
+
700
+ # @overload each { |value| ... }
701
+ # Materialises `self` and yields each element.
702
+ # @return [Object]
703
+ def each(&block)
704
+ to_ca.each(&block)
705
+ end
706
+
707
+ # @overload to_a
708
+ # Materialises `self` and returns the resulting Array.
709
+ # @return [Array]
710
+ def to_a
711
+ to_ca.to_a
712
+ end
713
+
714
+ # @overload sort(*args, **kwargs)
715
+ # Materialises `self` and sorts the resulting entity.
716
+ # @return [CArray]
717
+ def sort(*args, **kwargs)
718
+ to_ca.sort(*args, **kwargs)
719
+ end
720
+
721
+ # @overload to_memory_view
722
+ # Not supported for lazy binops.
723
+ # @raise [TypeError] always.
724
+ def to_memory_view
725
+ raise TypeError,
726
+ "lazy view (CABinOp) has no backing buffer; call `.to_ca` " \
727
+ "first to materialise into an entity before exporting via " \
728
+ "MemoryView."
729
+ end
730
+ end
731
+
732
+ class CALazyMarker
733
+ # @overload inspect
734
+ # Returns a short summary showing the wrapped entity.
735
+ # @return [String]
736
+ def inspect
737
+ "#<CALazyMarker over #{parent.inspect}>"
738
+ end
739
+
740
+ # @overload to_s
741
+ # Alias of {#inspect}.
742
+ # @return [String]
743
+ def to_s
744
+ inspect
745
+ end
746
+
747
+ # @overload dump_tree(indent = 0)
748
+ # Returns an ASCII summary of the marker and its parent entity.
749
+ # @return [String]
750
+ def dump_tree(indent = 0)
751
+ pad = " " * indent
752
+ "#{pad}lazy(marker)\n" \
753
+ "#{" " * (indent + 1)}#{parent.class}(#{parent.data_type_name}, #{parent.shape.inspect})\n"
754
+ end
755
+
756
+ # @overload each { |value| ... }
757
+ # Yields each element of the wrapped entity (no materialise
758
+ # needed).
759
+ # @return [Object]
760
+ def each(&block)
761
+ parent.each(&block)
762
+ end
763
+
764
+ # @overload to_a
765
+ # Returns the wrapped entity's Array.
766
+ # @return [Array]
767
+ def to_a
768
+ parent.to_a
769
+ end
770
+
771
+ # @overload to_memory_view
772
+ # Not supported: markers do not own a backing buffer.
773
+ # @raise [TypeError] always; call `.copy` first.
774
+ def to_memory_view
775
+ raise TypeError,
776
+ "lazy marker (CALazyMarker) has no backing buffer; call " \
777
+ "`.copy` first to materialise into an entity before " \
778
+ "exporting via MemoryView."
779
+ end
780
+ end
781
+
782
+ # ---------------------------------------------------------------------------
783
+ # Lazy views override `to_ca` to force evaluation.
784
+ #
785
+ # `CArray#to_ca` returns `self` (a concrete-backed CArray — entity or data
786
+ # view — is already a CArray, no copy needed). A lazy view has no data until
787
+ # evaluated, so `to_ca` must produce it: it materialises into a new entity,
788
+ # matching the Ruby `Enumerable#to_a` / lazy `force` convention. (`copy` is
789
+ # the materialise path; on a lazy view it evaluates the expression.)
790
+ #
791
+ # That entity is detached from the operands, so writes to it reach nothing:
792
+ # `writable: true` is refused rather than answered with a result that would
793
+ # swallow them.
794
+ # ---------------------------------------------------------------------------
795
+ [CAMonOp, CABinOp, CATriOp, CAMonCmp, CABinCmp, CALazyMarker].each do |klass|
796
+ klass.class_eval do
797
+ def to_ca(writable: false)
798
+ if writable
799
+ raise "#{self.class}#to_ca evaluates into a new entity; " \
800
+ "it can't satisfy `writable: true'"
801
+ end
802
+ copy
803
+ end
804
+ end
805
+ end
806
+
807
+ # ---------------------------------------------------------------------------
808
+ # CArray.fuse
809
+ #
810
+ # Transient lazy-fusion scope: wraps each CArray argument with `.lazy`,
811
+ # yields the wrappers (and any non-CArray args pass-through unchanged)
812
+ # to the block, then on block exit auto-materialises a bare lazy view
813
+ # return value into an entity. Non-lazy return values (Numeric, plain
814
+ # CArray, Array, nil, etc.) pass through as-is.
815
+ #
816
+ # Design notes:
817
+ # - Non-CArray args pass through eager (= polymorphic numeric helper
818
+ # idiom: `magnus(25.0)` → Float and `magnus(arr)` → CArray carried by
819
+ # a single helper definition, combined with the `CArray::CoreExtensions`
820
+ # refinement which gives Float/Integer/Rational the same postfix math
821
+ # API as CArray instances).
822
+ # - Shadow args are read-only (CA_FLAG_READ_ONLY via `.lazy`), so
823
+ # destructive ops on them raise naturally.
824
+ # - A nested fuse materialises at its own inner exit.
825
+ # - A bare lazy return auto-materialises; escaping the lazy view (e.g.
826
+ # stashing it in an Array) is the user's responsibility.
827
+ # ---------------------------------------------------------------------------
828
+ class << CArray
829
+ # @overload fuse(*args) { |*shadows| ... }
830
+ # Runs a transient lazy-fusion scope: wraps each CArray argument
831
+ # with `.lazy`, yields the wrappers (and any non-CArray args)
832
+ # to the block, then auto-materialises a bare lazy return value
833
+ # into an entity. Non-lazy returns pass through as-is.
834
+ # @param args [Array<CArray, Object>] operands.
835
+ # @yieldparam shadows [Array<CArray, Object>] lazy wrappers
836
+ # paired with pass-through non-CArray operands.
837
+ # @return [Object]
838
+ # @raise [LocalJumpError] when no block is given.
839
+ def fuse(*args)
840
+ raise LocalJumpError, "CArray.fuse requires a block" unless block_given?
841
+ shadows = args.map { |a| a.is_a?(CArray) ? a.lazy : a }
842
+ result = yield(*shadows)
843
+ case result
844
+ when CAMonOp, CABinOp, CAMonCmp, CABinCmp, CALazyMarker
845
+ result.to_ca
846
+ else
847
+ result
848
+ end
849
+ end
850
+
851
+ # CArray.lazy(*args) { |lazies| ... } — dual of fuse
852
+ #
853
+ # Like `fuse`, wraps each CArray argument with `.lazy` and yields it to
854
+ # the block, but **does not auto-materialise at block exit** (= returns
855
+ # the lazy structure as-is). If the block return is non-lazy (= Numeric
856
+ # / entity CArray / Array etc.) it's pass-through (= same polymorphic
857
+ # semantics as fuse).
858
+ #
859
+ # Use cases:
860
+ # - Passing a chain between functions: build the lazy expression
861
+ # inside the function and materialise at the caller
862
+ # (= `.to_ca` / `.sum` / `.mean(axis:)` etc.)
863
+ # - Reusable expressions: apply the same expr to multiple datasets
864
+ # - debug / dump_tree: observe the lazy structure as-is
865
+ # - Pick the materialise form later: full materialise or reduction
866
+ #
867
+ # Example:
868
+ # expr = CArray.lazy(a, b) { |s, o| (s + o) * 2 }
869
+ # expr.class #=> CABinOp (lazy view)
870
+ # expr.to_ca # full materialise
871
+ # expr.sum # reduction (= chain + reduce in 1 pass)
872
+ #
873
+ # Polymorphic semantics (= symmetric with fuse):
874
+ # CArray.lazy(25.0, b) { |s, o| s + o } # s=25.0 Float pass-through
875
+ # CArray.lazy(arr, b) { |s, o| s + o } # s=arr.lazy
876
+ # @overload lazy(*args) { |*shadows| ... }
877
+ # Like {.fuse} but does not auto-materialise: returns whatever
878
+ # the block yields (typically a lazy view) so the expression
879
+ # can be materialised later via `.to_ca`, `.sum`, `.mean`, etc.
880
+ # @param args [Array<CArray, Object>] operands.
881
+ # @yieldparam shadows [Array<CArray, Object>] lazy wrappers.
882
+ # @return [Object]
883
+ # @raise [LocalJumpError] when no block is given.
884
+ def lazy(*args)
885
+ raise LocalJumpError, "CArray.lazy requires a block" unless block_given?
886
+ shadows = args.map { |a| a.is_a?(CArray) ? a.lazy : a }
887
+ yield(*shadows)
888
+ end
889
+ end