carray 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +325 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +32 -32
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_group.c +55 -19
  8. data/ext/ca_binop_dispatch.c +5 -6
  9. data/ext/ca_binop_dispatch.h +0 -7
  10. data/ext/ca_categorical_iterator.c +13 -13
  11. data/ext/ca_for_each_element.h +6 -8
  12. data/ext/ca_group_iter.c +1 -1
  13. data/ext/ca_kernel_iterator.c +69 -23
  14. data/ext/ca_kernel_iterator.h +3 -10
  15. data/ext/ca_obj_bincmp.c +57 -27
  16. data/ext/ca_obj_binop.c +86 -30
  17. data/ext/ca_obj_const_string.c +3 -1
  18. data/ext/ca_obj_face.c +80 -0
  19. data/ext/ca_obj_face.h +33 -1
  20. data/ext/ca_obj_fake.c +11 -0
  21. data/ext/ca_obj_fixlen_string.c +3 -1
  22. data/ext/ca_obj_grid.c +11 -3
  23. data/ext/ca_obj_meld.c +13 -8
  24. data/ext/ca_obj_moncmp.c +33 -14
  25. data/ext/ca_obj_monop.c +19 -7
  26. data/ext/ca_obj_object.c +99 -4
  27. data/ext/ca_obj_record.c +3 -1
  28. data/ext/ca_obj_refer.c +30 -6
  29. data/ext/ca_obj_roll.c +13 -5
  30. data/ext/ca_obj_select_axis.c +16 -6
  31. data/ext/ca_obj_shift.c +3 -3
  32. data/ext/ca_obj_stride.c +106 -23
  33. data/ext/ca_obj_string.c +3 -1
  34. data/ext/ca_obj_tile.c +13 -5
  35. data/ext/ca_obj_time.c +3 -1
  36. data/ext/ca_obj_timedelta.c +3 -1
  37. data/ext/ca_obj_transpose.c +1 -1
  38. data/ext/ca_obj_triop.c +62 -23
  39. data/ext/ca_obj_window.c +86 -17
  40. data/ext/ca_op_cmplx64.h +123 -0
  41. data/ext/ca_op_ipower.c +0 -3
  42. data/ext/ca_sort_kernels.h +5 -5
  43. data/ext/ca_sweep_engine.c +78 -35
  44. data/ext/ca_sweep_engine.h +13 -4
  45. data/ext/ca_transform_common.c +7 -0
  46. data/ext/carray.h +127 -130
  47. data/ext/carray_access.c +56 -34
  48. data/ext/carray_bincount.c +8 -8
  49. data/ext/carray_broadcast.c +99 -6
  50. data/ext/carray_build_flags.h +3 -0
  51. data/ext/carray_call_cfunc.c +1491 -0
  52. data/ext/carray_call_cfunc.h +153 -0
  53. data/ext/carray_cast.c +69 -31
  54. data/ext/carray_conversion.c +22 -22
  55. data/ext/carray_copy.c +12 -2
  56. data/ext/carray_core.c +57 -10
  57. data/ext/carray_count.c +1 -1
  58. data/ext/carray_element.c +7 -7
  59. data/ext/carray_factorize.c +28 -28
  60. data/ext/carray_hold.c +1 -1
  61. data/ext/carray_index_classifier.c +7 -21
  62. data/ext/carray_internal.h +19 -3
  63. data/ext/carray_kernels_bincmp.c +1 -0
  64. data/ext/carray_kernels_binop.c +320 -298
  65. data/ext/carray_kernels_init.c +1095 -0
  66. data/ext/carray_kernels_map.c +1 -0
  67. data/ext/carray_kernels_moncmp.c +1 -0
  68. data/ext/carray_kernels_monop.c +412 -411
  69. data/ext/carray_kernels_reduce_aggregate.c +88 -87
  70. data/ext/carray_kernels_reduce_boolean.c +1 -0
  71. data/ext/carray_kernels_reduce_cumulative.c +54 -53
  72. data/ext/carray_kernels_reduce_extreme.c +51 -50
  73. data/ext/carray_kernels_reduce_variance.c +1 -0
  74. data/ext/carray_kernels_scan.c +1 -0
  75. data/ext/carray_kernels_search.c +93 -92
  76. data/ext/carray_kernels_sort.c +1 -0
  77. data/ext/carray_kernels_triop.c +1 -0
  78. data/ext/carray_lazy.c +175 -5
  79. data/ext/carray_mask.c +6 -5
  80. data/ext/carray_median_percentile.c +1 -1
  81. data/ext/carray_memory_view.c +46 -21
  82. data/ext/carray_operator.c +46 -49
  83. data/ext/carray_partition.c +4 -4
  84. data/ext/carray_random.c +7 -7
  85. data/ext/carray_scatter.c +1 -1
  86. data/ext/carray_sort.c +3 -3
  87. data/ext/carray_sort_kernel.c +10 -10
  88. data/ext/carray_test.c +1 -1
  89. data/ext/extconf.rb +18 -0
  90. data/ext/mk_call_cfunc.rb +243 -52
  91. data/ext/mkkernel.rb +354 -100
  92. data/ext/ruby_carray.c +18 -16
  93. data/ext/version.h +4 -4
  94. data/lib/carray/autoload_carray.rb +19 -0
  95. data/lib/carray/autoload_method_extension.rb +2 -1
  96. data/lib/carray/axis_group.rb +7 -7
  97. data/lib/carray/basics.rb +59 -59
  98. data/lib/carray/bincount_nd.rb +12 -12
  99. data/lib/carray/block_iterator.rb +24 -14
  100. data/lib/carray/categorical.rb +4 -4
  101. data/lib/carray/categorical_iterator.rb +98 -76
  102. data/lib/carray/conditional.rb +14 -14
  103. data/lib/carray/construct.rb +21 -0
  104. data/lib/carray/core_extensions.rb +62 -5
  105. data/lib/carray/data_type_extension.rb +18 -1
  106. data/lib/carray/frame/concat.rb +3 -3
  107. data/lib/carray/frame/convert.rb +1 -1
  108. data/lib/carray/frame/frame.rb +4 -11
  109. data/lib/carray/frame/io.rb +159 -2
  110. data/lib/carray/frame/sort.rb +1 -1
  111. data/lib/carray/frame/verbs.rb +18 -1
  112. data/lib/carray/fuse_source.rb +123 -0
  113. data/lib/carray/fusion.rb +218 -0
  114. data/lib/carray/histogram.rb +16 -16
  115. data/lib/carray/inspect.rb +1 -7
  116. data/lib/carray/iterator.rb +4 -3
  117. data/lib/carray/lazy.rb +125 -73
  118. data/lib/carray/meld_reduce.rb +2 -2
  119. data/lib/carray/methods/align_addr.rb +1 -1
  120. data/lib/carray/methods/composition.rb +1 -1
  121. data/lib/carray/methods/is_in.rb +12 -12
  122. data/lib/carray/methods/locate_addr.rb +6 -1
  123. data/lib/carray/methods/mask_duplicates.rb +1 -1
  124. data/lib/carray/methods/meshgrid.rb +4 -5
  125. data/lib/carray/methods/mode.rb +2 -2
  126. data/lib/carray/methods/nunique.rb +1 -1
  127. data/lib/carray/methods/snap.rb +7 -2
  128. data/lib/carray/methods/unique.rb +3 -3
  129. data/lib/carray/methods/value_counts.rb +2 -2
  130. data/lib/carray/runtime.rb +0 -19
  131. data/lib/carray/slab_iterator.rb +20 -7
  132. data/lib/carray/string_operation_extension.rb +5 -5
  133. data/lib/carray/time.rb +890 -491
  134. data/lib/carray/window_iterator.rb +285 -13
  135. data/lib/carray.rb +5 -5
  136. data/yard-stubs/ca_obj_array.rb +385 -0
  137. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  138. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  139. data/yard-stubs/ca_obj_block.rb +73 -0
  140. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  141. data/yard-stubs/ca_obj_fake.rb +31 -0
  142. data/yard-stubs/ca_obj_farray.rb +32 -0
  143. data/yard-stubs/ca_obj_field.rb +45 -0
  144. data/yard-stubs/ca_obj_grid.rb +35 -0
  145. data/yard-stubs/ca_obj_refer.rb +72 -0
  146. data/yard-stubs/ca_obj_roll.rb +45 -0
  147. data/yard-stubs/ca_obj_shift.rb +43 -0
  148. data/yard-stubs/ca_obj_stride.rb +181 -0
  149. data/yard-stubs/ca_obj_tile.rb +29 -0
  150. data/yard-stubs/ca_obj_transpose.rb +40 -0
  151. data/yard-stubs/ca_obj_window.rb +49 -0
  152. data/yard-stubs/carray_access.rb +131 -0
  153. data/yard-stubs/carray_attribute.rb +246 -0
  154. data/yard-stubs/carray_broadcast.rb +37 -0
  155. data/yard-stubs/carray_cast.rb +489 -0
  156. data/yard-stubs/carray_class.rb +65 -0
  157. data/yard-stubs/carray_conversion.rb +76 -0
  158. data/yard-stubs/carray_copy.rb +79 -0
  159. data/yard-stubs/carray_core.rb +114 -0
  160. data/yard-stubs/carray_count.rb +79 -0
  161. data/yard-stubs/carray_element.rb +108 -0
  162. data/yard-stubs/carray_generate.rb +66 -0
  163. data/yard-stubs/carray_lazy.rb +23 -0
  164. data/yard-stubs/carray_loop.rb +140 -0
  165. data/yard-stubs/carray_mask.rb +259 -0
  166. data/yard-stubs/carray_math.rb +132 -0
  167. data/yard-stubs/carray_mathfunc.rb +45 -0
  168. data/yard-stubs/carray_median_percentile.rb +89 -0
  169. data/yard-stubs/carray_memory_view.rb +163 -0
  170. data/yard-stubs/carray_order.rb +312 -0
  171. data/yard-stubs/carray_random.rb +89 -0
  172. data/yard-stubs/carray_scatter.rb +106 -0
  173. data/yard-stubs/carray_slab.rb +57 -0
  174. data/yard-stubs/carray_sort.rb +163 -0
  175. data/yard-stubs/carray_test.rb +85 -0
  176. data/yard-stubs/carray_undef.rb +64 -0
  177. data/yard-stubs/carray_utils.rb +97 -0
  178. data/yard-stubs/ruby_carray.rb +193 -0
  179. metadata +59 -10
  180. data/ext/ca_obj_unbound_repeat.c +0 -496
