carray 3.0.2 → 3.0.4

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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
data/lib/carray/lazy.rb CHANGED
@@ -2,12 +2,12 @@
2
2
  #
3
3
  # Per-op lazy-or-eager method redefinition on CArray.
4
4
  #
5
- # For each of the monop/monfunc methods, alias the existing eager
6
- # implementation as `__<name>_eager__` and redefine `<name>` to dispatch:
5
+ # For each of the monop/monfunc methods, keep the existing eager
6
+ # implementation as an UnboundMethod and redefine `<name>` to dispatch:
7
7
  #
8
- # - if self.__lazy_view__? → CAMonOp.__build__(self, OP_<NAME>)
8
+ # - if self.lazy_view? → CAMonOp.__build__(self, OP_<NAME>)
9
9
  # (cast node insertion handled in C if data_type mismatch)
10
- # - else → __<name>_eager__
10
+ # - else → the eager implementation
11
11
  #
12
12
  # CAREFUL: the redefinition must run once at load time AFTER the eager
13
13
  # methods are bound by Init_carray_math. carray_ext.bundle Init order
@@ -84,31 +84,42 @@ class CArray
84
84
  imag_i: CAMonOp::OP_IMAG_I,
85
85
  }.freeze
86
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)
87
+ # The lazy-or-eager dispatch of the operators, in a module in front of
88
+ # the eager methods: each reaches its eager method with super. The
89
+ # blocks are shareable (they capture an op id at most), so the operators
90
+ # can be called from any Ractor.
91
+ module LazyDispatch; end
92
+ prepend LazyDispatch
93
+
94
+ CArray.send(:define_named_methods, LazyDispatch,
95
+ LAZY_MONOP_OP_IDS.reject { |name, _| name == :round }, template: <<~'RUBY')
96
+ def __NAME__
97
+ if lazy_view?
98
+ CAMonOp.__build__(self, __VALUE__)
98
99
  else
99
- eager_um.bind_call(self)
100
+ super()
101
+ end
102
+ end
103
+ RUBY
104
+
105
+ # round takes digits, which the lazy node does not carry: rounding to
106
+ # digits evaluates the expression and rounds the result.
107
+ module LazyDispatch
108
+ def round(*digits)
109
+ if digits.empty? && lazy_view?
110
+ CAMonOp.__build__(self, CAMonOp::OP_ROUND)
111
+ else
112
+ super
100
113
  end
101
114
  end
102
115
  end
103
116
 
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.
117
+ # Operator aliases (`~` -> bit_neg, `-@` -> neg) were installed in
118
+ # CArray by rb_define_alias at Init time (see MkKernel.alias_monop in
119
+ # ext/carray_kernels_monop.c), and name the eager C function: `~a` on a
120
+ # lazy view would materialise instead of building a CAMonOp node. Made
121
+ # again here, an alias resolves through the prepended modules, so the
122
+ # operator form takes the dispatch above.
112
123
  alias_method :"~", :bit_neg
113
124
  alias_method :"-@", :neg
114
125
 
@@ -129,22 +140,24 @@ class CArray
129
140
  # ---------------------------------------------------------------------------
130
141
 
131
142
  # @private
