carray 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -25
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +1 -1
  5. data/NEWS.md +3 -0
  6. data/README.md +128 -44
  7. data/carray.gemspec +22 -24
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1208 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +125 -0
  15. data/ext/ca_binop_dispatch.h +159 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +241 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4321 -0
  26. data/ext/ca_kernel_iterator.h +2603 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +809 -0
  33. data/ext/ca_obj_binop.c +892 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +965 -0
  39. data/ext/ca_obj_face.c +670 -0
  40. data/ext/ca_obj_face.h +247 -0
  41. data/ext/ca_obj_fake.c +228 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +306 -0
  45. data/ext/ca_obj_grid.c +858 -440
  46. data/ext/ca_obj_meld.c +1034 -0
  47. data/ext/ca_obj_moncmp.c +569 -0
  48. data/ext/ca_obj_monop.c +1111 -0
  49. data/ext/ca_obj_object.c +774 -298
  50. data/ext/ca_obj_record.c +468 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +569 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +616 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1296 -0
  58. data/ext/ca_obj_shift.c +230 -792
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2501 -0
  62. data/ext/ca_obj_string.c +268 -0
  63. data/ext/ca_obj_tile.c +614 -0
  64. data/ext/ca_obj_time.c +546 -0
  65. data/ext/ca_obj_timedelta.c +435 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +746 -0
  68. data/ext/ca_obj_unbound_repeat.c +208 -241
  69. data/ext/ca_obj_window.c +1131 -563
  70. data/ext/ca_op_byte_swap.c +175 -0
  71. data/ext/ca_op_ipower.c +319 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +430 -0
  75. data/ext/ca_sweep_engine.h +157 -0
  76. data/ext/ca_transform_common.c +228 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +795 -402
  80. data/ext/carray_access.c +831 -711
  81. data/ext/carray_attribute.c +98 -330
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +283 -0
  84. data/ext/carray_call_cfunc.c +1360 -828
  85. data/ext/carray_call_cfunc.h +160 -0
  86. data/ext/carray_cast.c +1212 -301
  87. data/ext/carray_cast_func.rb +81 -40
  88. data/ext/carray_class.c +53 -63
  89. data/ext/carray_config.h +28 -0
  90. data/ext/carray_conversion.c +350 -346
  91. data/ext/carray_copy.c +156 -268
  92. data/ext/carray_core.c +1342 -199
  93. data/ext/carray_count.c +312 -0
  94. data/ext/carray_data_type.c +43 -19
  95. data/ext/carray_element.c +585 -213
  96. data/ext/carray_factorize.c +2542 -0
  97. data/ext/carray_generate.c +230 -559
  98. data/ext/carray_histogram.c +490 -0
  99. data/ext/carray_hold.c +228 -0
  100. data/ext/carray_index_classifier.c +1035 -0
  101. data/ext/carray_index_classifier.h +27 -0
  102. data/ext/carray_internal.h +120 -0
  103. data/ext/carray_kernels_bincmp.c +4445 -0
  104. data/ext/carray_kernels_binop.c +10979 -0
  105. data/ext/carray_kernels_init.c +36 -0
  106. data/ext/carray_kernels_map.c +3466 -0
  107. data/ext/carray_kernels_moncmp.c +2096 -0
  108. data/ext/carray_kernels_monop.c +18312 -0
  109. data/ext/carray_kernels_reduce_aggregate.c +25836 -0
  110. data/ext/carray_kernels_reduce_boolean.c +329 -0
  111. data/ext/carray_kernels_reduce_cumulative.c +14592 -0
  112. data/ext/carray_kernels_reduce_extreme.c +16947 -0
  113. data/ext/carray_kernels_reduce_variance.c +3909 -0
  114. data/ext/carray_kernels_scan.c +3692 -0
  115. data/ext/carray_kernels_search.c +32137 -0
  116. data/ext/carray_kernels_sort.c +10625 -0
  117. data/ext/carray_kernels_triop.c +1391 -0
  118. data/ext/carray_lazy.c +567 -0
  119. data/ext/carray_loop.c +88 -200
  120. data/ext/carray_mask.c +848 -154
  121. data/ext/carray_math_kernel.h +120 -0
  122. data/ext/carray_mathfunc.c +10 -241
  123. data/ext/carray_median_percentile.c +1257 -0
  124. data/ext/carray_memory_view.c +1625 -0
  125. data/ext/carray_operator.c +1526 -318
  126. data/ext/carray_order.c +664 -1394
  127. data/ext/carray_partition.c +416 -0
  128. data/ext/carray_random.c +518 -0
  129. data/ext/carray_scatter.c +357 -0
  130. data/ext/carray_slab.c +1219 -0
  131. data/ext/carray_slab.h +84 -0
  132. data/ext/carray_sort.c +829 -0
  133. data/ext/carray_sort_kernel.c +620 -0
  134. data/ext/carray_struct.c +695 -0
  135. data/ext/carray_test.c +343 -229
  136. data/ext/carray_undef.c +34 -17
  137. data/ext/carray_utils.c +175 -74
  138. data/ext/extconf.rb +216 -55
  139. data/ext/mk_call_cfunc.rb +480 -0
  140. data/ext/mkkernel.rb +8842 -0
  141. data/ext/ruby_carray.c +202 -101
  142. data/ext/version.h +4 -14
  143. data/ext/version.rb +5 -13
  144. data/lib/carray/arrow_tensor.rb +401 -0
  145. data/lib/carray/attribute.rb +166 -0
  146. data/lib/carray/autoload_carray.rb +220 -0
  147. data/lib/carray/autoload_method_extension.rb +44 -0
  148. data/lib/carray/axis_group.rb +711 -0
  149. data/lib/carray/basics.rb +481 -0
  150. data/lib/carray/bincount_nd.rb +358 -0
  151. data/lib/carray/block_iterator.rb +604 -0
  152. data/lib/carray/boolean_reduce.rb +109 -0
  153. data/lib/carray/categorical.rb +561 -0
  154. data/lib/carray/categorical_iterator.rb +1062 -0
  155. data/lib/carray/complex.rb +150 -0
  156. data/lib/carray/conditional.rb +216 -0
  157. data/lib/carray/const_string.rb +228 -0
  158. data/lib/carray/construct.rb +139 -328
  159. data/lib/carray/core_extensions.rb +240 -0
  160. data/lib/carray/data_type_extension.rb +233 -0
  161. data/lib/carray/fixlen_string.rb +95 -0
  162. data/lib/carray/frame/concat.rb +132 -0
  163. data/lib/carray/frame/convert.rb +95 -0
  164. data/lib/carray/frame/csv_parser.rb +211 -0
  165. data/lib/carray/frame/frame.rb +649 -0
  166. data/lib/carray/frame/group.rb +186 -0
  167. data/lib/carray/frame/io.rb +164 -0
  168. data/lib/carray/frame/join.rb +248 -0
  169. data/lib/carray/frame/records.rb +99 -0
  170. data/lib/carray/frame/sort.rb +113 -0
  171. data/lib/carray/frame/verbs.rb +299 -0
  172. data/lib/carray/frame.rb +16 -0
  173. data/lib/carray/histogram.rb +512 -0
  174. data/lib/carray/inspect.rb +37 -20
  175. data/lib/carray/iterator.rb +57 -349
  176. data/lib/carray/lazy.rb +889 -0
  177. data/lib/carray/mask_gap_fill.rb +200 -0
  178. data/lib/carray/math.rb +78 -342
  179. data/lib/carray/meld_reduce.rb +289 -0
  180. data/lib/carray/methods/align_addr.rb +116 -0
  181. data/lib/carray/methods/bin.rb +128 -0
  182. data/lib/carray/methods/bincount.rb +87 -0
  183. data/lib/carray/methods/bit_string.rb +92 -0
  184. data/lib/carray/methods/broadcast.rb +63 -0
  185. data/lib/carray/methods/choose.rb +39 -0
  186. data/lib/carray/methods/composition.rb +280 -0
  187. data/lib/carray/methods/gather_nd.rb +206 -0
  188. data/lib/carray/methods/index.rb +39 -0
  189. data/lib/carray/methods/insert_block.rb +99 -0
  190. data/lib/carray/methods/is_in.rb +141 -0
  191. data/lib/carray/methods/join.rb +90 -0
  192. data/lib/carray/methods/locate_addr.rb +47 -0
  193. data/lib/carray/methods/mask_duplicates.rb +41 -0
  194. data/lib/carray/methods/meshgrid.rb +91 -0
  195. data/lib/carray/methods/mode.rb +126 -0
  196. data/lib/carray/methods/nunique.rb +46 -0
  197. data/lib/carray/methods/resize.rb +56 -0
  198. data/lib/carray/methods/snap.rb +156 -0
  199. data/lib/carray/methods/string_format.rb +57 -0
  200. data/lib/carray/methods/unique.rb +47 -0
  201. data/lib/carray/methods/value_counts.rb +71 -0
  202. data/lib/carray/mkmf.rb +124 -101
  203. data/lib/carray/runtime.rb +108 -0
  204. data/lib/carray/serialize.rb +478 -167
  205. data/lib/carray/slab_iterator.rb +292 -0
  206. data/lib/carray/stack.rb +291 -0
  207. data/lib/carray/string.rb +56 -180
  208. data/lib/carray/string_operation_extension.rb +289 -0
  209. data/lib/carray/struct.rb +335 -323
  210. data/lib/carray/struct_builder.rb +697 -0
  211. data/lib/carray/table.rb +41 -2
  212. data/lib/carray/time.rb +2255 -38
  213. data/lib/carray/window_iterator.rb +655 -0
  214. data/lib/carray.rb +55 -57
  215. metadata +163 -130
  216. data/Rakefile +0 -51
  217. data/TODO.md +0 -18
  218. data/ext/ca_iter_block.c +0 -257
  219. data/ext/ca_iter_dimension.c +0 -299
  220. data/ext/ca_iter_window.c +0 -214
  221. data/ext/ca_obj_mapping.c +0 -644
  222. data/ext/carray_iterator.c +0 -641
  223. data/ext/carray_math.rb +0 -850
  224. data/ext/carray_numeric.c +0 -259
  225. data/ext/carray_sort_addr.c +0 -254
  226. data/ext/carray_stat.c +0 -2100
  227. data/ext/carray_stat_proc.rb +0 -1999
  228. data/ext/mkmath.rb +0 -741
  229. data/ext/ruby_ccomplex.c +0 -509
  230. data/ext/ruby_float_func.c +0 -86
  231. data/lib/carray/array.rb +0 -8
  232. data/lib/carray/autoload/autoload_base.rb +0 -19
  233. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  234. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  235. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  236. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  237. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  238. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  239. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  240. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  241. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  242. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  243. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  244. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  245. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  246. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  247. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  248. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  249. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  250. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  251. data/lib/carray/autoload.rb +0 -141
  252. data/lib/carray/basic.rb +0 -191
  253. data/lib/carray/broadcast.rb +0 -101
  254. data/lib/carray/compose.rb +0 -315
  255. data/lib/carray/convert.rb +0 -115
  256. data/lib/carray/info.rb +0 -110
  257. data/lib/carray/io/imagemagick.rb +0 -235
  258. data/lib/carray/mask.rb +0 -102
  259. data/lib/carray/math/histogram.rb +0 -177
  260. data/lib/carray/math/recurrence.rb +0 -93
  261. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  262. data/lib/carray/object/ca_obj_link.rb +0 -50
  263. data/lib/carray/object/ca_obj_pack.rb +0 -99
  264. data/lib/carray/obsolete.rb +0 -256
  265. data/lib/carray/ordering.rb +0 -181
  266. data/lib/carray/testing.rb +0 -51
  267. data/lib/carray/transform.rb +0 -109
  268. data/misc/Methods.ja.md +0 -182
  269. data/misc/NOTE +0 -51
  270. data/spec/Classes/CABitfield_spec.rb +0 -58
  271. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  272. data/spec/Classes/CABlock_spec.rb +0 -205
  273. data/spec/Classes/CAField_spec.rb +0 -39
  274. data/spec/Classes/CAGrid_spec.rb +0 -75
  275. data/spec/Classes/CAMap_spec.rb +0 -0
  276. data/spec/Classes/CAMapping_spec.rb +0 -105
  277. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  278. data/spec/Classes/CAObject_spec.rb +0 -33
  279. data/spec/Classes/CARefer_spec.rb +0 -93
  280. data/spec/Classes/CARepeat_spec.rb +0 -65
  281. data/spec/Classes/CASelect_spec.rb +0 -22
  282. data/spec/Classes/CAShift_spec.rb +0 -16
  283. data/spec/Classes/CAStruct_spec.rb +0 -71
  284. data/spec/Classes/CATranspose_spec.rb +0 -60
  285. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  286. data/spec/Classes/CAWindow_spec.rb +0 -54
  287. data/spec/Classes/CAWrap_spec.rb +0 -8
  288. data/spec/Classes/CArray_spec.rb +0 -184
  289. data/spec/Classes/CScalar_spec.rb +0 -55
  290. data/spec/Classes/ex1.rb +0 -46
  291. data/spec/Features/feature_130_spec.rb +0 -19
  292. data/spec/Features/feature_attributes_spec.rb +0 -280
  293. data/spec/Features/feature_boolean_spec.rb +0 -98
  294. data/spec/Features/feature_broadcast.rb +0 -116
  295. data/spec/Features/feature_cast_function.rb +0 -19
  296. data/spec/Features/feature_cast_spec.rb +0 -33
  297. data/spec/Features/feature_class_spec.rb +0 -84
  298. data/spec/Features/feature_complex_spec.rb +0 -42
  299. data/spec/Features/feature_composite_spec.rb +0 -124
  300. data/spec/Features/feature_convert_spec.rb +0 -46
  301. data/spec/Features/feature_copy_spec.rb +0 -123
  302. data/spec/Features/feature_creation_spec.rb +0 -84
  303. data/spec/Features/feature_element_spec.rb +0 -144
  304. data/spec/Features/feature_extream_spec.rb +0 -54
  305. data/spec/Features/feature_generate_spec.rb +0 -74
  306. data/spec/Features/feature_index_spec.rb +0 -69
  307. data/spec/Features/feature_mask_spec.rb +0 -580
  308. data/spec/Features/feature_math_spec.rb +0 -97
  309. data/spec/Features/feature_order_spec.rb +0 -146
  310. data/spec/Features/feature_ref_store_spec.rb +0 -209
  311. data/spec/Features/feature_serialization_spec.rb +0 -125
  312. data/spec/Features/feature_stat_spec.rb +0 -397
  313. data/spec/Features/feature_virtual_spec.rb +0 -48
  314. data/spec/Features/method_eq_spec.rb +0 -81
  315. data/spec/Features/method_is_nan_spec.rb +0 -12
  316. data/spec/Features/method_map_spec.rb +0 -54
  317. data/spec/Features/method_max_with.rb +0 -20
  318. data/spec/Features/method_min_with.rb +0 -19
  319. data/spec/Features/method_ne_spec.rb +0 -18
  320. data/spec/Features/method_project_spec.rb +0 -188
  321. data/spec/Features/method_ref_spec.rb +0 -27
  322. data/spec/Features/method_round_spec.rb +0 -11
  323. data/spec/Features/method_s_linspace_spec.rb +0 -48
  324. data/spec/Features/method_s_span_spec.rb +0 -14
  325. data/spec/Features/method_seq_spec.rb +0 -47
  326. data/spec/Features/method_sort_with.rb +0 -43
  327. data/spec/Features/method_sorted_with.rb +0 -29
  328. data/spec/Features/method_span_spec.rb +0 -42
  329. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  330. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  331. data/spec/spec_all.rb +0 -10
  332. data/utils/ca_ase.rb +0 -21
  333. data/utils/ca_methods.rb +0 -15
  334. data/utils/cast_checker.rb +0 -30
  335. data/utils/convert_test.rb +0 -73
  336. data/utils/extract_yard.rb +0 -22
  337. data/utils/guess_shape.rb +0 -76
  338. data/utils/monkey_patch_methods.rb +0 -62
  339. data/utils/remove_resource_fork.sh +0 -5