data/lib/carray/lazy.rb CHANGED
@@ -73,11 +73,11 @@ class CArray
73
73
  rad_2pi: CAMonOp::OP_RAD_2PI,
74
74
  rad_pi: CAMonOp::OP_RAD_PI,
75
75
 
76
- # Sign function (preserve dtype). bool/uint → 0/1, sint → -1/0/1,
76
+ # Sign function (preserves the data type). bool/uint → 0/1, sint → -1/0/1,
77
77
  # float → -1/0/1 NaN-preserving, complex → unit vector or 0.
78
78
  sign: CAMonOp::OP_SIGN,
79
79
 
80
- # imag_i: preserve-dtype primitive (0 for numeric, cimag for complex
80
+ # imag_i: type-preserving primitive (0 for numeric, cimag for complex
81
81
  # in the real slot). Primarily consumed by the `imag` special case
82
82
  # below but also directly callable via `a.lazy.imag_i`; entry here
83
83
  # so the direct call fuses instead of falling to eager.
@@ -118,24 +118,28 @@ class CArray
118
118
  # complex-specific dispatch (= magnitude as float). Re-express that
119
119
  # dispatch via chain composition over existing lazy ops so abs rides
120
120
  # the lazy substrate fully:
121
- # numeric parent: CAMonOp(OP_ABS_I) (1 node)
122
- # complex parent: CAMonOp(cast_f64) ∘ CAMonOp(OP_ABS_I) (2 nodes)
123
- # The complex chain works because ca_monop_abs_i for cmplx128 stores
124
- # |z| in the real slot with imag=0, then cast cmplx128->f64 picks up
125
- # the real part — semantically identical to rb_ca_abs (= abs_i + .real
126
- # + copy). No new C kernel, no new op_id required.
121
+ # numeric parent: CAMonOp(OP_ABS_I) (1 node)
122
+ # complex parent: CAMonOp(cast_<float>) ∘ CAMonOp(OP_ABS_I) (2 nodes)
123
+ # The complex chain works because ca_monop_abs_i stores |z| in the
124
+ # real slot with imag=0, then the cast picks up the real part —
125
+ # semantically identical to the eager abs. The cast target is the
126
+ # real component width of the operand (cmplx64 -> f32, cmplx128 ->
127
+ # f64), matching the eager kernel's `complex: :real_of_source`.
128
+ # No new C kernel, no new op_id required.
127
129
  # ---------------------------------------------------------------------------