132
- alias_method :__abs_eager__, :abs
133
- __abs_eager_um__ = instance_method(:__abs_eager__)
134
- define_method(:abs) do
135
- if __lazy_view__?
136
- abs_i_node = CAMonOp.__build__(self, CAMonOp::OP_ABS_I)
137
- if complex?
138
- # float_dt is a Symbol; convert via data_type_code so the cast
139
- # op_id can be computed by Integer arithmetic.
140
- float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
141
- CAMonOp.__build__(abs_i_node,
142
- CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
143
+ module LazyDispatch
144
+ def abs
145
+ if lazy_view?
146
+ # A boolean has no absolute value: the eager abs refuses it.
147
+ return super() if boolean?
148
+ abs_i_node = CAMonOp.__build__(self, CAMonOp::OP_ABS_I)
149
+ if complex?
150
+ # float_dt is a Symbol; convert via data_type_code so the cast
151
+ # op_id can be computed by Integer arithmetic.
152
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
153
+ CAMonOp.__build__(abs_i_node,
154
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
155
+ else
156
+ abs_i_node
157
+ end
143
158
  else
144
- abs_i_node
159
+ super()
145
160
  end
146
- else
147
- __abs_eager_um__.bind_call(self)
148
161
  end
149
162
  end
150
163
 
@@ -179,44 +192,48 @@ class CArray
179
192
  # ---------------------------------------------------------------------------
180
193
 
181
194
  # @private
182
- alias_method :__real_eager__, :real
183
- __real_eager_um__ = instance_method(:__real_eager__)
184
- define_method(:real) do
185
- if __lazy_view__?
186
- if complex?
187
- float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
188
- # float_dt is a Symbol; convert via data_type_code for kernel
189
- # op_id arithmetic.
190
- CAMonOp.__build__(self,
191
- CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
195
+ module LazyDispatch
196
+ def real
197
+ if lazy_view?
198
+ if complex?
199
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
200
+ # float_dt is a Symbol; convert via data_type_code for kernel
201
+ # op_id arithmetic.
202
+ CAMonOp.__build__(self,
203
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
204
+ else
205
+ # `real` of a real array is the array itself; preserve the lazy
206
+ # chain unchanged (eager returns self[] = CARefer wrap, but for
207
+ # lazy chain consumption returning self keeps streaming intact).
208
+ self
209
+ end
192
210
  else
193
- # `real` of a real array is the array itself; preserve the lazy
194
- # chain unchanged (eager returns self[] = CARefer wrap, but for
195
- # lazy chain consumption returning self keeps streaming intact).
196
- self
211
+ super()
197
212
  end
198
- else
199
- __real_eager_um__.bind_call(self)
200
213
  end
201
214
  end
202
215
 
203
216
  # @!visibility private
204
- alias_method :__imag_eager__, :imag
205
- __imag_eager_um__ = instance_method(:__imag_eager__)
206
- define_method(:imag) do
207
- if __lazy_view__?
208
- imag_i_node = CAMonOp.__build__(self, CAMonOp::OP_IMAG_I)
209
- if complex?
210
- float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
211
- # float_dt is a Symbol; convert via data_type_code for kernel
212
- # op_id arithmetic.
213
- CAMonOp.__build__(imag_i_node,
214
- CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
217
+ module LazyDispatch
218
+ def imag
219
+ if lazy_view?
220
+ imag_i_node = CAMonOp.__build__(self, CAMonOp::OP_IMAG_I)
221
+ if boolean?
222
+ # imag_i reads a boolean as int64; the eager imag stays boolean.
223
+ CAMonOp.__build__(imag_i_node,
224
+ CAMonOp::CAST_BASE + CArray.data_type_code(CA_BOOLEAN))
225
+ elsif complex?
226
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
227
+ # float_dt is a Symbol; convert via data_type_code for kernel
228
+ # op_id arithmetic.
229
+ CAMonOp.__build__(imag_i_node,
230
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
231
+ else
232
+ imag_i_node
233
+ end
215
234
  else
216
- imag_i_node
235
+ super()
217
236
  end
218
- else
219
- __imag_eager_um__.bind_call(self)
220
237
  end
221
238
  end
222
239
 
@@ -235,31 +252,31 @@ class CArray
235
252
  #
236
253
  # Both paths keep the operand's float / complex width (f32 → f32,
237
254
  # cmplx64 → f32), and both send integers to f64 since pi does not fit
238
- # an integer slot. That agrees with the general lazy-substrate rule
239
- # "scalar keeps operand's precision" (see ca_lazy_wrap_scalar header).
255
+ # an integer slot.
240
256
  # ---------------------------------------------------------------------------
241
257
 
242
258
  # @private
243
- alias_method :__arg_eager__, :arg
244
- __arg_eager_um__ = instance_method(:__arg_eager__)
245
- define_method(:arg) do
246
- if __lazy_view__?
247
- parent = self
248
- if integer? || boolean?
249
- parent = CAMonOp.__build__(parent,
250
- CAMonOp::CAST_BASE +
251
- CArray.data_type_code(CA_FLOAT64))
252
- end
253
- arg_i_node = CAMonOp.__build__(parent, CAMonOp::OP_ARG_I)
254
- if complex?
255
- float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
256
- CAMonOp.__build__(arg_i_node,
257
- CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
259
+ module LazyDispatch
260
+ def arg
261
+ if lazy_view?
262
+ return super() if boolean? # the eager arg refuses it
263
+ parent = self
264
+ if integer?
265
+ parent = CAMonOp.__build__(parent,
266
+ CAMonOp::CAST_BASE +
267
+ CArray.data_type_code(CA_FLOAT64))
268
+ end
269
+ arg_i_node = CAMonOp.__build__(parent, CAMonOp::OP_ARG_I)
270
+ if complex?
271
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
272
+ CAMonOp.__build__(arg_i_node,
273
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
274
+ else
275
+ arg_i_node
276
+ end
258
277
  else
259
- arg_i_node
278
+ super()
260
279
  end
261
- else
262
- __arg_eager_um__.bind_call(self)
263
280
  end
264
281
  end
265
282
 
@@ -313,26 +330,26 @@ class CArray
313
330
  # branch uses C99 `remainder`, round-half-to-even).
314
331
  }.freeze
315
332
 
316
- LAZY_BINOP_OP_IDS.each do |method_name, op_id|
317
- eager_alias = :"__#{method_name}_eager__"
318
- alias_method(eager_alias, method_name)
319
- eager_um = instance_method(eager_alias)
320
- define_method(method_name) do |other|
321
- if __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
322
- CABinOp.__build__(self, other, op_id)
333
+ CArray.send(:define_named_methods, LazyDispatch, LAZY_BINOP_OP_IDS, template: <<~'RUBY')
334
+ def __NAME__(other)
335
+ if lazy_view? || (other.is_a?(CArray) && other.lazy_view?)
336
+ CABinOp.__build__(self, other, __VALUE__)
323
337
  else
324
- eager_um.bind_call(self, other)
338
+ super(other)
325
339
  end
326
340
  end
327
- end
341
+ RUBY
342
+
343
+ # The shift count of an Integer is brought into the array's type before
344
+ # either form sees it (see CArray::IntegerShiftCount).
345
+ prepend IntegerShiftCount
328
346
 
329
347
  # Binop word aliases (`add` -> `+`, `mul` -> `*`, `bit_and` -> `&`, …) were
330
- # installed by rb_define_alias at Init time (see MkKernel.alias_binop in
331
- # ext/carray_kernels_*.c) BEFORE the redefinitions above. The C alias
332
- # snapshots the original eager method entry, so the word form still points
333
- # at the original C function and skips lazy dispatch — `a.lazy.add(1)`
334
- # materialises instead of building a CABinOp node. Re-alias here so the
335
- # word form follows the redefined operator method.
348
+ # installed in CArray by rb_define_alias at Init time (see
349
+ # MkKernel.alias_binop in ext/carray_kernels_*.c) and name the eager C
350
+ # function, so `a.lazy.add(1)` would materialise. Made again here, an
351
+ # alias resolves through the prepended modules -- IntegerShiftCount, then
352
+ # LazyDispatch -- so the word form takes the operator's.
336
353
  {
337
354
  add: :+,
338
355
  sub: :-,
@@ -345,21 +362,20 @@ class CArray
345
362
  bit_lshift: :<<,
346
363
  bit_rshift: :>>,
347
364
  }.each do |word, op|
348
- alias_method(word, op)
365
+ alias_method word, op
349
366
  end
350
367
 
351
368
  # `pow` is the canonical C method (defined by rb_define_method in
352
369
  # ext/ca_op_ipower.c) that `**` is rb_define_alias'd to. The `**`
353
- # override in this file (with the ipower Integer-exponent fast path)
354
- # does not propagate to `pow` through the C alias, so `a.lazy.pow(2)`
355
- # falls to eager. Re-alias so `pow` follows the redefined `**`.
370
+ # dispatch in this file does not reach `pow` through the C alias, so
371
+ # `a.lazy.pow(2)` would fall to eager; `pow` is aliased again here.
356
372
  alias_method :pow, :**
357
373
 
358
374
  # `fmax` / `fmin` are rb_define_alias'd to `pmax` / `pmin` at Init
359
- # time (same C-alias-snapshot trap). Re-alias so the C99-familiar
360
- # spellings follow the redefined lazy dispatch above.
361
- alias_method :fmax, :pmax
362
- alias_method :fmin, :pmin
375
+ # time (the same C alias); they are aliased again beside them, in
376
+ # LazyDispatch.
377
+ LazyDispatch.send(:alias_method, :fmax, :pmax)
378
+ LazyDispatch.send(:alias_method, :fmin, :pmin)
363
379
 
364
380
  # ---------------------------------------------------------------------------
365
381
  # Triop dispatch (fma / fms / clip). CATriOp is the CABinOp analog for
@@ -367,7 +383,7 @@ class CArray
367
383
  # dispatches to CATriOp.__build__ when any of self / op2 / op3 is a
368
384
  # lazy view, and falls to the eager C method otherwise.
369
385
  #
370
- # `clip` is the strict-clamp entry (`__clip_ki__`, called by the
386
+ # `clip` is the strict-clamp entry (`clamp_between`, called by the
371
387
  # lib/carray/basics.rb `clip` wrapper's both-bounds-present path). The
372
388
  # nil-bound one-sided cases route through the wrapper's `pmax` / `pmin`
373
389
  # calls, which themselves lazy-fuse via LAZY_BINOP_OP_IDS above — so
@@ -376,26 +392,26 @@ class CArray
376
392
  # ---------------------------------------------------------------------------
377
393
 
378
394
  LAZY_TRIOP_OP_IDS = {
379
- fma: CATriOp::OP_FMA,
380
- fms: CATriOp::OP_FMS,
381
- __clip_ki__: CATriOp::OP_CLIP,
395
+ fma: CATriOp::OP_FMA,
396
+ fms: CATriOp::OP_FMS,
397
+ clamp_between: CATriOp::OP_CLIP,
382
398
  }.freeze
383
399
 
384
- LAZY_TRIOP_OP_IDS.each do |method_name, op_id|
385
- eager_alias = :"__#{method_name}_eager_triop__"
386
- alias_method(eager_alias, method_name)
387
- eager_um = instance_method(eager_alias)
388
- define_method(method_name) do |op2, op3|
389
- lazy_receiver = __lazy_view__? ||
390
- (op2.is_a?(CArray) && op2.__lazy_view__?) ||
391
- (op3.is_a?(CArray) && op3.__lazy_view__?)
400
+ CArray.send(:define_named_methods, LazyDispatch, LAZY_TRIOP_OP_IDS, template: <<~'RUBY')
401
+ def __NAME__(op2, op3)
402
+ lazy_receiver = lazy_view? ||
403
+ (op2.is_a?(CArray) && op2.lazy_view?) ||
404
+ (op3.is_a?(CArray) && op3.lazy_view?)
392
405
  if lazy_receiver
393
- CATriOp.__build__(self, op2, op3, op_id)
406
+ CATriOp.__build__(self, op2, op3, __VALUE__)
394
407
  else
395
- eager_um.bind_call(self, op2, op3)
408
+ super(op2, op3)
396
409
  end
397
410
  end
398
- end
411
+ RUBY
412
+ # The clip kernel takes both bounds; the public entry is `clip`.
413
+ private :clamp_between
414
+ LazyDispatch.send(:private, :clamp_between)
399
415
 
400
416
  # `**` integer-exponent lazy fast path.
401
417
  #
@@ -417,16 +433,17 @@ class CArray
417
433
  # `power` binop kernel already uses `op_powi_<type>` for integer data_types
418
434
  # via the `int:` expr in MkKernel.binop :power, so OP_POW + int parent
419
435
  # is already fast. The lazy-substrate gap was Float/Complex only.
420
- __pow_eager_um__ = instance_method(:"__**_eager__")
421
- define_method(:**) do |other|
422
- if other.is_a?(Integer) && (float? || complex?) &&
423
- (__lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?))
424
- CABinOp.__build__(self, CScalar.int64.tap { |s| s[0] = other },
425
- CABinOp::OP_IPOWER)
426
- elsif __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
427
- CABinOp.__build__(self, other, CABinOp::OP_POW)
428
- else
429
- __pow_eager_um__.bind_call(self, other)
436
+ module LazyDispatch
437
+ def **(other)
438
+ if other.is_a?(Integer) && (float? || complex?) &&
439
+ (lazy_view? || (other.is_a?(CArray) && other.lazy_view?))
440
+ CABinOp.__build__(self, CScalar.int64.tap { |s| s[0] = other },
441
+ CABinOp::OP_IPOWER)
442
+ elsif lazy_view? || (other.is_a?(CArray) && other.lazy_view?)
443
+ CABinOp.__build__(self, other, CABinOp::OP_POW)
444
+ else
445
+ super(other)
446
+ end
430
447
  end
431
448
  end
432
449
 
@@ -472,40 +489,34 @@ class CArray
472
489
  :feq => CABinCmp::OP_FEQ,
473
490
  }.freeze
474
491
 
475
- LAZY_BINCMP_OP_IDS.each do |method_name, op_id|
476
- eager_alias = :"__#{method_name}_eager_bincmp__"
477
- alias_method(eager_alias, method_name)
478
- eager_um = instance_method(eager_alias)
479
- define_method(method_name) do |other|
480
- if __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
481
- CABinCmp.__build__(self, other, op_id)
492
+ CArray.send(:define_named_methods, LazyDispatch, LAZY_BINCMP_OP_IDS, template: <<~'RUBY')
493
+ def __NAME__(other)
494
+ if lazy_view? || (other.is_a?(CArray) && other.lazy_view?)
495
+ CABinCmp.__build__(self, other, __VALUE__)
482
496
  else
483
- eager_um.bind_call(self, other)
497
+ super(other)
484
498
  end
485
499
  end
486
- end
500
+ RUBY
487
501
 
488
502
  # tolerance-bearing bincmp ops (= is_close / is_equiv) use the same
489
503
  # CABinCmp dispatch but with a 2nd `tol` positional arg. Kept in a
490
- # separate dict because the LAZY_BINCMP_OP_IDS define_method block above
504
+ # separate dict because the LAZY_BINCMP_OP_IDS template above
491
505
  # uses arity 1 (|other|); these need arity 2 (|other, tol|).
492
506
  LAZY_BINCMP_TOL_OP_IDS = {
493
507
  :is_close => CABinCmp::OP_IS_CLOSE,
494
508
  :is_equiv => CABinCmp::OP_IS_EQUIV,
495
509
  }.freeze
496
510
 
497
- LAZY_BINCMP_TOL_OP_IDS.each do |method_name, op_id|
498
- eager_alias = :"__#{method_name}_eager_bincmp_tol__"
499
- alias_method(eager_alias, method_name)
500
- eager_um = instance_method(eager_alias)
501
- define_method(method_name) do |other, tol|
502
- if __lazy_view__? || (other.is_a?(CArray) && other.__lazy_view__?)
503
- CABinCmp.__build__(self, other, op_id, tol.to_f.abs)
511
+ CArray.send(:define_named_methods, LazyDispatch, LAZY_BINCMP_TOL_OP_IDS, template: <<~'RUBY')
512
+ def __NAME__(other, tol)
513
+ if lazy_view? || (other.is_a?(CArray) && other.lazy_view?)
514
+ CABinCmp.__build__(self, other, __VALUE__, tol.to_f.abs)
504
515
  else
505
- eager_um.bind_call(self, other, tol)
516
+ super(other, tol)
506
517
  end
507
518
  end
508
- end
519
+ RUBY
509
520
 
510
521
  # @!visibility private
511
522
  LAZY_MONCMP_OP_IDS = {
@@ -516,36 +527,58 @@ class CArray
516
527
  signbit: CAMonCmp::OP_SIGNBIT,
517
528
  }.freeze
518
529
 
519
- LAZY_MONCMP_OP_IDS.each do |name, op_id|
520
- eager_alias = :"__#{name}_eager_moncmp__"
521
- alias_method(eager_alias, name)
522
- eager_um = instance_method(eager_alias)
523
- define_method(name) do
524
- if __lazy_view__?
525
- CAMonCmp.__build__(self, op_id)
530
+ CArray.send(:define_named_methods, LazyDispatch, LAZY_MONCMP_OP_IDS, template: <<~'RUBY')
531
+ def __NAME__
532
+ if lazy_view?
533
+ CAMonCmp.__build__(self, __VALUE__)
526
534
  else
527
- eager_um.bind_call(self)
535
+ super()
528
536
  end
529
537
  end
530
- end
538
+ RUBY
531
539
 
540
+ # A lazy receiver takes the scalar the eager coerce makes, and stays
541
+ # lazy itself: the builder promotes the pair as the eager operator does.
532
542
  # @!visibility private
533
- alias_method :__coerce_eager__, :coerce
534
- coerce_um = instance_method(:__coerce_eager__)
535
- define_method(:coerce) do |other|
536
- if __lazy_view__?
537
- dt_name = data_type_name
538
- if CScalar.respond_to?(dt_name)
539
- s = CScalar.public_send(dt_name)
540
- s[0] = other
541
- [s, self]
542
- else
543
- coerce_um.bind_call(self, other)
543
+ module LazyDispatch
544
+ def coerce(other)
545
+ begin
546
+ scalar, receiver = super(other)
547
+ rescue RangeError => e
548
+ raise unless other.is_a?(Integer)
549
+ return [OutOfRangeInteger.new(other, self, e), self]
544
550
  end
545
- else
546
- coerce_um.bind_call(self, other)
551
+ lazy_view? ? [scalar, self] : [scalar, receiver]
547
552
  end
548
553
  end
554
+
555
+ # An Integer the array's type cannot hold, on the left of an operator.
556
+ # A comparison is answered from the array's side, where it compares by
557
+ # value; any other operator refuses the Integer as the array would.
558
+ class OutOfRangeInteger
559
+ def initialize (value, array, error)
560
+ @value, @array, @error = value, array, error
561
+ end
562
+
563
+ def < (other) = flip(other, :>)
564
+ def <= (other) = flip(other, :>=)
565
+ def > (other) = flip(other, :<)
566
+ def >= (other) = flip(other, :<=)
567
+
568
+ def method_missing (*)
569
+ raise @error
570
+ end
571
+
572
+ def respond_to_missing? (*) = false
573
+
574
+ private
575
+
576
+ def flip (other, op)
577
+ raise @error unless other.equal?(@array)
578
+ @array.send(op, @value)
579
+ end
580
+ end
581
+ private_constant :OutOfRangeInteger
549
582
  end
550
583
 
551
584
  # ---------------------------------------------------------------------------
@@ -618,14 +651,6 @@ class CAMonOp
618
651
  to_ca.to_a
619
652
  end
620
653
 
621
- # @overload sort(*args, **kwargs)
622
- # Materialises `self` into a copy and returns
623
- # `entity.sort(*args, **kwargs)`.
624
- # @return [CArray]
625
- def sort(*args, **kwargs)
626
- copy.sort(*args, **kwargs)
627
- end
628
-
629
654
  # --- MemoryView export → TypeError + .copy recommendation -----------
630
655
 
631
656
  # bulk-memory-view / Numo / Apache Arrow consumers go through the MV
@@ -714,13 +739,6 @@ class CABinOp
714
739
  to_ca.to_a
715
740
  end
716
741
 
717
- # @overload sort(*args, **kwargs)
718
- # Materialises `self` and sorts the resulting entity.
719
- # @return [CArray]
720
- def sort(*args, **kwargs)
721
- to_ca.sort(*args, **kwargs)
722
- end
723
-
724
742
  # @overload to_memory_view
725
743
  # Not supported for lazy binops.
726
744
  # @raise [TypeError] always.
@@ -856,8 +874,13 @@ class << CArray
856
874
  # is not an answer: one that raises is dropped, with a warning, and
857
875
  # expressions are walked from there on.
858
876
  #
877
+ # Only the main Ractor asks it: it is registered there, and is not
878
+ # shared with the others, where expressions are walked.
879
+ #
859
880
  # @return [#call, nil]
860
- attr_reader :expression_evaluator
881
+ def expression_evaluator
882
+ Ractor.current == Ractor.main ? @expression_evaluator : nil
883
+ end
861
884
 
862
885
  # @param object [#call, nil] see {#expression_evaluator}
863
886
  # @raise [ArgumentError] when it cannot be called.