@@ -0,0 +1,1111 @@
1
+ /* ---------------------------------------------------------------------------
2
+
3
+ Lazy monadic element-wise op view (CAMonOp). Holds (parent, op_id,
4
+ output_data_type). Carries `CA_FLAG_READ_ONLY`; `[]=` raises, except
5
+ the writable cast / byte_swap op_ids.
6
+
7
+ ## In-place chain evaluation
8
+
9
+ When materialising a chain `a.lazy.f.g.h.to_ca`, the xfer_stride does:
10
+
11
+ 1. iterative chain collect: walk this->parent->...->parent until a
12
+ non-CAMonOp leaf is found, recording op_ids bottom-up
13
+ 2. pull leaf into the output buffer (= `data`) via ca_xfer_stride —
14
+ or, when a cast node is at the innermost position (cast-before),
15
+ pull into a small leaf-data_type scratch and ca_cast_block into output
16
+ 3. apply remaining ops in reverse order, in-place on `data` (=
17
+ ptr1==ptr2==data, i1==i2==1)
18
+
19
+ Chain-intermediate scratch count = 0, depth-independent. At most one
20
+ leaf-scratch (for the cast input) when a widening cast is required;
21
+ this is also depth-independent. monop is element-local, so in-place
22
+ is safe; masked cells may be computed but are unobservable (mask
23
+ propagates on a separate channel).
24
+
25
+ ## Scope
26
+
27
+ - 34 ops via dispatch table (`ext/ca_monop_dispatch.c`)
28
+ - Cast nodes for widening monfunc on an integer parent (cast-before)
29
+ - Per-(op, data_type) byte parity with the eager path
30
+
31
+ ---------------------------------------------------------------------------- */
32
+
33
+ #include "carray.h"
34
+ #include "carray_internal.h" /* ca_lazy_arena_*, ca_is_lazy_view */
35
+ #include "ca_monop_dispatch.h"
36
+
37
+ /* ca_cast_block (carray_cast.c) */
38
+ extern void ca_cast_block (ca_size_t n, void *ap1, void *ptr1,
39
+ void *ap2, void *ptr2);
40
+
41
+ /* byte_swap buffer-level helper (ext/ca_obj_byte_swap.c). Handles CMPLX
42
+ half-independent swap (CMPLX64 = 4-byte halves, CMPLX128 = 8-byte
43
+ halves); other data_types = ca_swap_bytes(buf, bytes, elements).
44
+ CA_FIXLEN+data_class is excluded. */
45
+ extern void ca_byte_swap_buffer (int8_t data_type, ca_size_t bytes,
46
+ ca_size_t elements, char *buf);
47
+
48
+ /* ------------------------------------------------------------------- */
49
+ /* CAMonOp struct */
50
+ /* ------------------------------------------------------------------- */
51
+
52
+ int8_t CA_OBJ_MONOP;
53
+ VALUE rb_cCAMonOp;
54
+
55
+ /* Defined in ext/carray_lazy.c — needed for collapse-on-consume and
56
+ chain-walk leaf detection. */
57
+ extern int8_t CA_OBJ_LAZY_MARKER;
58
+
59
+ /* CABinOp / CABinCmp / CAMonCmp obj_type forward decls (defined in
60
+ ext/ca_obj_*.c). Used by ca_monop_view_is_single_cast to recognise
61
+ lazy parents. (Re-externed at the end of this file; a duplicate decl
62
+ is safe.) */
63
+ extern int8_t CA_OBJ_BINOP;
64
+ extern int8_t CA_OBJ_BINCMP;
65
+ extern int8_t CA_OBJ_MONCMP;
66
+
67
+ typedef struct CAMonOp {
68
+ int16_t obj_type;
69
+ int8_t data_type;
70
+ int8_t ndim;
71
+ int32_t flags;
72
+ ca_size_t bytes;
73
+ ca_size_t elements;
74
+ ca_size_t *dim;
75
+ char *ptr;
76
+ CArray *mask;
77
+ char *_pool; /* framework-managed pool buffer (NULL = legacy ALLOC_N path). */
78
+ CArray *parent;
79
+ uint32_t attach;
80
+ uint8_t nosync;
81
+ /* CAMonOp-specific tail */
82
+ uint16_t op_id;
83
+ /* data_class snapshot for cast op_id paths that need data_class
84
+ metadata propagation (e.g. `as_type(MyStruct)` CA_FIXLEN + data_class
85
+ field-recursive operations). Stays Qnil for the non-cast or
86
+ no-data_class case; mirrors the CAByteSwap pattern. */
87
+ VALUE data_class;
88
+ } CAMonOp;
89
+
90
+ static size_t
91
+ ca_monop_dsize (const void *ap)
92
+ {
93
+ const CAMonOp *ca = (const CAMonOp *) ap;
94
+ return sizeof(CAMonOp) + ca->ndim * sizeof(ca_size_t);
95
+ }
96
+
97
+ /* Pool framework hooks: single ndim-sized tail (dim) in the _pool buffer
98
+ (uniform alloc/free discipline). */
99
+ static size_t
100
+ ca_monop_pool_bytes (int8_t ndim)
101
+ {
102
+ ca_size_t n = (ndim > 0) ? ndim : 1;
103
+ return (size_t) n * sizeof(ca_size_t);
104
+ }
105
+
106
+ static void
107
+ ca_monop_pool_init (void *ap, int8_t ndim)
108
+ {
109
+ CAMonOp *ca = (CAMonOp *) ap;
110
+ ca->dim = (ca_size_t *) ca->_pool;
111
+ }
112
+
113
+ /* Custom dmark: mark the prefix (= parent ivar + mask + standard CArray
114
+ VALUE refs) AND the tail data_class VALUE. Mirrors ca_byte_swap_mark. */
115
+ static void
116
+ ca_monop_mark (void *ap)
117
+ {
118
+ CAMonOp *ca = (CAMonOp *) ap;
119
+ ca_mark(ca); /* prefix */
120
+ rb_gc_mark(ca->data_class); /* tail VALUE */
121
+ }
122
+
123
+ const rb_data_type_t camonop_data_type = {
124
+ .parent = &caview_data_type,
125
+ .wrap_struct_name = "CAMonOp",
126
+ .function = {
127
+ .dmark = ca_monop_mark,
128
+ .dfree = ca_free,
129
+ .dsize = ca_monop_dsize,
130
+ .dcompact = NULL
131
+ },
132
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
133
+ };
134
+
135
+ /* ------------------------------------------------------------------- */
136
+ /* single-cast discrimination helper */
137
+ /* ------------------------------------------------------------------- */
138
+
139
+ /* See ca_monop_dispatch.h for contract. Returns 1 iff `view` is a
140
+ CAMonOp instance whose op_id is a cast op AND whose parent is not also
141
+ a CAMonOp (= single-node cast view, the structural successor of CAFake).
142
+ This is the discrimination predicate used by ca_kernel_iterator's
143
+ per-fiber fused gate to keep the transform-fused fast path live for
144
+ single-cast views while chain CAMonOp (= depth >= 2) continues through
145
+ the chain materialise path. */
146
+ int
147
+ ca_monop_view_is_single_cast (CArray *view)
148
+ {
149
+ CAMonOp *m;
150
+ CArray *p;
151
+ if ( view == NULL || view->obj_type != CA_OBJ_MONOP ) {
152
+ return 0;
153
+ }
154
+ m = (CAMonOp *) view;
155
+ if ( ! ca_monop_is_cast(m->op_id) ) {
156
+ return 0;
157
+ }
158
+ p = m->parent;
159
+ if ( p == NULL ) {
160
+ return 0;
161
+ }
162
+ /* "single-cast" = cast over a NON-lazy parent. When the parent is
163
+ itself a lazy view family member, the cast contributes to a lazy
164
+ chain and should NOT be excluded from ca_is_lazy_view (otherwise
165
+ CArray.fuse chains break through the cast node, and `<=>` 1-pass fuse
166
+ degenerates back to 3-pass). Cast over entity / CAStride / CAFake /
167
+ etc. (= NOT a lazy parent) stays single-cast → eager binop dispatch
168
+ (so `1/zero` zero-check works). */
169
+ return ( p->obj_type != CA_OBJ_MONOP
170
+ && p->obj_type != CA_OBJ_BINOP
171
+ && p->obj_type != CA_OBJ_BINCMP
172
+ && p->obj_type != CA_OBJ_MONCMP
173
+ && p->obj_type != CA_OBJ_LAZY_MARKER );
174
+ }
175
+
176
+ /* ------------------------------------------------------------------- */
177
+ /* setup / new / free */
178
+ /* ------------------------------------------------------------------- */
179
+
180
+ static int
181
+ ca_monop_setup (CAMonOp *ca, CArray *parent, uint16_t op_id)
182
+ {
183
+ int8_t out_dt = ca_lazy_promote_monop(op_id, parent->data_type);
184
+ ca_size_t out_bytes = ca_sizeof[out_dt];
185
+
186
+ ca->obj_type = CA_OBJ_MONOP;
187
+ ca->data_type = out_dt;
188
+ /* A cast op_id encodes a value-converting cast (analogue of CAFake),
189
+ which is writable — `rb_ca_wrap_writable` returns the cast view
190
+ directly and write-back reverse-casts through ca_ptr2ptr. Non-cast
191
+ ops (= monop / monfunc) remain read-only. This matches the CAFake /
192
+ wrap_writable semantics. */
193
+ ca->flags = ca_monop_is_writable_view(op_id) ? 0 : CA_FLAG_READ_ONLY;
194
+ ca->ndim = parent->ndim;
195
+ ca->bytes = out_bytes;
196
+ ca->elements = parent->elements;
197
+ ca->ptr = NULL;
198
+ ca->mask = NULL;
199
+ if ( ! ca->_pool ) {
200
+ ca->dim = ALLOC_N(ca_size_t, parent->ndim);
201
+ }
202
+ ca->parent = parent;
203
+ ca->attach = 0;
204
+ ca->nosync = 0;
205
+ ca->op_id = op_id;
206
+ ca->data_class = Qnil; /* set by the builder when rtype is a class */
207
+
208
+ memcpy(ca->dim, parent->dim, parent->ndim * sizeof(ca_size_t));
209
+
210
+ if ( ca_has_mask(parent) ) {
211
+ ca_create_mask(ca);
212
+ }
213
+
214
+ if ( ca_is_scalar(parent) ) {
215
+ ca_set_flag(ca, CA_FLAG_SCALAR);
216
+ }
217
+
218
+ return 0;
219
+ }
220
+
221
+ CAMonOp *
222
+ ca_monop_new (CArray *parent, uint16_t op_id)
223
+ {
224
+ CAMonOp *ca = (CAMonOp *) ca_array_alloc(CA_OBJ_MONOP, parent->ndim);
225
+ ca_monop_setup(ca, parent, op_id);
226
+ return ca;
227
+ }
228
+
229
+ static void
230
+ free_ca_monop (void *ap)
231
+ {
232
+ CAMonOp *ca = (CAMonOp *) ap;
233
+ if ( ca != NULL ) {
234
+ ca_free(ca->mask);
235
+ if ( ca->_pool ) {
236
+ ca_array_free(ca); /* dim lives in _pool */
237
+ }
238
+ else {
239
+ xfree(ca->dim);
240
+ xfree(ca);
241
+ }
242
+ }
243
+ }
244
+
245
+ /* ------------------------------------------------------------------- */
246
+ /* operation function table */
247
+ /* ------------------------------------------------------------------- */
248
+
249
+ /* Forward declarations (cross-references between slot functions). */
250
+ static void ca_monop_func_xfer_stride (void *ap, ca_size_t *starts,
251
+ ca_size_t *counts, ca_size_t *strides,
252
+ void *data, int dir);
253
+
254
+ static void *
255
+ ca_monop_func_clone (void *ap)
256
+ {
257
+ CAMonOp *ca = (CAMonOp *) ap;
258
+ return ca_monop_new(ca->parent, ca->op_id);
259
+ }
260
+
261
+ /* per-cell access: walk the chain materialising 1 cell via per-cell
262
+ xfer_index that triggers materialise through the parent chain. This is
263
+ not catastrophic at typical depths, but hot loops should use a `.to_ca`
264
+ snapshot.
265
+
266
+ PUT path: when this view is a writable cast view (= cast op_id),
267
+ reverse-cast the incoming `data` (view data_type) to a per-cell scratch
268
+ in parent data_type, then ca_store_index into parent. Non-cast monop
269
+ views remain read-only (rb_raise). */
270
+ static void
271
+ ca_monop_func_xfer_index (void *ap, ca_size_t *idx, void *data, int dir)
272
+ {
273
+ CAMonOp *ca = (CAMonOp *) ap;
274
+ ca_size_t starts[CA_RANK_MAX];
275
+ ca_size_t counts[CA_RANK_MAX];
276
+ ca_size_t strides[CA_RANK_MAX];
277
+ int8_t k;
278
+ ca_size_t s;
279
+
280
+ if ( dir != CA_XFER_GET ) {
281
+ /* PUT: only writable-view op_ids accept writes (cast, or byte_swap
282
+ involution). */
283
+ if ( ! ca_monop_is_writable_view(ca->op_id) ) {
284
+ rb_raise(rb_eRuntimeError, "CAMonOp is read-only (xfer_index PUT)");
285
+ }
286
+ {
287
+ char scratch[32]; /* max sizeof for any built-in numeric data_type */
288
+ ca_size_t bytes_per_cell = (ca_monop_is_cast(ca->op_id))
289
+ ? ca->parent->bytes : ca->bytes;
290
+ char *p = (bytes_per_cell <= 32) ? scratch
291
+ : (char *) ALLOC_N(char, bytes_per_cell);
292
+ if ( ca_monop_is_cast(ca->op_id) ) {
293
+ /* Reverse-cast view-data_type `data` to parent data_type scratch, then
294
+ store into parent at idx[]. */
295
+ ca_ptr2ptr((CArray *) ca, data, ca->parent, p);
296
+ }
297
+ else {
298
+ /* byte_swap (involution): copy + byte_swap into parent-bytes
299
+ layout (= same bytes since data_type-preserving). */
300
+ memcpy(p, data, ca->bytes);
301
+ ca_byte_swap_buffer(ca->data_type, ca->bytes, 1, p);
302
+ }
303
+ ca_store_index(ca->parent, idx, p);
304
+ if ( p != scratch ) xfree(p);
305
+ }
306
+ return;
307
+ }
308
+
309
+ /* Single-cell region: counts[k] = 1 for all axes, starts = idx[]. */
310
+ s = ca->bytes;
311
+ for ( k = ca->ndim - 1; k >= 0; k-- ) {
312
+ starts[k] = idx[k];
313
+ counts[k] = 1;
314
+ strides[k] = s;
315
+ }
316
+ ca_monop_func_xfer_stride(ca, starts, counts, strides, data, CA_XFER_GET);
317
+ }
318
+
319
+ static void
320
+ ca_monop_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
321
+ void *data, int dir)
322
+ {
323
+ CAMonOp *ca = (CAMonOp *) ap;
324
+ ca_size_t i;
325
+ char *out = (char *) data;
326
+
327
+ if ( dir != CA_XFER_GET ) {
328
+ /* PUT: only writable-view op_ids accept writes. */
329
+ if ( ! ca_monop_is_writable_view(ca->op_id) ) {
330
+ rb_raise(rb_eRuntimeError, "CAMonOp is read-only (xfer_addrs PUT)");
331
+ }
332
+ /* Per-cell loop: forward to xfer_index PUT (handles cast/byte_swap
333
+ branch internally). */
334
+ for ( i = 0; i < n; i++ ) {
335
+ ca_size_t idx[CA_RANK_MAX];
336
+ ca_addr2index((CArray *)ca, addrs[i], idx);
337
+ ca_monop_func_xfer_index(ca, idx, out + i * ca->bytes, CA_XFER_PUT);
338
+ }
339
+ return;
340
+ }
341
+
342
+ /* Naive per-addr loop: convert addr → idx, then xfer_index. This path
343
+ is not optimised. */
344
+ for ( i = 0; i < n; i++ ) {
345
+ ca_size_t idx[CA_RANK_MAX];
346
+ ca_addr2index((CArray *)ca, addrs[i], idx);
347
+ ca_monop_func_xfer_index(ca, idx, out + i * ca->bytes, CA_XFER_GET);
348
+ }
349
+ }
350
+
351
+ /* ------------------------------------------------------------------- */
352
+ /* In-place chain eval (scratch = 0) */
353
+ /* ------------------------------------------------------------------- */
354
+
355
+ /* Test counter: incremented every time xfer_stride allocates a leaf-side
356
+ scratch buffer (for the cast-before pull). Stays 0 across any depth
357
+ chain when no cast is needed, and 1 (cast scratch) regardless of chain
358
+ depth when the leaf needs widening. Reset and read from Ruby for
359
+ tests. */
360
+ ca_size_t ca_monop_scratch_acquire_count = 0;
361
+
362
+ /* Test counter: incremented every time CAMonOp's xfer_stride entry is
363
+ called. Used to pin that `inspect` / `dump_tree` do NOT materialise
364
+ (counter unchanged) while `each` / `to_a` / `sort` / `to_ca` etc. DO
365
+ (counter increments). */
366
+ ca_size_t ca_monop_materialise_call_count = 0;
367
+
368
+ #ifndef CA_MAX_LAZY_DEPTH
369
+ #define CA_MAX_LAZY_DEPTH 256
370
+ #endif
371
+
372
+ /* Apply a single op in-place on `data` (src == dst, unit element stride).
373
+ Raises if the kernel is not implemented for the given input data_type.
374
+
375
+ Unmask fast path:
376
+ We always pass `mask == NULL` to the kernel, which selects the SIMD-
377
+ friendly "no mask" branch in the existing eager kernels (see e.g.
378
+ ca_monop_sqrt_float64_t in carray_math.c — `if (m) { ... } else
379
+ { ...SIMD loop... }`). Mask cells contain garbage after evaluation,
380
+ but they are unobservable: the output mask is built separately via
381
+ the attach lifecycle (ca_monop_func_create_mask + parent.mask
382
+ CARefer), so reads of masked cells return UNDEF regardless of byte
383
+ contents. This is the design license stated in CLAUDE.md
384
+ §"design premises" ("mask is not a protection mechanism").
385
+
386
+ A "partial mask slow path" (= a cell-wise branch to skip masked-cell
387
+ compute) is a possible future micro-optimisation; it is not done
388
+ because masked-cell compute does not affect correctness and the SIMD
389
+ path is faster overall for typical mask densities. */
390
+ static void
391
+ apply_monop_in_place (uint16_t op_id, int8_t cur_dt,
392
+ ca_size_t slab_n, void *data)
393
+ {
394
+ ca_monop_func_t fn = ca_monop_kernel_lookup(op_id, cur_dt);
395
+ if ( fn == NULL ) {
396
+ rb_raise(rb_eRuntimeError,
397
+ "CAMonOp: kernel not implemented (op_id=%u data_type=%d)",
398
+ (unsigned) op_id, (int) cur_dt);
399
+ }
400
+ fn(slab_n, NULL, (char *) data, 1, (char *) data, 1);
401
+ }
402
+
403
+ /* Minimal CArray-shaped stub used as ca_cast_block's data_type-only carrier.
404
+ ca_cast_block only reads data_type to index into ca_cast_func_table. */
405
+ typedef struct {
406
+ int16_t obj_type;
407
+ int8_t data_type;
408
+ } ca_data_type_stub_t;
409
+
410
+ /* Pull leaf into the output buffer. Three paths:
411
+
412
+ 1. No size change (leaf.bytes == out.bytes): pull directly into
413
+ `data` using the caller's `strides` (= output byte layout).
414
+ Zero scratch; cur_dt = leaf.data_type.
415
+
416
+ 2. Size mismatch + cast at leaf (`has_cast_at_leaf == 1`): allocate
417
+ a small scratch sized in leaf bytes, pull leaf into scratch with
418
+ row-major leaf-byte strides, then ca_cast_block scratch → data
419
+ (in the cast target's data_type). Scratch count = 1. cur_dt =
420
+ cast target data_type.
421
+
422
+ 3. Size mismatch + cast mid-chain (`has_cast_at_leaf == 0`):
423
+ allocate scratch sized in leaf bytes, pull leaf into scratch
424
+ with leaf-byte strides, then memcpy scratch → start of `data`
425
+ (compact leaf-byte layout at the head of the larger buffer).
426
+ A later mid-chain cast op will expand it into the output layout.
427
+ Scratch count = 1. cur_dt = leaf.data_type.
428
+
429
+ The shared property: when output.bytes != leaf.bytes, the caller's
430
+ `strides` (= output byte layout) cannot be passed directly to
431
+ ca_xfer_stride on the leaf because they don't match leaf's per-cell
432
+ size. Pulling into a leaf-sized compact scratch decouples leaf-
433
+ layout from output-layout. */
434
+ static int8_t
435
+ pull_leaf_with_optional_cast (CArray *leaf, uint16_t innermost_op,
436
+ int has_cast_at_leaf, int8_t out_bytes,
437
+ ca_size_t slab_n,
438
+ ca_size_t *starts, ca_size_t *counts,
439
+ ca_size_t *strides, void *data)
440
+ {
441
+ ca_size_t leaf_bytes = leaf->bytes;
442
+
443
+ /* Path 1: no size mismatch, no cast — direct pull. */
444
+ if ( leaf_bytes == out_bytes && ! has_cast_at_leaf ) {
445
+ ca_xfer_stride(leaf, starts, counts, strides, data, CA_XFER_GET);
446
+ return leaf->data_type;
447
+ }
448
+
449
+ /* Paths 2 & 3 both need a leaf-sized scratch + leaf-byte strides
450
+ for the leaf pull. */
451
+ ca_size_t parent_strides[CA_RANK_MAX];
452
+ ca_size_t total_bytes = slab_n * leaf_bytes;
453
+ void *scratch;
454
+ volatile VALUE holder;
455
+ int8_t k;
456
+ {
457
+ ca_size_t s = leaf_bytes;
458
+ for ( k = leaf->ndim - 1; k >= 0; k-- ) {
459
+ parent_strides[k] = s;
460
+ s *= counts[k];
461
+ }
462
+ }
463
+ (void) holder;
464
+ scratch = ca_lazy_arena_acquire(total_bytes);
465
+ ca_monop_scratch_acquire_count++;
466
+ ca_xfer_stride(leaf, starts, counts, parent_strides, scratch, CA_XFER_GET);
467
+
468
+ int8_t result_dt;
469
+ if ( has_cast_at_leaf ) {
470
+ /* Path 2: cast scratch → data with output layout in target data_type. */
471
+ int8_t target_dt = (int8_t)(innermost_op - CA_MONOP_CAST_BASE);
472
+ ca_data_type_stub_t src_stub = { 0, leaf->data_type };
473
+ ca_data_type_stub_t dst_stub = { 0, target_dt };
474
+ ca_cast_block(slab_n, &src_stub, scratch, &dst_stub, data);
475
+ result_dt = target_dt;
476
+ } else {
477
+ /* Path 3: memcpy scratch → start of data. Mid-chain cast will
478
+ expand to output layout later. */
479
+ memcpy(data, scratch, total_bytes);
480
+ result_dt = leaf->data_type;
481
+ }
482
+
483
+ ca_lazy_arena_release(scratch);
484
+ return result_dt;
485
+ }
486
+
487
+ static void
488
+ ca_monop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
489
+ ca_size_t *strides, void *data, int dir)
490
+ {
491
+ CAMonOp *mo = (CAMonOp *) ap;
492
+ uint16_t op_chain[CA_MAX_LAZY_DEPTH];
493
+ int chain_len = 0;
494
+ CArray *leaf;
495
+ ca_size_t slab_n;
496
+ int8_t k;
497
+ int8_t cur_dt;
498
+ int i;
499
+ int has_cast_at_leaf = 0;
500
+ int chain_apply_count;
501
+
502
+ /* Writable-view op_ids (cast / byte_swap) use CAFake-style xfer_stride
503
+ for both GET and PUT. This avoids the chain-materialise inline path
504
+ that fails for a narrowing cast (parent.bytes > ca.bytes → buffer
505
+ overflow in the pull_leaf path 3). Mirrors the CAFake xfer_stride
506
+ fallback.
507
+ - cast: pstrides = strides/ca.bytes * parent.bytes; ca_cast_block.
508
+ - byte_swap: same data_type → pstrides = strides; involution apply. */
509
+ if ( ca_monop_is_writable_view(mo->op_id) ) {
510
+ int is_cast = ca_monop_is_cast(mo->op_id);
511
+ ca_size_t pstrides[CA_RANK_MAX];
512
+ ca_size_t n = 1;
513
+ ca_size_t parent_bytes = is_cast ? mo->parent->bytes : mo->bytes;
514
+ int8_t kk;
515
+ char *v;
516
+ volatile VALUE holder;
517
+ for ( kk = 0; kk < mo->ndim; kk++ ) {
518
+ n *= counts[kk];
519
+ pstrides[kk] = is_cast
520
+ ? (strides[kk] / mo->bytes * mo->parent->bytes)
521
+ : strides[kk];
522
+ }
523
+ v = ALLOCV_N(char, holder, n * parent_bytes);
524
+ if ( dir == CA_XFER_GET ) {
525
+ ca_xfer_stride(mo->parent, starts, counts, pstrides, v, CA_XFER_GET);
526
+ if ( is_cast ) {
527
+ ca_cast_block(n, mo->parent, v, (CArray *) mo, data);
528
+ }
529
+ else {
530
+ /* byte_swap (involution): apply on scratch then memcpy → data. */
531
+ ca_byte_swap_buffer(mo->data_type, mo->bytes, n, v);
532
+ memcpy(data, v, n * mo->bytes);
533
+ }
534
+ }
535
+ else {
536
+ if ( is_cast ) {
537
+ ca_cast_block(n, (CArray *) mo, data, mo->parent, v);
538
+ }
539
+ else {
540
+ /* byte_swap PUT: copy data into scratch + byte_swap (involution). */
541
+ memcpy(v, data, n * mo->bytes);
542
+ ca_byte_swap_buffer(mo->data_type, mo->bytes, n, v);
543
+ }
544
+ ca_xfer_stride(mo->parent, starts, counts, pstrides, v, CA_XFER_PUT);
545
+ }
546
+ ALLOCV_END(holder);
547
+ return;
548
+ }
549
+
550
+ /* Writable-view op_id PUT/GET is handled above. Below: non-writable
551
+ monop/monfunc. PUT is read-only. */
552
+ if ( dir != CA_XFER_GET ) {
553
+ rb_raise(rb_eRuntimeError, "CAMonOp is read-only (xfer_stride PUT)");
554
+ }
555
+ ca_monop_materialise_call_count++;
556
+
557
+ /* === 1. iterative chain collect ===
558
+ Walk down through CAMonOp parents, recording op_ids bottom-up.
559
+ Skip transparent CALazyMarker (collapse-on-consume already happened
560
+ at construction, but defensive in case a marker is mid-chain). */
561
+ leaf = (CArray *) mo;
562
+ while ( leaf->obj_type == CA_OBJ_MONOP ) {
563
+ CAMonOp *node = (CAMonOp *) leaf;
564
+ if ( chain_len >= CA_MAX_LAZY_DEPTH ) {
565
+ rb_raise(rb_eRuntimeError,
566
+ "CAMonOp chain depth exceeds CA_MAX_LAZY_DEPTH (%d)",
567
+ CA_MAX_LAZY_DEPTH);
568
+ }
569
+ op_chain[chain_len++] = node->op_id;
570
+ leaf = node->parent;
571
+ }
572
+ /* Skip a CALazyMarker leaf: transparent pass-through to its parent. */
573
+ while ( leaf->obj_type == CA_OBJ_LAZY_MARKER ) {
574
+ leaf = ((CAView *) leaf)->parent;
575
+ }
576
+
577
+ /* Detect whether the innermost recorded op is a cast. Cast nodes are
578
+ always inserted adjacent to the leaf at construction (cast-before),
579
+ so they appear at op_chain[chain_len - 1]. */
580
+ chain_apply_count = chain_len;
581
+ if ( chain_len > 0 && ca_monop_is_cast(op_chain[chain_len - 1]) ) {
582
+ has_cast_at_leaf = 1;
583
+ chain_apply_count = chain_len - 1;
584
+ }
585
+
586
+ /* === 2. pull leaf into output buffer (with optional cast) === */
587
+ slab_n = 1;
588
+ for ( k = 0; k < mo->ndim; k++ ) {
589
+ slab_n *= counts[k];
590
+ }
591
+ cur_dt = pull_leaf_with_optional_cast(
592
+ leaf,
593
+ has_cast_at_leaf ? op_chain[chain_len - 1] : 0,
594
+ has_cast_at_leaf,
595
+ (int8_t) mo->bytes,
596
+ slab_n, starts, counts, strides, data
597
+ );
598
+
599
+ /* === 3. apply remaining chain in reverse (leaf side → outermost) ===
600
+ Cast nodes may appear mid-chain when preserve ops precede a
601
+ widening monfunc (e.g. `int16.lazy.neg.sinh` inserts cast_f64
602
+ between neg and sinh). Handle them via ca_cast_block + scratch
603
+ similar to pull_leaf_with_optional_cast. */
604
+ for ( i = chain_apply_count - 1; i >= 0; i-- ) {
605
+ uint16_t op_id = op_chain[i];
606
+ if ( ca_monop_is_cast(op_id) ) {
607
+ int8_t target_dt = (int8_t)(op_id - CA_MONOP_CAST_BASE);
608
+ ca_size_t src_bytes = ca_sizeof[cur_dt];
609
+ ca_size_t total_bytes = slab_n * src_bytes;
610
+ void *scratch;
611
+ ca_data_type_stub_t src_stub = { 0, cur_dt };
612
+ ca_data_type_stub_t dst_stub = { 0, target_dt };
613
+
614
+ /* Copy current data → scratch (in src data_type), then cast scratch
615
+ → data (in target data_type). Required because in-place cast
616
+ would overlap source/dest reads when target_bytes > src_bytes
617
+ (forward-walk reads cells past their own write boundary). */
618
+ scratch = ca_lazy_arena_acquire(total_bytes);
619
+ ca_monop_scratch_acquire_count++;
620
+ memcpy(scratch, data, total_bytes);
621
+ ca_cast_block(slab_n, &src_stub, scratch, &dst_stub, data);
622
+ ca_lazy_arena_release(scratch);
623
+ cur_dt = target_dt;
624
+ } else {
625
+ apply_monop_in_place(op_id, cur_dt, slab_n, data);
626
+ cur_dt = ca_lazy_promote_monop(op_id, cur_dt);
627
+ }
628
+ }
629
+ }
630
+
631
+ static void
632
+ ca_monop_func_xfer_all (void *ap, void *data, int dir)
633
+ {
634
+ CAMonOp *ca = (CAMonOp *) ap;
635
+ ca_size_t starts[CA_RANK_MAX];
636
+ ca_size_t native[CA_RANK_MAX];
637
+ int8_t k;
638
+ ca_size_t s = ca->bytes;
639
+ for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
640
+ for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
641
+ ca_xfer_stride(ca, starts, ca->dim, native, data, dir);
642
+ }
643
+
644
+ static void
645
+ ca_monop_func_allocate (void *ap)
646
+ {
647
+ CAMonOp *ca = (CAMonOp *) ap;
648
+ /* A writable cast view's allocate path also needs the parent attached
649
+ so a later sync (sync slot → ca_cast_block(ca.ptr → parent.ptr))
650
+ finds parent.ptr valid. Caller pattern is `ca_allocate(view); fill
651
+ view.ptr; ca_sync(view); ca_detach(view)` (a write-only buffer
652
+ allocation that bypasses the parent data copy).
653
+
654
+ CAREFUL but safe: ca_attach / ca_allocate slots fire only on the
655
+ count 0→1 transition, so if both allocate and attach run on the same
656
+ view only the first triggers the slot; the second just increments the
657
+ count. Total ca_attach(parent) calls per view lifecycle = 1. CAFake
658
+ uses this same pattern. */
659
+ if ( ca_monop_is_writable_view(ca->op_id) ) {
660
+ ca_attach(ca->parent);
661
+ }
662
+ ca->ptr = xmalloc(ca_length(ca));
663
+ /* CA_OBJECT initialisation for GC safety. */
664
+ if ( ca->data_type == CA_OBJECT ) {
665
+ VALUE *p = (VALUE *) ca->ptr;
666
+ VALUE zero = SIZE2NUM(0);
667
+ ca_size_t i;
668
+ for ( i = 0; i < ca->elements; i++ ) {
669
+ *p++ = zero;
670
+ }
671
+ }
672
+ }
673
+
674
+ static void
675
+ ca_monop_func_attach (void *ap)
676
+ {
677
+ /* CAREFUL: call the view-specific xfer_stride directly, bypassing the
678
+ public ca_xfer_stride dispatcher. A self-fill via the dispatcher
679
+ would hit the self-memcpy fast path (data == ca->ptr → no-op) and
680
+ leave the buffer as garbage. */
681
+ CAMonOp *ca = (CAMonOp *) ap;
682
+ ca_size_t starts[CA_RANK_MAX];
683
+ ca_size_t native[CA_RANK_MAX];
684
+ int8_t k;
685
+ ca_size_t s;
686
+
687
+ /* A writable cast view uses the CAFake-style attach lifecycle
688
+ (= ca_attach(parent); ca_cast_block(parent.ptr → ca.ptr)). This
689
+ avoids the chain-materialise inline path, which assumes the output
690
+ buffer is at least as big as the leaf data and so fails for a
691
+ narrowing cast like float64→int32.
692
+
693
+ Non-cast (= chain materialise / read-only) uses the in-place chain
694
+ eval path. Its output buffer is in monop data_type; widening at the
695
+ leaf is supported via path 3 (mid-chain cast scratch expansion).
696
+
697
+ Mirrors CAFake.attach. */
698
+ if ( ca_monop_is_writable_view(ca->op_id) ) {
699
+ ca_attach(ca->parent);
700
+ ca->ptr = xmalloc(ca_length(ca));
701
+ /* CA_OBJECT initialisation for GC safety (mirrors CAFake.allocate):
702
+ zero-init the VALUE cells before any potential GC can observe
703
+ them. */
704
+ if ( ca->data_type == CA_OBJECT ) {
705
+ VALUE *p = (VALUE *) ca->ptr;
706
+ VALUE zero = SIZE2NUM(0);
707
+ ca_size_t i;
708
+ for ( i = 0; i < ca->elements; i++ ) {
709
+ *p++ = zero;
710
+ }
711
+ }
712
+ if ( ca_monop_is_cast(ca->op_id) ) {
713
+ /* Cast parent.ptr (parent data_type) → ca.ptr (cast target data_type). */
714
+ ca_cast_block(ca->elements, ca->parent, ca->parent->ptr,
715
+ (CArray *) ca, ca->ptr);
716
+ }
717
+ else {
718
+ /* byte_swap: memcpy parent.ptr → ca.ptr (same data_type same bytes),
719
+ then byte_swap in place. */
720
+ memcpy(ca->ptr, ca->parent->ptr, ca_length(ca));
721
+ ca_byte_swap_buffer(ca->data_type, ca->bytes, ca->elements, ca->ptr);
722
+ }
723
+ return;
724
+ }
725
+
726
+ ca->ptr = xmalloc(ca_length(ca));
727
+
728
+ s = ca->bytes;
729
+ for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
730
+ for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
731
+ ca_monop_func_xfer_stride(ca, starts, ca->dim, native, ca->ptr, CA_XFER_GET);
732
+ }
733
+
734
+ static void
735
+ ca_monop_func_sync (void *ap)
736
+ {
737
+ CAMonOp *ca = (CAMonOp *) ap;
738
+ /* Writable cast lifecycle. A CABlock-on-CAMonOp(cast) sub-view fill
739
+ propagates via this path:
740
+ 1. CABlock.attach pulls CAMonOp via xfer_stride GET into CABlock.ptr
741
+ 2. user fills CABlock.ptr region
742
+ 3. CABlock.sync writes CABlock.ptr back via xfer_stride PUT to CAMonOp
743
+ (fills the CAMonOp.ptr region in cast data_type)
744
+ 4. CAMonOp.sync (= this) writes CAMonOp.ptr back to parent via a
745
+ reverse-cast bulk (ca_cast_block) + parent.sync
746
+ Non-cast (= chain materialise / read-only) skips this: no writes ever
747
+ accumulate in ca->ptr, so sync would be redundant. Mirrors the
748
+ CAFake sync pattern. */
749
+ if ( ! ca_monop_is_writable_view(ca->op_id) ) {
750
+ return;
751
+ }
752
+ if ( ca_monop_is_cast(ca->op_id) ) {
753
+ /* Cast view → parent: reverse-cast bulk. */
754
+ ca_update_mask(ca);
755
+ if ( ca->mask ) {
756
+ ca_cast_block_with_mask(ca->elements, ca, ca->ptr,
757
+ ca->parent, ca->parent->ptr,
758
+ (boolean8_t *) ca->parent->mask->ptr);
759
+ }
760
+ else {
761
+ ca_cast_block(ca->elements, ca, ca->ptr, ca->parent, ca->parent->ptr);
762
+ }
763
+ }
764
+ else {
765
+ /* byte_swap (involution): apply on ca.ptr in place (re-swap), then
766
+ memcpy ca.ptr → parent.ptr. Mirrors CAByteSwap sync. */
767
+ ca_byte_swap_buffer(ca->data_type, ca->bytes, ca->elements, ca->ptr);
768
+ memcpy(ca->parent->ptr, ca->ptr, ca_length(ca));
769
+ }
770
+ ca_sync(ca->parent);
771
+ }
772
+
773
+ static void
774
+ ca_monop_func_detach (void *ap)
775
+ {
776
+ CAMonOp *ca = (CAMonOp *) ap;
777
+ xfree(ca->ptr);
778
+ ca->ptr = NULL;
779
+ /* A cast op_id attaches the parent in ca_monop_func_attach; pair it
780
+ here with one ca_detach(parent), so the attach/detach on the parent
781
+ balance across the view lifecycle. Mirrors CAFake detach. */
782
+ if ( ca_monop_is_writable_view(ca->op_id) ) {
783
+ ca_detach(ca->parent);
784
+ }
785
+ }
786
+
787
+ static void
788
+ ca_monop_func_fill_data (void *ap, void *ptr)
789
+ {
790
+ CAMonOp *ca = (CAMonOp *) ap;
791
+ /* Writable-view op_id (cast / byte_swap) fill path — operate on the
792
+ incoming scalar in view data_type, write the parent in parent
793
+ data_type (= same bytes for byte_swap, reverse-cast for cast).
794
+ Non-writable monop/monfunc remains read-only. */
795
+ if ( ! ca_monop_is_writable_view(ca->op_id) ) {
796
+ rb_raise(rb_eRuntimeError, "CAMonOp is read-only (fill_data)");
797
+ }
798
+ {
799
+ int is_cast = ca_monop_is_cast(ca->op_id);
800
+ ca_size_t parent_bytes = is_cast ? ca->parent->bytes : ca->bytes;
801
+ char stack_v[32];
802
+ char *v = (parent_bytes <= 32) ? stack_v
803
+ : (char *) xmalloc(parent_bytes);
804
+ if ( is_cast ) {
805
+ ca_ptr2ptr((CArray *) ca, ptr, ca->parent, v);
806
+ }
807
+ else {
808
+ memcpy(v, ptr, ca->bytes);
809
+ ca_byte_swap_buffer(ca->data_type, ca->bytes, 1, v);
810
+ }
811
+ ca_fill(ca->parent, v);
812
+ if ( v != stack_v ) xfree(v);
813
+ }
814
+ }
815
+
816
+ static void
817
+ ca_monop_func_create_mask (void *ap)
818
+ {
819
+ CAMonOp *ca = (CAMonOp *) ap;
820
+ if ( ! ca->parent->mask ) {
821
+ ca_create_mask(ca->parent);
822
+ }
823
+ ca->mask =
824
+ (CArray *) ca_refer_new(ca->parent->mask,
825
+ CA_BOOLEAN, ca->ndim, ca->dim, 0, 0);
826
+ }
827
+
828
+ ca_operation_function_t ca_monop_func = {
829
+ -1, /* CA_OBJ_MONOP, set at install time */
830
+ CA_VIEW_ARRAY,
831
+ free_ca_monop,
832
+ ca_monop_func_clone,
833
+ ca_monop_func_allocate,
834
+ ca_monop_func_attach,
835
+ ca_monop_func_sync,
836
+ ca_monop_func_detach,
837
+ ca_monop_func_fill_data,
838
+ ca_monop_func_create_mask,
839
+ ca_monop_func_xfer_index,
840
+ ca_monop_func_xfer_addrs,
841
+ NULL, /* fold_stride: never-fold (op boundary) */
842
+ ca_monop_func_xfer_stride,
843
+ ca_monop_func_xfer_all,
844
+ };
845
+
846
+ /* ------------------------------------------------------------------- */
847
+ /* Public predicate + constructor */
848
+ /* ------------------------------------------------------------------- */
849
+
850
+ /* CABinOp obj_type. Declared in ca_obj_binop.c. */
851
+ extern int8_t CA_OBJ_BINOP;
852
+ /* CATriOp obj_type. Declared in ca_obj_triop.c. */
853
+ extern int8_t CA_OBJ_TRIOP;
854
+ /* CABinCmp / CAMonCmp. Declared in ca_obj_bincmp.c / ca_obj_moncmp.c. */
855
+ extern int8_t CA_OBJ_BINCMP;
856
+ extern int8_t CA_OBJ_MONCMP;
857
+
858
+ int
859
+ ca_is_lazy_view (void *ap)
860
+ {
861
+ CArray *ca = (CArray *) ap;
862
+ /* A single-cast CAMonOp (= structural successor of the CAFake numeric
863
+ view, see ca_monop_view_is_single_cast) is a value-converting
864
+ type-adapt view, NOT a lazy chain. Excluding it from is_lazy_view
865
+ preserves eager binop semantics (e.g. `a.as_int32 / b` calls the
866
+ eager div kernel with zero-check, not a CABinOp lazy build), matching
867
+ CAFake behaviour. Chain CAMonOp (depth >= 2) and non-cast
868
+ monop/monfunc remain lazy. */
869
+ if ( ca->obj_type == CA_OBJ_MONOP ) {
870
+ if ( ca_monop_view_is_single_cast(ca) ) {
871
+ return 0;
872
+ }
873
+ return 1;
874
+ }
875
+ return ( ca->obj_type == CA_OBJ_LAZY_MARKER ||
876
+ ca->obj_type == CA_OBJ_BINOP ||
877
+ ca->obj_type == CA_OBJ_TRIOP ||
878
+ ca->obj_type == CA_OBJ_BINCMP ||
879
+ ca->obj_type == CA_OBJ_MONCMP );
880
+ }
881
+
882
+ /* Low-level CAMonOp constructor: wraps `cary` directly, no cast insertion.
883
+ Used internally by the public builder below for the cast node and the
884
+ final op node. */
885
+ static VALUE
886
+ rb_ca_monop_new (VALUE cary, uint16_t op_id)
887
+ {
888
+ volatile VALUE obj;
889
+ CArray *parent;
890
+ CAMonOp *ca;
891
+ rb_check_carray_object(cary);
892
+ TypedData_Get_Struct(cary, CArray, &carray_data_type, parent);
893
+ ca = ca_monop_new(parent, op_id);
894
+ obj = ca_wrap_struct(ca);
895
+ rb_ca_set_parent(obj, cary);
896
+ return obj;
897
+ }
898
+
899
+ /* Public builder: build a CAMonOp tree node for `op_id` over `cary`,
900
+ inserting a cast node if the existing eager kernel for op_id needs
901
+ a different input data_type than cary provides (cast-before).
902
+
903
+ If `cary` is a CALazyMarker, collapse-on-consume: use marker's parent
904
+ as the actual node parent so the marker doesn't appear in the tree. */
905
+ VALUE
906
+ rb_ca_monop_build (VALUE cary, uint16_t op_id)
907
+ {
908
+ CArray *parent;
909
+ VALUE target = cary;
910
+ int8_t parent_dt;
911
+ int8_t kernel_in_dt;
912
+
913
+ rb_check_carray_object(cary);
914
+ TypedData_Get_Struct(cary, CArray, &carray_data_type, parent);
915
+
916
+ /* Collapse-on-consume: if cary is a marker, replace it with its Ruby
917
+ parent for both the CAMonOp parent and the data_type probe. */
918
+ if ( parent->obj_type == CA_OBJ_LAZY_MARKER ) {
919
+ target = rb_ca_parent(cary);
920
+ TypedData_Get_Struct(target, CArray, &carray_data_type, parent);
921
+ }
922
+
923
+ parent_dt = parent->data_type;
924
+ kernel_in_dt = ca_monop_kernel_input_data_type(op_id, parent_dt);
925
+
926
+ /* Insert cast node if the kernel wants a different input data_type. */
927
+ if ( kernel_in_dt != parent_dt ) {
928
+ uint16_t cast_op_id = CA_MONOP_CAST_BASE + (uint16_t) kernel_in_dt;
929
+ target = rb_ca_monop_new(target, cast_op_id);
930
+ }
931
+
932
+ return rb_ca_monop_new(target, op_id);
933
+ }
934
+
935
+ /* Ruby surface: returns the configured CAMonOp. lib/carray/lazy.rb
936
+ invokes this from the per-op method redefinitions (`.lazy.<op>`). */
937
+ static VALUE
938
+ rb_ca_monop_s_build (VALUE klass, VALUE cary, VALUE op_id_val)
939
+ {
940
+ uint16_t op_id = (uint16_t) NUM2UINT(op_id_val);
941
+ (void) klass;
942
+ return rb_ca_monop_build(cary, op_id);
943
+ }
944
+
945
+ /* Ruby predicate: true if self is a lazy view (CAMonOp or CALazyMarker). */
946
+ static VALUE
947
+ rb_ca_is_lazy_view_p (VALUE self)
948
+ {
949
+ CArray *ca;
950
+ TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
951
+ return ca_is_lazy_view(ca) ? Qtrue : Qfalse;
952
+ }
953
+
954
+ /* Read CAMonOp's op_id for inspect/dump_tree consumers in Ruby. */
955
+ static VALUE
956
+ rb_ca_monop_op_id (VALUE self)
957
+ {
958
+ CAMonOp *mo;
959
+ TypedData_Get_Struct(self, CAMonOp, &camonop_data_type, mo);
960
+ return UINT2NUM(mo->op_id);
961
+ }
962
+
963
+ /* ------------------------------------------------------------------- */
964
+ /* Ruby surface helpers */
965
+ /* ------------------------------------------------------------------- */
966
+
967
+ /* Reset and read the scratch-acquire counter used by tests. It stays 0
968
+ because in-place chain eval allocates no scratch; only attach (xfer_all
969
+ → output) and the parent's own xfer_stride alloc, neither of which we
970
+ count here. */
971
+ static VALUE
972
+ rb_ca_monop_s_reset_scratch_counter (VALUE klass)
973
+ {
974
+ ca_monop_scratch_acquire_count = 0;
975
+ return Qnil;
976
+ }
977
+
978
+ static VALUE
979
+ rb_ca_monop_s_scratch_count (VALUE klass)
980
+ {
981
+ return SIZE2NUM(ca_monop_scratch_acquire_count);
982
+ }
983
+
984
+ static VALUE
985
+ rb_ca_monop_s_reset_materialise_counter (VALUE klass)
986
+ {
987
+ ca_monop_materialise_call_count = 0;
988
+ return Qnil;
989
+ }
990
+
991
+ static VALUE
992
+ rb_ca_monop_s_materialise_count (VALUE klass)
993
+ {
994
+ return SIZE2NUM(ca_monop_materialise_call_count);
995
+ }
996
+
997
+ static VALUE
998
+ rb_ca_monop_s_allocate (VALUE klass)
999
+ {
1000
+ CAMonOp *ca;
1001
+ return TypedData_Make_Struct(klass, CAMonOp, &camonop_data_type, ca);
1002
+ }
1003
+
1004
+ static VALUE
1005
+ rb_ca_monop_initialize_copy (VALUE self, VALUE other)
1006
+ {
1007
+ CAMonOp *ca, *cs;
1008
+ TypedData_Get_Struct(self, CAMonOp, &camonop_data_type, ca);
1009
+ TypedData_Get_Struct(other, CAMonOp, &camonop_data_type, cs);
1010
+ if ( ca_func[CA_OBJ_MONOP].pool_init ) {
1011
+ ca_array_pool_alloc(ca, CA_OBJ_MONOP, cs->parent->ndim);
1012
+ }
1013
+ ca_monop_setup(ca, cs->parent, cs->op_id);
1014
+ return self;
1015
+ }
1016
+
1017
+ void
1018
+ Init_ca_obj_monop (void)
1019
+ {
1020
+ rb_cCAMonOp = rb_define_class("CAMonOp", rb_cCAView);
1021
+
1022
+ ca_monop_func.struct_size = sizeof(CAMonOp);
1023
+ ca_monop_func.pool_bytes = ca_monop_pool_bytes;
1024
+ ca_monop_func.pool_init = ca_monop_pool_init;
1025
+
1026
+ CA_OBJ_MONOP = ca_install_obj_type(rb_cCAMonOp,
1027
+ &camonop_data_type,
1028
+ rb_cCArrayMask,
1029
+ &carray_mask_data_type, &ca_monop_func, sizeof(ca_monop_func));
1030
+ rb_define_const(rb_cObject, "CA_OBJ_MONOP", INT2NUM(CA_OBJ_MONOP));
1031
+
1032
+ /* op_id constants (all 34 ops). Cast op_ids are computed at
1033
+ call sites as CA_MONOP_CAST_BASE + data_type (no per-data_type constant). */
1034
+ rb_define_const(rb_cCAMonOp, "OP_ZERO", INT2NUM(CA_MONOP_ZERO));
1035
+ rb_define_const(rb_cCAMonOp, "OP_ONE", INT2NUM(CA_MONOP_ONE));
1036
+ rb_define_const(rb_cCAMonOp, "OP_FRAC", INT2NUM(CA_MONOP_FRAC));
1037
+ rb_define_const(rb_cCAMonOp, "OP_NEG", INT2NUM(CA_MONOP_NEG));
1038
+ rb_define_const(rb_cCAMonOp, "OP_BIT_NEG", INT2NUM(CA_MONOP_BIT_NEG));
1039
+ rb_define_const(rb_cCAMonOp, "OP_ABS_I", INT2NUM(CA_MONOP_ABS_I));
1040
+ rb_define_const(rb_cCAMonOp, "OP_CONJ", INT2NUM(CA_MONOP_CONJ));
1041
+ rb_define_const(rb_cCAMonOp, "OP_NOT", INT2NUM(CA_MONOP_NOT));
1042
+ rb_define_const(rb_cCAMonOp, "OP_CEIL", INT2NUM(CA_MONOP_CEIL));
1043
+ rb_define_const(rb_cCAMonOp, "OP_FLOOR", INT2NUM(CA_MONOP_FLOOR));
1044
+ rb_define_const(rb_cCAMonOp, "OP_ROUND", INT2NUM(CA_MONOP_ROUND));
1045
+ rb_define_const(rb_cCAMonOp, "OP_RCP", INT2NUM(CA_MONOP_RCP));
1046
+ rb_define_const(rb_cCAMonOp, "OP_RAD", INT2NUM(CA_MONOP_RAD));
1047
+ rb_define_const(rb_cCAMonOp, "OP_DEG", INT2NUM(CA_MONOP_DEG));
1048
+ rb_define_const(rb_cCAMonOp, "OP_SQRT", INT2NUM(CA_MONOP_SQRT));
1049
+ rb_define_const(rb_cCAMonOp, "OP_EXP", INT2NUM(CA_MONOP_EXP));
1050
+ rb_define_const(rb_cCAMonOp, "OP_EXP2", INT2NUM(CA_MONOP_EXP2));
1051
+ rb_define_const(rb_cCAMonOp, "OP_EXP10", INT2NUM(CA_MONOP_EXP10));
1052
+ rb_define_const(rb_cCAMonOp, "OP_LOG", INT2NUM(CA_MONOP_LOG));
1053
+ rb_define_const(rb_cCAMonOp, "OP_LOG10", INT2NUM(CA_MONOP_LOG10));
1054
+ rb_define_const(rb_cCAMonOp, "OP_LOG2", INT2NUM(CA_MONOP_LOG2));
1055
+ rb_define_const(rb_cCAMonOp, "OP_LOGB", INT2NUM(CA_MONOP_LOGB));
1056
+ rb_define_const(rb_cCAMonOp, "OP_SIN", INT2NUM(CA_MONOP_SIN));
1057
+ rb_define_const(rb_cCAMonOp, "OP_COS", INT2NUM(CA_MONOP_COS));
1058
+ rb_define_const(rb_cCAMonOp, "OP_TAN", INT2NUM(CA_MONOP_TAN));
1059
+ rb_define_const(rb_cCAMonOp, "OP_ASIN", INT2NUM(CA_MONOP_ASIN));
1060
+ rb_define_const(rb_cCAMonOp, "OP_ACOS", INT2NUM(CA_MONOP_ACOS));
1061
+ rb_define_const(rb_cCAMonOp, "OP_ATAN", INT2NUM(CA_MONOP_ATAN));
1062
+ rb_define_const(rb_cCAMonOp, "OP_SINH", INT2NUM(CA_MONOP_SINH));
1063
+ rb_define_const(rb_cCAMonOp, "OP_COSH", INT2NUM(CA_MONOP_COSH));
1064
+ rb_define_const(rb_cCAMonOp, "OP_TANH", INT2NUM(CA_MONOP_TANH));
1065
+ rb_define_const(rb_cCAMonOp, "OP_ASINH", INT2NUM(CA_MONOP_ASINH));
1066
+ rb_define_const(rb_cCAMonOp, "OP_ACOSH", INT2NUM(CA_MONOP_ACOSH));
1067
+ rb_define_const(rb_cCAMonOp, "OP_ATANH", INT2NUM(CA_MONOP_ATANH));
1068
+ rb_define_const(rb_cCAMonOp, "OP_IMAG_I", INT2NUM(CA_MONOP_IMAG_I));
1069
+ /* M.1: PyTorch alignment additions */
1070
+ rb_define_const(rb_cCAMonOp, "OP_EXPM1", INT2NUM(CA_MONOP_EXPM1));
1071
+ rb_define_const(rb_cCAMonOp, "OP_LOG1P", INT2NUM(CA_MONOP_LOG1P));
1072
+ rb_define_const(rb_cCAMonOp, "OP_RSQRT", INT2NUM(CA_MONOP_RSQRT));
1073
+ rb_define_const(rb_cCAMonOp, "OP_TRUNC", INT2NUM(CA_MONOP_TRUNC));
1074
+ rb_define_const(rb_cCAMonOp, "OP_SQUARE", INT2NUM(CA_MONOP_SQUARE));
1075
+ /* M.4: angle normalisation migration */
1076
+ rb_define_const(rb_cCAMonOp, "OP_DEG_360", INT2NUM(CA_MONOP_DEG_360));
1077
+ rb_define_const(rb_cCAMonOp, "OP_DEG_180", INT2NUM(CA_MONOP_DEG_180));
1078
+ rb_define_const(rb_cCAMonOp, "OP_RAD_2PI", INT2NUM(CA_MONOP_RAD_2PI));
1079
+ rb_define_const(rb_cCAMonOp, "OP_RAD_PI", INT2NUM(CA_MONOP_RAD_PI));
1080
+ rb_define_const(rb_cCAMonOp, "OP_SIGN", INT2NUM(CA_MONOP_SIGN));
1081
+ rb_define_const(rb_cCAMonOp, "OP_ARG_I", INT2NUM(CA_MONOP_ARG_I));
1082
+ rb_define_const(rb_cCAMonOp, "CAST_BASE", INT2NUM(CA_MONOP_CAST_BASE));
1083
+
1084
+ rb_define_alloc_func(rb_cCAMonOp, rb_ca_monop_s_allocate);
1085
+ rb_define_method(rb_cCAMonOp, "initialize_copy",
1086
+ rb_ca_monop_initialize_copy, 1);
1087
+
1088
+ /* Public builder (used by lib/carray/lazy.rb per-op method
1089
+ redefinitions). Inserts a cast node if needed (cast-before). */
1090
+ rb_define_singleton_method(rb_cCAMonOp, "__build__",
1091
+ rb_ca_monop_s_build, 2);
1092
+
1093
+ /* Lazy-view predicate exposed on CArray (covers CAMonOp and
1094
+ CALazyMarker). */
1095
+ rb_define_method(rb_cCArray, "__lazy_view__?",
1096
+ rb_ca_is_lazy_view_p, 0);
1097
+
1098
+ /* op_id accessor for inspect / dump_tree consumers in Ruby. */
1099
+ rb_define_method(rb_cCAMonOp, "__op_id__",
1100
+ rb_ca_monop_op_id, 0);
1101
+
1102
+ /* Test instrumentation (not user-facing API). */
1103
+ rb_define_singleton_method(rb_cCAMonOp, "__reset_scratch_counter__",
1104
+ rb_ca_monop_s_reset_scratch_counter, 0);
1105
+ rb_define_singleton_method(rb_cCAMonOp, "__scratch_count__",
1106
+ rb_ca_monop_s_scratch_count, 0);
1107
+ rb_define_singleton_method(rb_cCAMonOp, "__reset_materialise_counter__",
1108
+ rb_ca_monop_s_reset_materialise_counter, 0);
1109
+ rb_define_singleton_method(rb_cCAMonOp, "__materialise_count__",
1110
+ rb_ca_monop_s_materialise_count, 0);
1111
+ }