128
130
 
131
+ # @private
129
132
  alias_method :__abs_eager__, :abs
130
133
  __abs_eager_um__ = instance_method(:__abs_eager__)
131
134
  define_method(:abs) do
132
135
  if __lazy_view__?
133
136
  abs_i_node = CAMonOp.__build__(self, CAMonOp::OP_ABS_I)
134
137
  if complex?
135
- # CA_FLOAT64 is a Symbol; convert via data_type_code so the cast
138
+ # float_dt is a Symbol; convert via data_type_code so the cast
136
139
  # op_id can be computed by Integer arithmetic.
140
+ float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
137
141
  CAMonOp.__build__(abs_i_node,
138
- CAMonOp::CAST_BASE + CArray.data_type_code(CA_FLOAT64))
142
+ CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
139
143
  else
140
144
  abs_i_node
141
145
  end
@@ -174,6 +178,7 @@ class CArray
174
178
  # mutability on the lazy path is not a regression.
175
179
  # ---------------------------------------------------------------------------
176
180
 
181
+ # @private
177
182
  alias_method :__real_eager__, :real
178
183
  __real_eager_um__ = instance_method(:__real_eager__)
179
184
  define_method(:real) do
@@ -216,11 +221,11 @@ class CArray
216
221
  end
217
222
 
218
223
  # ---------------------------------------------------------------------------
219
- # arg lazy fuse: eager `arg` always returns f64 regardless of input
220
- # dtype (data_type-changing monop; see MkKernel.monop :arg output rule).
221
- # CAMonOp's cast-before invariant requires input dtype == output dtype
224
+ # arg lazy fuse: eager `arg` is a data_type-changing monop (see
225
+ # MkKernel.monop :arg output rule).
226
+ # CAMonOp's cast-before invariant requires input data type == output data type
222
227
  # at each in-place step, so `arg` can't sit directly in the substrate.
223
- # Chain compose via the preserve-dtype `arg_i` primitive:
228
+ # Chain compose via the type-preserving `arg_i` primitive:
224
229
  #
225
230
  # integer / bool parent → cast_f64 ∘ arg_i (result f64)
226
231
  # float parent → arg_i (result same float)
@@ -228,14 +233,13 @@ class CArray
228
233
  # component of complex slot; cast extracts
229
234
  # the real part, same trick as abs / imag)
230
235
  #
231
- # Note: eager `arg` always widens to f64. The lazy path preserves
232
- # the operand's float / complex width (f32 f32, cmplx64 f32)
233
- # rather than always going to f64 — matches the general lazy-substrate
234
- # rule "scalar keeps operand's precision" (see ca_lazy_wrap_scalar
235
- # header). A user needing exact eager-parity can wrap with
236
- # `.to_type(:float64)` before / after.
236
+ # Both paths keep the operand's float / complex width (f32 → f32,
237
+ # cmplx64 f32), and both send integers to f64 since pi does not fit
238
+ # an integer slot. That agrees with the general lazy-substrate rule
239
+ # "scalar keeps operand's precision" (see ca_lazy_wrap_scalar header).
237
240
  # ---------------------------------------------------------------------------
238
241
 
242
+ # @private
239
243
  alias_method :__arg_eager__, :arg
240
244
  __arg_eager_um__ = instance_method(:__arg_eager__)
241
245
  define_method(:arg) do
@@ -307,7 +311,6 @@ class CArray
307
311
 
308
312
  # IEEE 754 remainder (distinct semantics from `%` / `mod`: float
309
313
  # branch uses C99 `remainder`, round-half-to-even).
310
- :reminder => CABinOp::OP_REMINDER,
311
314
  }.freeze
312
315
 
313
316
  LAZY_BINOP_OP_IDS.each do |method_name, op_id|
@@ -533,7 +536,7 @@ class CArray
533
536
  if __lazy_view__?
534
537
  dt_name = data_type_name
535
538
  if CScalar.respond_to?(dt_name)
536
- s = CScalar.send(dt_name)
539
+ s = CScalar.public_send(dt_name)
537
540
  s[0] = other
538
541
  [s, self]
539
542
  else
@@ -801,6 +804,15 @@ end
801
804
  end
802
805
  copy
803
806
  end
807
+
808
+ # Where something is registered to compute an expression -- a compiler,
809
+ # say -- it is asked here, and the walk below is what happens when it
810
+ # declines, when nothing is registered, or when the array is small
811
+ # enough that walking is the faster answer.
812
+ def copy
813
+ return super unless CArray.expression_evaluator
814
+ CArray::Fusion.evaluate(self) || super
815
+ end
804
816
  end
805
817
  end
806
818
 
@@ -825,65 +837,105 @@ end
825
837
  # - A bare lazy return auto-materialises; escaping the lazy view (e.g.
826
838
  # stashing it in an Array) is the user's responsibility.
827
839
  # ---------------------------------------------------------------------------
840
+ # CArray.fuse
841
+ #
842
+ # Builds an expression instead of evaluating it, so that it is computed in
843
+ # one pass with no array standing for a step along the way. The block is
844
+ # read rather than called; see CArray::FuseSource for why, and for what
845
+ # happens where its source cannot be read.
846
+ # ---------------------------------------------------------------------------
828
847
  class << CArray
829
- # @overload fuse(*args) { |*shadows| ... }
830
- # Runs a transient lazy-fusion scope: wraps each CArray argument
831
- # with `.lazy`, yields the wrappers (and any non-CArray args)
832
- # to the block, then auto-materialises a bare lazy return value
833
- # into an entity. Non-lazy returns pass through as-is.
834
- # @param args [Array<CArray, Object>] operands.
835
- # @yieldparam shadows [Array<CArray, Object>] lazy wrappers
836
- # paired with pass-through non-CArray operands.
837
- # @return [Object]
838
- # @raise [LocalJumpError] when no block is given.
839
- def fuse(*args)
840
- raise LocalJumpError, "CArray.fuse requires a block" unless block_given?
841
- shadows = args.map { |a| a.is_a?(CArray) ? a.lazy : a }
842
- result = yield(*shadows)
843
- case result
844
- when CAMonOp, CABinOp, CAMonCmp, CABinCmp, CALazyMarker
845
- result.to_ca
846
- else
847
- result
848
+ # The object asked to compute an expression, or nil. CArray can always
849
+ # walk one, so nothing has to be registered and nothing changes when
850
+ # nothing is; what a registered evaluator adds is a second way to arrive
851
+ # at the same answer, and it may decline.
852
+ #
853
+ # It is called as `call(plan, out)` -- see {CArray::Fusion} for what a
854
+ # plan holds -- and answers by filling `out` and returning something
855
+ # true, or by returning something false having written nothing. Raising
856
+ # is not an answer: one that raises is dropped, with a warning, and
857
+ # expressions are walked from there on.
858
+ #
859
+ # @return [#call, nil]
860
+ attr_reader :expression_evaluator
861
+
862
+ # @param object [#call, nil] see {#expression_evaluator}
863
+ # @raise [ArgumentError] when it cannot be called.
864
+ def expression_evaluator= (object)
865
+ if object && ! object.respond_to?(:call)
866
+ raise ArgumentError,
867
+ "an expression evaluator must respond to #call(plan, out)"
848
868
  end
869
+ @expression_evaluator = object
849
870
  end
850
871
 
851
- # CArray.lazy(*args) { |lazies| ... } — dual of fuse
872
+ # @overload fuse { <expression> }
873
+ # Builds the expression rather than evaluating it, so that it is
874
+ # computed in one pass with no array standing for a step along the way.
852
875
  #
853
- # Like `fuse`, wraps each CArray argument with `.lazy` and yields it to
854
- # the block, but **does not auto-materialise at block exit** (= returns
855
- # the lazy structure as-is). If the block return is non-lazy (= Numeric
856
- # / entity CArray / Array etc.) it's pass-through (= same polymorphic
857
- # semantics as fuse).
876
+ # The block is not called. Its source is read, every name in it that
877
+ # holds a CArray is given `.lazy`, and the result is evaluated back in
878
+ # the block's own binding -- so `self`, instance variables, methods and
879
+ # constants are what they were where it was written.
858
880
  #
859
- # Use cases:
860
- # - Passing a chain between functions: build the lazy expression
861
- # inside the function and materialise at the caller
862
- # (= `.to_ca` / `.sum` / `.mean(axis:)` etc.)
863
- # - Reusable expressions: apply the same expr to multiple datasets
864
- # - debug / dump_tree: observe the lazy structure as-is
865
- # - Pick the materialise form later: full materialise or reduction
881
+ # out[] = CArray.fuse { (a + b) * (c - a) }
882
+ # total = CArray.fuse { a * weight }.sum
866
883
  #
867
- # Example:
868
- # expr = CArray.lazy(a, b) { |s, o| (s + o) * 2 }
869
- # expr.class #=> CABinOp (lazy view)
870
- # expr.to_ca # full materialise
871
- # expr.sum # reduction (= chain + reduce in 1 pass)
884
+ # What comes back is the expression, not an array: it is computed where
885
+ # it is used -- stored into an array, reduced, or asked for one with
886
+ # `to_ca`. A block holding anything but an expression over arrays comes
887
+ # back as whatever it evaluated to.
872
888
  #
873
- # Polymorphic semantics (= symmetric with fuse):
874
- # CArray.lazy(25.0, b) { |s, o| s + o } # s=25.0 Float pass-through
875
- # CArray.lazy(arr, b) { |s, o| s + o } # s=arr.lazy
876
- # @overload lazy(*args) { |*shadows| ... }
877
- # Like {.fuse} but does not auto-materialise: returns whatever
878
- # the block yields (typically a lazy view) so the expression
879
- # can be materialised later via `.to_ca`, `.sum`, `.mean`, etc.
880
- # @param args [Array<CArray, Object>] operands.
881
- # @yieldparam shadows [Array<CArray, Object>] lazy wrappers.
882
- # @return [Object]
889
+ # @return [Object] the expression, as a lazy view
883
890
  # @raise [LocalJumpError] when no block is given.
884
- def lazy(*args)
885
- raise LocalJumpError, "CArray.lazy requires a block" unless block_given?
886
- shadows = args.map { |a| a.is_a?(CArray) ? a.lazy : a }
887
- yield(*shadows)
891
+ # @raise [ArgumentError] when the block's source cannot be read -- in
892
+ # irb, in `eval`, or from a file that is gone. Write `.lazy` on the
893
+ # operands there: `a.lazy + b.lazy`.
894
+ def fuse (*args, &block)
895
+ raise LocalJumpError, "CArray.fuse requires a block" unless block
896
+ unless args.empty?
897
+ raise ArgumentError,
898
+ "CArray.fuse takes no arguments: write the expression itself, " \
899
+ "`CArray.fuse { a + b }`, and the names in it are read from " \
900
+ "where the block was written"
901
+ end
902
+ CArray::FuseSource.evaluate(block)
903
+ end
904
+
905
+ # @overload jit_for (*extents) { |i, j, ...| ... }
906
+ # Runs a block over an index space, so that a cell may reach the ones
907
+ # around it -- a recurrence, a stencil. The block is compiled, and the
908
+ # compiler is the carray-jit gem.
909
+ # @raise [NotImplementedError] when that gem is not installed.
910
+ def jit_for (*extents, **options)
911
+ raise NotImplementedError, no_compiler("jit_for")
912
+ end
913
+
914
+ # @overload jit_each { ... }
915
+ # Runs a block over the cells of arrays at once, naming each cell by the
916
+ # array it came from, and writing what it computes into arrays of yours:
917
+ # `CArray.jit_each { out = a + b * c }`. The block is compiled, and the
918
+ # compiler is the carray-jit gem.
919
+ # @raise [NotImplementedError] when that gem is not installed.
920
+ def jit_each (&block)
921
+ raise NotImplementedError, no_compiler("jit_each")
922
+ end
923
+
924
+ # @overload jit_map { ... }
925
+ # The same block with its value asked for: the last statement is what
926
+ # every cell of the result gets, and the result comes back. The block is
927
+ # compiled, and the compiler is the carray-jit gem.
928
+ # @raise [NotImplementedError] when that gem is not installed.
929
+ def jit_map (&block)
930
+ raise NotImplementedError, no_compiler("jit_map")
931
+ end
932
+
933
+ private
934
+
935
+ def no_compiler (name)
936
+ "CArray.#{name} compiles its block, and the compiler is the carray-jit " \
937
+ "gem, which is not installed. An expression over whole arrays can be " \
938
+ "written as `CArray.fuse { ... }` instead, which needs no compiler; " \
939
+ "reaching a neighbouring cell, or writing the loop itself, does."
888
940
  end
889
941
  end
@@ -190,7 +190,7 @@ class CAMeld
190
190
  # is unchanged. (Reduced axis == meld_axis is handled by the callers'
191
191
  # meld_axis branch and never reaches here.)
192
192
  def non_meld_axis_decompose(op, axis)
193
- parts = parents.map { |p| p.send(op, axis: axis) }
193
+ parts = parents.map { |p| p.public_send(op, axis: axis) }
194
194
  axis_norm = axis < 0 ? axis + ndim : axis
195
195
  new_meld_axis = axis_norm < meld_axis ? meld_axis - 1 : meld_axis
196
196
  CArray.meld(parts, axis: new_meld_axis).copy
@@ -202,7 +202,7 @@ class CAMeld
202
202
  # Empty-parent / short-parent handling — Welford needs n >= 2 per parent
203
203
  # to recover m2 from p.variance for sample, n >= 1 for variancep (n=1
204
204
  # gives m2=0, fine). Any parent below its threshold punts to super,
205
- # which handles UNDEF / 0-count per CLAUDE.md contract.
205
+ # which handles the UNDEF / zero-count cases.
206
206
  min_n = sample ? 2 : 1
207
207
  if axis.nil?
208
208
  return yield if parents.any? { |p| p.elements < min_n }
@@ -33,7 +33,7 @@ class CArray
33
33
  #
34
34
  # Value equality follows the value-hash discovery family (numeric `==` with
35
35
  # NaN collapsed and `-0.0 == +0.0`; object `hash` / `eql?`; fixlen byte
36
- # equality). Arrays are coerced to the first array's dtype within the same
36
+ # equality). Arrays are coerced to the first array's data type within the same
37
37
  # family (cross-family raises). Masked cells do not enter `common`.
38
38
  #
39
39
  # @param arrays [Array<CArray>] two or more arrays (Array / Range coerced
@@ -12,7 +12,7 @@
12
12
  # (`stack` / `meld` / `montage`, plus `CArray#stack` and `CAStack#append`)
13
13
  # lives in carray/stack.rb. The cheat sheet below covers the whole family.
14
14
  #
15
- # | view (same dtype) | eager (auto-cast)
15
+ # | view (same data type) | eager (auto-cast)
16
16
  # -------------------+------------------------+-----------------------
17
17
  # concat existing | meld (CAMeld view) | concatenate (materialised)
18
18
  # axis (ndim same) | |
@@ -10,12 +10,12 @@ class CArray
10
10
  # argument is accepted; to test a few immediate values pass an
11
11
  # Array (`a.is_in([0, -1])`).
12
12
  #
13
- # When `self` and `values` have different numeric dtypes they are
13
+ # When `self` and `values` have different numeric data types they are
14
14
  # promoted to a common type first (the same promotion binops use,
15
15
  # {CArray.result_type}), so membership is value-correct across
16
- # dtypes (e.g. an int cell equals a float set element of the same
16
+ # data types (e.g. an int cell equals a float set element of the same
17
17
  # value, and a fractional set element never truncates onto an int
18
- # cell). Genuinely incompatible dtypes (e.g. numeric vs fixlen)
18
+ # cell). Genuinely incompatible data types (e.g. numeric vs fixlen)
19
19
  # raise.
20
20
  #
21
21
  # Membership is value-based and shares the distinctness of the
@@ -32,7 +32,7 @@ class CArray
32
32
  # reduction, compose with {#any}: `a.is_in(values).any(axis: k)`.
33
33
  #
34
34
  # @param values [CArray, Array, Range] the set to test membership
35
- # against. Promoted with `self` to a common dtype.
35
+ # against. Promoted with `self` to a common data type.
36
36
  # Between two time arrays the question is about instants, not ticks:
37
37
  # `values` is reconciled into `self`'s unit first, so a `:D` array and an
38
38
  # `:h` array match on the instants they share. The same holds for the set
@@ -49,7 +49,7 @@ class CArray
49
49
  # `self` and `other`, in `self`'s first-appearance order.
50
50
  #
51
51
  # Value-based, sharing the distinctness of the discovery family
52
- # (see {#is_in}); `self` and `other` are promoted to a common dtype.
52
+ # (see {#is_in}); `self` and `other` are promoted to a common data type.
53
53
  # Masked cells of either array do not participate. The result is
54
54
  # always flat, like {#unique}, because the distinct values of a
55
55
  # fiber vary in number.
@@ -97,18 +97,18 @@ class CArray
97
97
 
98
98
  # Reconcile a set-valued argument with self to a common data_type via
99
99
  # CArray.result_type (the single-source promotion rule the eager binop and
100
- # lazy CABinOp share), returning [self', set']. Only dtypes are reconciled,
100
+ # lazy CABinOp share), returning [self', set']. Only data types are reconciled,
101
101
  # never shapes: unlike the binop coercion (cast_self_or_other) the set's
102
102
  # shape never broadcasts against self's, so a size-1 self keeps its shape.
103
- # result_type raises for genuinely incompatible dtypes (numeric vs fixlen).
103
+ # result_type raises for genuinely incompatible data types (numeric vs fixlen).
104
104
  #
105
- # A bare Array / Range has no intrinsic dtype, so the common type is inferred
105
+ # A bare Array / Range has no intrinsic data type, so the common type is inferred
106
106
  # from self and the individual elements (result_type classifies each scalar).
107
107
  # This keeps a fractional literal from truncating onto an int self, and an
108
108
  # int literal from boxing a float self into the object lane (where Float 2.0
109
- # is not eql? Integer 2). A CArray argument uses its own dtype; any other
109
+ # is not eql? Integer 2). A CArray argument uses its own data type; any other
110
110
  # operand (Numo, a MemoryView producer, ...) comes in through wrap_readonly,
111
- # the canonical type-coercion entry, so its format's dtype drives the promote.
111
+ # the canonical type-coercion entry, so its format's data type drives the promote.
112
112
  def promote_value_set (values)
113
113
  case values
114
114
  when Array then return promote_elements(values)
@@ -123,8 +123,8 @@ class CArray
123
123
  # Bare Array / Range against numeric self: infer the common numeric type
124
124
  # from self and the elements (each classified by result_type), so a
125
125
  # fractional literal promotes self to float instead of truncating. Against
126
- # object / fixlen self the elements are values, not dtype specifiers (a
127
- # String is a value, not a type name), so build the set in self's dtype.
126
+ # object / fixlen self the elements are values, not data type specifiers (a
127
+ # String is a value, not a type name), so build the set in self's data type.
128
128
  def promote_elements (elems)
129
129
  if data_type == CA_OBJECT || data_type == CA_FIXLEN
130
130
  [self, elems.to_ca.to_type(data_type)]
@@ -35,7 +35,12 @@ class CArray
35
35
  fill[] = sec.nil? ? UNDEF : sec
36
36
  sec = fill
37
37
  end
38
- si = sec.mask_invalid.send(direction).int64
38
+ masked = sec.mask_invalid
39
+ si = case direction
40
+ when :round then masked.round
41
+ when :floor then masked.floor
42
+ when :ceil then masked.ceil
43
+ end.int64
39
44
  idx = ri.project(si)
40
45
  if tolerance
41
46
  dist = (ref.project(idx) - self).abs
@@ -30,7 +30,7 @@ class CArray
30
30
  flatten.send(:__mask_duplicates__, 0).reshape(*shape)
31
31
  else
32
32
  # Per-fiber single-pass seen-set hash (C __mask_duplicates__): one lane
33
- # per dtype family (integer widen, float bitwise key with NaN collapse,
33
+ # per data type family (integer widen, float bitwise key with NaN collapse,
34
34
  # object rb_hash + rb_eql, fixlen byte-hash + memcmp, boolean via the
35
35
  # uint8 lane). O(distinct) memory, no sort/gather/scatter buffers.
36
36
  __mask_duplicates__(normalize_axis(axis, "mask_duplicates"))
@@ -17,10 +17,9 @@ class CArray
17
17
  # input order in both modes.
18
18
  #
19
19
  # When `copy` is true (default) each output is a materialised
20
- # CArray; when false, view chains (CARepeat / CAUnboundRepeat)
21
- # are returned. When `sparse` is true the outputs are
22
- # CAUnboundRepeat views that broadcast on demand, saving memory
23
- # for large grids.
20
+ # CArray; when false, a view is returned. When `sparse` is true
21
+ # each output keeps a size-1 axis wherever the full grid would
22
+ # repeat, and broadcasts on demand, saving memory for large grids.
24
23
  #
25
24
  # If a block is given, yields the resulting arrays as splat
26
25
  # arguments and returns the block's value.
@@ -77,7 +76,7 @@ class CArray
77
76
  list = axes.map.with_index do |axis, k|
78
77
  d = dest[k]
79
78
  idx = if sparse
80
- Array.new(ndim) { |i| i == d ? nil : :* }
79
+ Array.new(ndim) { |i| i == d ? nil : :_ }
81
80
  else
82
81
  out_shape.dup.tap { |s| s[d] = :% }
83
82
  end
@@ -33,7 +33,7 @@ class CArray
33
33
  # the whole array.
34
34
  # @return [CArray] boolean CArray of `self.shape`.
35
35
  def is_mode (axis: nil)
36
- # Per-fiber two-pass frequency table (C __is_mode__), one lane per dtype
36
+ # Per-fiber two-pass frequency table (C __is_mode__), one lane per data type
37
37
  # family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen
38
38
  # byte-hash + memcmp). Ties are all marked; masked cells stay false.
39
39
  if axis.nil?
@@ -112,7 +112,7 @@ class CArray
112
112
 
113
113
  private
114
114
 
115
- # Flat mode: the distinct modal values ascending, 1-D CArray of self's dtype.
115
+ # Flat mode: the distinct modal values ascending, 1-D CArray of self's data type.
116
116
  # The single source of what counts as a mode (per-axis reuses it per fiber).
117
117
  # The distinct values with the maximum count, read from the frequency table
118
118
  # (value_counts, which covers numeric / object / fixlen), then sorted
@@ -30,7 +30,7 @@ class CArray
30
30
  # @return [Integer, CArray] Integer for `axis: nil`, otherwise a
31
31
  # reduced `CA_INT64` CArray.
32
32
  def nunique (axis: nil, keep_axis: false)
33
- # Per-fiber single-pass seen-set hash (C __nunique__), one lane per dtype
33
+ # Per-fiber single-pass seen-set hash (C __nunique__), one lane per data type
34
34
  # family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen
35
35
  # byte-hash + memcmp). Masked cells are skipped; the accumulator is a no-op
36
36
  # (the distinct count is the interned-key count).
@@ -7,7 +7,7 @@ class CArray
7
7
  # matching `CArray#round`; `:floor` snaps toward `-inf`, `:ceil`
8
8
  # toward `+inf`).
9
9
  #
10
- # The output has the same dtype as `self` (integer input is coerced
10
+ # The output has the same data type as `self` (integer input is coerced
11
11
  # to float internally when `step` / `offset` are floats, following
12
12
  # normal arithmetic promotion). NaN / Inf are preserved as a mask
13
13
  # on the output; the rounding kernels map NaN to 0.0, which would
@@ -45,7 +45,12 @@ class CArray
45
45
  # Detect NaN / Inf before rounding (which maps NaN -> 0.0 silently).
46
46
  invalid_mask = scaled.float? ? scaled.is_invalid : nil
47
47
 
48
- out = scaled.send(direction) * step + offset
48
+ snapped = case direction
49
+ when :round then scaled.round
50
+ when :floor then scaled.floor
51
+ when :ceil then scaled.ceil
52
+ end
53
+ out = snapped * step + offset
49
54
 
50
55
  if invalid_mask && invalid_mask.count(true) > 0
51
56
  out.mask = out.has_mask? ? (out.mask | invalid_mask) : invalid_mask
@@ -16,7 +16,7 @@ class CArray
16
16
  # Masked cells do not participate and never appear in the result;
17
17
  # an all-masked array yields an empty CArray.
18
18
  #
19
- # Distinctness follows `==` for numeric dtypes, with two float
19
+ # Distinctness follows `==` for numeric data types, with two float
20
20
  # special cases so the result matches value-based expectations:
21
21
  # all NaN collapse to a single distinct value (rather than one per
22
22
  # cell) and -0.0 / +0.0 are the same value. The value kept for
@@ -32,10 +32,10 @@ class CArray
32
32
  # its own unit: the distinct values are values, so the array comes back
33
33
  # as itself rather than as raw storage ticks.
34
34
  #
35
- # @return [CArray] 1-D CArray of the distinct values, same dtype
35
+ # @return [CArray] 1-D CArray of the distinct values, same data type
36
36
  # as `self`.
37
37
  def unique (sort: false)
38
- # Single-pass seen-set hash (C __unique_flat__), one lane per dtype family:
38
+ # Single-pass seen-set hash (C __unique_flat__), one lane per data type family:
39
39
  # integer widens to a 64-bit key; float uses the bitwise key with all-NaN
40
40
  # collapsed and -0.0 / +0.0 normalized; object keys on rb_hash + rb_eql and
41
41
  # fixlen on a byte-hash + memcmp, both reproducing Ruby Hash distinctness.
@@ -3,7 +3,7 @@ class CArray
3
3
  # @overload value_counts(sort: false)
4
4
  # Returns `[values, counts]`, the distinct values of `self` paired
5
5
  # with the number of times each occurs. `values` is a 1-D CArray of
6
- # `self`'s dtype; `counts` is a 1-D `CA_INT64` where `counts[i]` is
6
+ # `self`'s data type; `counts` is a 1-D `CA_INT64` where `counts[i]` is
7
7
  # the number of occurrences of `values[i]`. This is the frequency-
8
8
  # table member of the value-hash discovery family ({#unique},
9
9
  # {#mask_duplicates}, {#nunique}); like {#unique} it always
@@ -39,7 +39,7 @@ class CArray
39
39
  raise ArgumentError, "value_counts: sort must be false, :count, or :value"
40
40
  end
41
41
  # Single-pass frequency-table hash (C __value_counts_flat__), one lane per
42
- # dtype family: integer widens to a 64-bit key; float uses the bitwise key
42
+ # data type family: integer widens to a 64-bit key; float uses the bitwise key
43
43
  # with all NaN collapsed and -0.0 / +0.0 normalized; object keys on rb_hash +
44
44
  # rb_eql and fixlen on a byte-hash + memcmp, both reproducing Ruby Hash
45
45
  # distinctness. Masked cells are skipped in the kernel.
@@ -87,22 +87,3 @@ class CArray
87
87
 
88
88
  end
89
89
 
90
- class CAUnboundRepeat
91
- # Overrides {CArray#template} for unbound-repeat views.
92
- #
93
- # The C-level `CArray#template` allocates an entity sized to `self.shape`,
94
- # which for a CAUnboundRepeat would include the inflated unbound (`:*`)
95
- # axes — semantically wrong (unbound axes are not yet bound to a size)
96
- # and a memory bomb on broadcast patterns. Required for correctness.
97
- #
98
- # This override allocates at the **parent's compact shape** instead, then
99
- # rewraps the result with the same `spec` so the returned view preserves
100
- # the unbound broadcast pattern.
101
- #
102
- # @param argv [Array] forwarded to {CArray#template} (data_type, bytes:, ...)
103
- # @yield forwarded to {CArray#template}
104
- # @return [CAUnboundRepeat] new entity wrapped with the original unbound spec
105
- def template (*argv, &block)
106
- return parent.template(*argv,&block)[*spec.map{|x| x != :* ? nil : x}]
107
- end
108
- end
@@ -90,7 +90,7 @@ class CASlabIterator < CAIterator
90
90
  # A per-slab reduction that folds each slab to one value is exactly the core
91
91
  # per-axis reduction over the slab axes, so every reduction is a direct
92
92
  # delegation to `reference.<op>(axis: slab_axes)`. This inherits the core
93
- # dtype, mask, empty/all-masked (identity vs UNDEF) and epsilon-close
93
+ # data type, mask, empty/all-masked (identity vs UNDEF) and epsilon-close
94
94
  # contracts unchanged -- there is no separate slab reduction kernel. (Unlike
95
95
  # the map / reduce block surface, these are mask-aware: they route through the
96
96
  # core reduction, which handles masked sources.)
@@ -98,14 +98,23 @@ class CASlabIterator < CAIterator
98
98
  # @overload sum
99
99
  # Per-slab sum, delegating to `reference.sum(axis: slab_axes)`.
100
100
  # @return [CArray] one value per slab (shape = self.dim minus the slab axes)
101
+ # @overload accumulate
102
+ # Per-slab sum kept in the source's own data type, wrapping at its width,
103
+ # as the core `accumulate` does -- `sum` answers in the type the core
104
+ # promotes to (float64 for integers).
105
+ # @return [CArray] one value per slab
101
106
  # The rest are analogous: prod / mean / min / max, sample and population
102
107
  # variance / stddev, all / any, fused minmax ([min, max] pair), the axis-local
103
108
  # position min_index / max_index (index within the slab axes), and the flat
104
109
  # source address min_addr / max_addr (which source cell holds the extremum).
105
- [:sum, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
110
+ [:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
106
111
  :variancep, :stddevp, :minmax, :min_index, :max_index,
107
112
  :min_addr, :max_addr].each do |op|
108
- define_method(op) { @reference.send(op, axis: @slab_axes) }
113
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
114
+ def #{op}
115
+ @reference.#{op}(axis: @slab_axes)
116
+ end
117
+ RUBY
109
118
  end
110
119
 
111
120
  # @overload count(v = <none>)
@@ -243,7 +252,7 @@ class CASlabIterator < CAIterator
243
252
  #
244
253
  # A per-slab running accumulation along the slab axis is exactly the core
245
254
  # per-axis cumulative over that axis, so every scan delegates to
246
- # `reference.<op>(axis: slab_axis)`, inheriting the core dtype / mask (masked
255
+ # `reference.<op>(axis: slab_axis)`, inheriting the core data type / mask (masked
247
256
  # cells hold the running total, output unmasked) contracts unchanged. Each
248
257
  # cell is in exactly one slab (a partition), so the running value is
249
258
  # well-defined; the surface is uniform with the family even though it
@@ -258,16 +267,20 @@ class CASlabIterator < CAIterator
258
267
  # Per-slab inclusive running product (float64), reference-shaped.
259
268
  # @return [CArray]
260
269
  # @overload cummax
261
- # Per-slab inclusive running maximum (reference dtype), reference-shaped.
270
+ # Per-slab inclusive running maximum (reference data type), reference-shaped.
262
271
  # @return [CArray]
263
272
  # @overload cummin
264
- # Per-slab inclusive running minimum (reference dtype), reference-shaped.
273
+ # Per-slab inclusive running minimum (reference data type), reference-shaped.
265
274
  # @return [CArray]
266
275
  # @overload cumcount
267
276
  # Per-slab running count of present cells (int64), reference-shaped.
268
277
  # @return [CArray]
269
278
  [:cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |op|
270
- define_method(op) { @reference.send(op, axis: single_scan_axis(op)) }
279
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
280
+ def #{op}
281
+ @reference.#{op}(axis: single_scan_axis(:#{op}))
282
+ end
283
+ RUBY
271
284
  end
272
285
 
273
286
  private