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/ext/ruby_carray.c CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Single entry point Init_carray_ext: declares the CArray class
4
4
  hierarchy + exception class + CA module, populates top-level
5
- constants (CA_RANK_MAX / CA_NIL / CA_* data_type Symbols / alignment
5
+ constants (CA_RANK_MAX / CA_* data_type Symbols / alignment
6
6
  values), then drives the per-module Init_* sequence in load order.
7
7
 
8
8
  CAREFUL: the Init_* call order below carries real ordering
@@ -14,6 +14,7 @@
14
14
  ---------------------------------------------------------------------------- */
15
15
 
16
16
  #include "carray.h"
17
+ #include "carray_build_flags.h"
17
18
  #include "version.h"
18
19
 
19
20
  #ifdef HAVE_RB_ARITHMETIC_SEQUENCE_EXTRACT
@@ -26,7 +27,7 @@ VALUE rb_cCAIterator;
26
27
  VALUE rb_eCADataTypeError;
27
28
  VALUE rb_mCA;
28
29
 
29
- VALUE CA_NIL;
30
+ VALUE CA_UNSPECIFIED;
30
31
 
31
32
  void Init_carray_core ();
32
33
  void Init_carray_undef ();
@@ -81,7 +82,6 @@ void Init_ca_obj_stack (); /* multi-parent outer-axis stack */
81
82
  void Init_ca_obj_meld (); /* multi-parent ragged concat along existing axis */
82
83
  void Init_ca_obj_transpose ();
83
84
  void Init_ca_obj_repeat ();
84
- void Init_ca_obj_unbound_repeat ();
85
85
  void Init_ca_obj_reduce ();
86
86
  void Init_ca_obj_field ();
87
87
  void Init_ca_obj_fake ();
@@ -107,7 +107,7 @@ void Init_ca_obj_moncmp (); /* CAMonCmp + lazy elementwise predicate dispatch *
107
107
 
108
108
  /* Init_carray_iterator retired; CAIterator is a form-only base defined inline
109
109
  in Init_carray_ext (accessors in lib/carray/iterator.rb). The 2.0 C engines
110
- (dimension / window / block iterators) live at samples/caiterator/. */
110
+ (dimension / window / block iterators) were retired in 3.0. */
111
111
 
112
112
  void Init_carray_mathfunc ();
113
113
 
@@ -142,7 +142,7 @@ Init_carray_ext (void)
142
142
  rb_cCScalar = rb_define_class("CScalar", rb_cCArray);
143
143
  rb_cCAView = rb_define_class("CAView", rb_cCArray);
144
144
  /* CAREFUL: CAStride must exist before its subclasses (CARefer /
145
- CABlock / CAField / CARepeat / CAUnboundRepeat / CATranspose /
145
+ CABlock / CAField / CARepeat / CATranspose /
146
146
  CAFarray) are defined or registered. See devel/CAStride.md. */
147
147
  rb_cCAStride = rb_define_class("CAStride", rb_cCAView);
148
148
  rb_cCARefer = rb_define_class("CARefer", rb_cCAStride);
@@ -151,10 +151,6 @@ Init_carray_ext (void)
151
151
  rb_cCASelect = rb_define_class("CASelect", rb_cCAView);
152
152
  rb_cCAObject = rb_define_class("CAObject", rb_cCAView);
153
153
  rb_cCARepeat = rb_define_class("CARepeat", rb_cCAStride);
154
- /* CAUnboundRepeat: a CAStride with stride-0 axes on each `*` position
155
- and sized (matching parent) on each nil position. */
156
- rb_cCAUnboundRepeat = rb_define_class("CAUnboundRepeat", rb_cCAStride);
157
-
158
154
  rb_cCArrayMask = rb_define_class("CArrayMask", rb_cCArray);
159
155
  rb_cCAStrideMask = rb_define_class("CAStrideMask", rb_cCAStride);
160
156
  rb_cCAReferMask = rb_define_class("CAReferMask", rb_cCARefer);
@@ -162,7 +158,6 @@ Init_carray_ext (void)
162
158
  rb_cCAFieldMask = rb_define_class("CAFieldMask", rb_cCAField);
163
159
  rb_cCASelectMask = rb_define_class("CASelectMask", rb_cCASelect);
164
160
  rb_cCARepeatMask = rb_define_class("CARepeatMask", rb_cCARepeat);
165
- rb_cCAUnboundRepeatMask = rb_define_class("CAUnboundRepeatMask", rb_cCAUnboundRepeat);
166
161
 
167
162
  /* -- Exception class -- */
168
163
 
@@ -175,6 +170,10 @@ Init_carray_ext (void)
175
170
 
176
171
  /* -- version -- */
177
172
 
173
+ /* The flags this build's arithmetic was compiled with. Anything that
174
+ computes what these kernels compute, somewhere other than in them, has
175
+ to be built the same way to arrive at the same answer. */
176
+ rb_define_const(rb_cCArray, "BUILD_FLAGS", rb_str_new2(CA_BUILD_FLAGS));
178
177
  rb_define_const(rb_cCArray, "VERSION", rb_str_new2(CA_VERSION));
179
178
  rb_define_const(rb_cCArray, "VERSION_CODE", INT2NUM(CA_VERSION_CODE));
180
179
  rb_define_const(rb_cCArray, "VERSION_MAJOR", INT2NUM(CA_VERSION_MAJOR));
@@ -184,8 +183,14 @@ Init_carray_ext (void)
184
183
 
185
184
  /* -- system -- */
186
185
  rb_define_const(rb_cObject, "CA_RANK_MAX", INT2NUM(CA_RANK_MAX));
187
- CA_NIL = rb_funcall(rb_cObject, rb_intern("new"), 0);
188
- rb_define_const(rb_cObject, "CA_NIL", CA_NIL);
186
+ /* Sentinel meaning "the caller did not give this argument". It is
187
+ distinct from nil because nil is itself a legal fill value (a
188
+ CA_OBJECT array can be filled with nil), so nil cannot mark
189
+ absence. C code only ever compares against it; the value is never
190
+ read and must never be passed in from Ruby. The constant exists to
191
+ anchor the object against the GC. */
192
+ CA_UNSPECIFIED = rb_funcall(rb_cObject, rb_intern("new"), 0);
193
+ rb_define_const(rb_cCArray, "UNSPECIFIED", CA_UNSPECIFIED);
189
194
 
190
195
  #ifdef HAVE_COMPLEX_H
191
196
  /* @private */
@@ -320,7 +325,6 @@ Init_carray_ext (void)
320
325
  Init_ca_obj_meld();
321
326
  Init_ca_obj_transpose();
322
327
  Init_ca_obj_repeat();
323
- Init_ca_obj_unbound_repeat();
324
328
  Init_ca_obj_reduce();
325
329
  Init_ca_obj_field();
326
330
  Init_ca_obj_fake();
@@ -351,9 +355,7 @@ Init_carray_ext (void)
351
355
  /* CAIterator is the form-only base of the 3.0 iterator family; its shared
352
356
  accessors are plain Ruby attr_readers (lib/carray/iterator.rb). The C
353
357
  reduction iterators subclass it and carray_access.c constructs
354
- CASlabIterator by name, so the class object must exist here. The retired
355
- 2.0 dispatch engines (dimension / window / block C iterators + the
356
- kernel_at_addr base) are preserved at samples/caiterator/. */
358
+ CASlabIterator by name, so the class object must exist here. */
357
359
  rb_cCAIterator = rb_define_class("CAIterator", rb_cObject);
358
360
 
359
361
  Init_ca_group_iter(); /* devel/MEMO_AXIS_GROUP.md — axis-group [] surface
data/ext/version.h CHANGED
@@ -1,6 +1,6 @@
1
- #define CA_VERSION "3.0.0"
2
- #define CA_VERSION_CODE 300
1
+ #define CA_VERSION "3.0.1"
2
+ #define CA_VERSION_CODE 301
3
3
  #define CA_VERSION_MAJOR 3
4
4
  #define CA_VERSION_MINOR 0
5
- #define CA_VERSION_TEENY 0
6
- #define CA_VERSION_DATE "2026/08/25"
5
+ #define CA_VERSION_TEENY 1
6
+ #define CA_VERSION_DATE "2026/09/07"
@@ -62,6 +62,24 @@ class CArray
62
62
  autoload :TableMethods, "carray/table"
63
63
  end
64
64
 
65
+ # ---- Fusion (reading a lazy expression as a plan) --------------------------
66
+ #
67
+ # Nothing in the core asks for a plan -- CArray can always walk the view --
68
+ # so this loads only when something that compiles one asks.
69
+
70
+ class CArray
71
+ autoload :Fusion, "carray/fusion"
72
+ end
73
+
74
+ # ---- Reading a block as an expression (CArray.fuse) -------------------------
75
+ #
76
+ # Only `fuse` needs this, and only when it is called, so the parser it uses
77
+ # is not loaded by `require "carray"`.
78
+
79
+ class CArray
80
+ autoload :FuseSource, "carray/fuse_source"
81
+ end
82
+
65
83
  # ---- Iterators (class / slab) ----------------------------------------------
66
84
 
67
85
  autoload :CASlabIterator, "carray/slab_iterator"
@@ -172,6 +190,7 @@ end
172
190
  # (2) Single-feature method files under carray/methods/
173
191
  # ============================================================================
174
192
 
193
+
175
194
  class CArray
176
195
  # composition family (eager ragged list -> one array)
177
196
  autoload_method "self.concatenate", "carray/methods/composition"
@@ -15,6 +15,7 @@
15
15
  # monkey patching is avoided -- only classes that explicitly `extend
16
16
  # AutoloadMethodExtension` gain the method.
17
17
 
18
+ # @private
18
19
  module AutoloadMethodExtension
19
20
  # @!visibility private
20
21
  def autoload_method (method, library)
@@ -31,7 +32,7 @@ module AutoloadMethodExtension
31
32
  private
32
33
 
33
34
  def autoload_define (target, name, library, original_spec)
34
- target.send(:define_method, name) do |*args, **kwargs, &block|
35
+ target.define_method(name) do |*args, **kwargs, &block|
35
36
  begin
36
37
  require library
37
38
  rescue LoadError
@@ -401,8 +401,8 @@ class CAGroupIterator
401
401
  end
402
402
 
403
403
  # @overload cummax(axis: :group)
404
- # Per-group inclusive running maximum, source-shaped, in the source dtype
405
- # (extrema do not grow magnitude, so the dtype is preserved). The first
404
+ # Per-group inclusive running maximum, source-shaped, in the source data type
405
+ # (extrema do not grow magnitude, so the data type is preserved). The first
406
406
  # member of a group emits its own value. Without :group delegates to value
407
407
  # cummax.
408
408
  # @return [CArray]
@@ -411,7 +411,7 @@ class CAGroupIterator
411
411
  end
412
412
 
413
413
  # @overload cummin(axis: :group)
414
- # Per-group inclusive running minimum, source-shaped, in the source dtype.
414
+ # Per-group inclusive running minimum, source-shaped, in the source data type.
415
415
  # Without :group delegates to value cummin.
416
416
  # @return [CArray]
417
417
  def cummin (**kw)
@@ -612,7 +612,7 @@ class CAGroupIterator
612
612
  unless value.respond_to?(op)
613
613
  raise NotImplementedError, "axis_group scan: value has no ##{op}"
614
614
  end
615
- return value.send(op)
615
+ return value.public_send(op)
616
616
  end
617
617
  unless fused.empty?
618
618
  raise ArgumentError,
@@ -630,7 +630,7 @@ class CAGroupIterator
630
630
  # operation, and remains a follow-up.
631
631
  def order_stat (op, args, kw)
632
632
  has_group, fused = AxisGroup.parse_axis(kw[:axis])
633
- return value.send(op, *args) unless has_group
633
+ return value.public_send(op, *args) unless has_group
634
634
  unless fused.empty?
635
635
  raise NotImplementedError,
636
636
  "axis_group: folding a band into an order statistic (axis: [:group, k]) " \
@@ -651,13 +651,13 @@ class CAGroupIterator
651
651
  ccat, kdims, gslots, bslots, gaxes = composite_layout
652
652
  nout = (op == :quantile) ? 5 : 1
653
653
  if bslots.empty?
654
- res = value.group_by_category(ccat).send(op, *args)
654
+ res = value.group_by_category(ccat).public_send(op, *args)
655
655
  return nout == 1 ? res.reshape(*kdims) : res.map { |r| r.reshape(*kdims) }
656
656
  end
657
657
  out_shape = spec.slot_meta.map { |m| m[:kind] == :group ? m[:k] : m[:len] }
658
658
  outs = Array.new(nout) { CArray.float64(*out_shape) }
659
659
  each_band_block(ccat, gslots, bslots, gaxes) do |_vi, out_idx, _co, gi|
660
- result = gi.send(op, *args)
660
+ result = gi.public_send(op, *args)
661
661
  if nout == 1
662
662
  outs[0][*out_idx] = result.reshape(*kdims)
663
663
  else
data/lib/carray/basics.rb CHANGED
@@ -30,27 +30,26 @@ class CArray
30
30
  # shift as other axes are inserted (e.g. `insert_axis(0, 1, 2)`
31
31
  # puts one axis before each of the first three source axes).
32
32
  #
33
- # Each inserted axis takes one of three forms, chosen by its
34
- # `repeat` value: `1` (or `nil`) for a plain size-1 axis, an
35
- # Integer `N > 1` for a read-only bound repeat view, or `:*` for
36
- # an unbound repeat that binds on assignment. `repeat` is either
37
- # a single value applied to every inserted axis, or an Array
38
- # giving one value per position.
33
+ # Each inserted axis takes one of two forms, chosen by its
34
+ # `repeat` value: `1` (or `nil`) for a plain size-1 axis, or an
35
+ # Integer `N > 1` for a read-only bound repeat view. `repeat` is
36
+ # either a single value applied to every inserted axis, or an
37
+ # Array giving one value per position.
39
38
  #
40
- # The everyday way to add an axis is the `:_` / `:*` indexer
41
- # when the shape is known at the call site; `insert_axis` is for
42
- # library code that builds the axis list programmatically.
39
+ # The everyday way to add an axis is the `:_` indexer when the
40
+ # shape is known at the call site; `insert_axis` is for library
41
+ # code that builds the axis list programmatically.
43
42
  #
44
43
  # @param positions [Array<Integer>] source-frame positions of the
45
44
  # new axes.
46
- # @param repeat [Integer, Symbol, Array, nil] repeat spec applied
47
- # to each inserted axis.
45
+ # @param repeat [Integer, Array, nil] repeat spec applied to each
46
+ # inserted axis.
48
47
  # @return [CArray] view with the new axes inserted.
49
48
  # @example
50
49
  # a = CArray.int32(3, 4).seq
51
50
  # a.insert_axis(0) # shape (1, 3, 4)
52
51
  # a.insert_axis(1, repeat: 5) # shape (3, 5, 4)
53
- # a.insert_axis(0, 1, repeat: [:*, 3]) # mixed unbound + bound
52
+ # a.insert_axis(0, 1, repeat: [1, 3]) # size-1 then bound
54
53
  def insert_axis (*positions, repeat: nil)
55
54
  flat = positions.flatten
56
55
  if flat.empty?
@@ -81,57 +80,31 @@ class CArray
81
80
  Array.new(flat.length, repeat)
82
81
  end
83
82
 
84
- # Validate each value. A positive Integer or :* only; nil is not a
85
- # valid per-axis repeat.
86
- reps.each do |r|
87
- case r
88
- when Integer
89
- raise ArgumentError, "insert_axis: repeat count must be >= 1" if r < 1
90
- when :*
91
- # ok
92
- else
93
- raise ArgumentError,
94
- "insert_axis: repeat must be a positive Integer or :*, got #{r.inspect}"
95
- end
83
+ # Validate each value. A positive Integer only; nil is not a valid
84
+ # per-axis repeat.
85
+ reps.each do |r|
86
+ unless r.is_a?(Integer)
87
+ raise ArgumentError,
88
+ "insert_axis: repeat must be a positive Integer, got #{r.inspect}"
96
89
  end
90
+ raise ArgumentError, "insert_axis: repeat count must be >= 1" if r < 1
91
+ end
97
92
 
98
- # Final output layout: stable order by (gap, argument index) keeps
99
- # same-gap axes in argument order; the k-th inserted axis lands at output
100
- # position gap + k. This output position is only used to drive the
101
- # output-shaped view constructors (broadcast_to / unbound_repeat); the
102
- # actual insertion always goes through the source-frame primitive below.
103
- order = (0...flat.length).sort_by { |i| [gaps[i], i] }
104
- final = {}
105
- order.each_with_index { |i, k| final[i] = gaps[i] + k }
106
-
107
- unbound_args = order.select { |i| reps[i] == :* }
108
- concrete_args = order.reject { |i| reps[i] == :* } # in output order
109
-
110
- # Stage 1: insert the concrete (size-1 / bound) axes by their source
111
- # gaps, then grow the bound ones with broadcast_to.
112
- inter = self
113
- unless concrete_args.empty?
114
- inter = __insert_axis_size1__(*concrete_args.map { |i| gaps[i] })
115
- if concrete_args.any? { |i| reps[i].is_a?(Integer) && reps[i] > 1 }
116
- shp = inter.shape
117
- concrete_args.each do |i|
118
- r = reps[i]
119
- next unless r.is_a?(Integer) && r > 1
120
- # intermediate position = final position minus unbound axes before it
121
- shp[final[i] - unbound_args.count { |u| final[u] < final[i] }] = r
122
- end
123
- inter = inter.broadcast_to(*shp)
124
- end
125
- end
93
+ # Final output layout: stable order by (gap, argument index) keeps
94
+ # same-gap axes in argument order; the k-th inserted axis lands at output
95
+ # position gap + k. This output position only drives broadcast_to; the
96
+ # insertion itself always goes through the source-frame primitive.
97
+ order = (0...flat.length).sort_by { |i| [gaps[i], i] }
98
+ final = {}
99
+ order.each_with_index { |i, k| final[i] = gaps[i] + k }
126
100
 
127
- return inter if unbound_args.empty?
101
+ inter = __insert_axis_size1__(*order.map { |i| gaps[i] })
102
+ return inter unless order.any? { |i| reps[i] > 1 }
128
103
 
129
- # Stage 2: add the unbound axes over the final ndim (`:*` at unbound
130
- # positions, nil consumes one stage-1 axis in order).
131
- pattern = Array.new(ndim + flat.length, nil)
132
- unbound_args.each { |i| pattern[final[i]] = :* }
133
- inter.unbound_repeat(*pattern)
134
- end
104
+ shp = inter.shape
105
+ order.each { |i| shp[final[i]] = reps[i] if reps[i] > 1 }
106
+ inter.broadcast_to(*shp)
107
+ end
135
108
 
136
109
  # @overload drop_axis
137
110
  # Returns a view of `self` with every size-1 axis dropped.
@@ -246,6 +219,33 @@ class CArray
246
219
 
247
220
  alias cmp <=>
248
221
 
222
+ # @!group Elementwise math
223
+
224
+ # Returns `[quotient, remainder]` element-wise.
225
+ #
226
+ # The quotient is floored toward -inf, so `q * other + r == self` holds
227
+ # for every sign combination -- the pair Ruby's `Integer#divmod` and
228
+ # `Float#divmod` return. For integers that is `self / other` unchanged;
229
+ # for floats `/` is true division, so the quotient is floored here.
230
+ #
231
+ # @param other [CArray, Numeric] divisor.
232
+ # @return [Array<CArray>] the floored quotient and the remainder.
233
+ # @raise [ArgumentError] for complex arrays, which have no ordering to
234
+ # floor toward.
235
+ # @example
236
+ # CA_INT32([-7]).divmod(3) # => [-3, 2]
237
+ # CA_DOUBLE([-7.0]).divmod(3.0) # => [-3.0, 2.0]
238
+ def divmod(other)
239
+ if complex?
240
+ raise ArgumentError, "divmod is not defined for complex arrays"
241
+ end
242
+ q = self / other
243
+ q = q.floor unless q.integer?
244
+ [q, self % other]
245
+ end
246
+
247
+ # @!endgroup
248
+
249
249
  # @overload clip(min, max = nil, fill_value = nil, lfill: nil, ufill: nil)
250
250
  # Returns `self` with every element clamped to `[min, max]`.
251
251
  #
@@ -68,16 +68,16 @@ class CArray
68
68
  # use `CArray#histogram`.
69
69
  class BincountND
70
70
 
71
- # @overload initialize(lengths:, fiber_shape: [], weights_dtype: nil)
71
+ # @overload initialize(lengths:, fiber_shape: [], weights_data_type: nil)
72
72
  # Allocates a new N-D discrete bincount accumulator.
73
73
  # @param lengths [Array<Integer>] per-dimension label ranges;
74
74
  # each must be `>= 1`.
75
75
  # @param fiber_shape [Array<Integer>] shape of the leading
76
76
  # axes.
77
- # @param weights_dtype [Symbol, nil] `data_type` for weighted
77
+ # @param weights_data_type [Symbol, nil] `data_type` for weighted
78
78
  # accumulators; `nil` for pure counts (int64).
79
79
  # @return [BincountND]
80
- def initialize (lengths:, fiber_shape: [], weights_dtype: nil)
80
+ def initialize (lengths:, fiber_shape: [], weights_data_type: nil)
81
81
  @lengths = lengths.map(&:to_i)
82
82
  raise ArgumentError, "lengths must be a non-empty list" if @lengths.empty?
83
83
  @lengths.each_with_index do |l, k|
@@ -85,11 +85,11 @@ class CArray
85
85
  end
86
86
  @m = @lengths.size
87
87
  @fiber_shape = fiber_shape.map(&:to_i).freeze
88
- @weighted = !weights_dtype.nil?
89
- @counts_dtype = @weighted ? weights_dtype : :int64
88
+ @weighted = !weights_data_type.nil?
89
+ @counts_data_type = @weighted ? weights_data_type : :int64
90
90
  ext_dims = @lengths.map { |l| l + 1 } # +1: upper overflow cell
91
91
  ext_shape = @fiber_shape + ext_dims
92
- @full_counts = CArray.public_send(@counts_dtype, *ext_shape).fill(0)
92
+ @full_counts = CArray.public_send(@counts_data_type, *ext_shape).fill(0)
93
93
  @sample_axis = nil
94
94
  @channel_axis = nil
95
95
  end
@@ -209,7 +209,7 @@ class CArray
209
209
 
210
210
  if weights
211
211
  raise ArgumentError, "weights given but accumulator is unweighted" unless @weighted
212
- weights = CArray.wrap_readonly(weights, @counts_dtype)
212
+ weights = CArray.wrap_readonly(weights, @counts_data_type)
213
213
  expected_w_shape = chunk.shape.dup
214
214
  expected_w_shape.delete_at(channel_ax)
215
215
  unless weights.shape == expected_w_shape
@@ -297,7 +297,7 @@ class CArray
297
297
  result = self.class.send(:new,
298
298
  lengths: @lengths,
299
299
  fiber_shape: @fiber_shape,
300
- weights_dtype: @weighted ? @counts_dtype : nil)
300
+ weights_data_type: @weighted ? @counts_data_type : nil)
301
301
  rf = result.instance_variable_get(:@full_counts)
302
302
  rf[] = @full_counts + other.full_counts
303
303
  result.instance_variable_set(:@sample_axis, @sample_axis)
@@ -344,14 +344,14 @@ class CArray
344
344
  [sample_ax, channel_ax].sort.reverse.each { |p| fiber_shape.delete_at(p) }
345
345
 
346
346
  # Weighted counts are float64-only (the FLAT bincount coerces weights to the
347
- # counts dtype and the FIBER kernel requires float64 weights/counts), so the
348
- # dtype is fixed here rather than derived from the weights' own dtype.
349
- weights_dtype = (:float64 if weights)
347
+ # counts data type and the FIBER kernel requires float64 weights/counts), so
348
+ # the type is fixed here rather than derived from the weights' own type.
349
+ weights_data_type = (:float64 if weights)
350
350
 
351
351
  h = BincountND.send(:new,
352
352
  lengths: lengths,
353
353
  fiber_shape: fiber_shape,
354
- weights_dtype: weights_dtype)
354
+ weights_data_type: weights_data_type)
355
355
  h.add(self, axis: axis, weights: weights)
356
356
  h
357
357
  end
@@ -19,7 +19,7 @@
19
19
  # is a `block_view` (a CAStride: zero-copy over the source via compose-fold),
20
20
  # reduced over the trailing tile axes by a core reduction and scattered into the
21
21
  # ceil-shaped tile grid. One core reduction per region; the named reductions
22
- # delegate straight to the core, so their dtype / mask / empty (ERI) / epsilon
22
+ # delegate straight to the core, so their data type / mask / empty (ERI) / epsilon
23
23
  # contracts are the core's, unchanged. Because tiles do not overlap, no
24
24
  # padded entity is built (unlike CAWindowIterator) and the interior stays on
25
25
  # the source buffer.
@@ -165,7 +165,7 @@ class CABlockIterator < CAIterator
165
165
  # quantile) which are assembled into that many grids. Boundary regions use a
166
166
  # smaller (unmasked, ragged) block_view whose present cell count is naturally
167
167
  # below a full tile, so `min_count:` marks them UNDEF with no masking. The
168
- # output dtype is seeded from the first region (all regions share it).
168
+ # output data type is seeded from the first region (all regions share it).
169
169
  def assemble
170
170
  outs = nil
171
171
  each_region do |strip_ranges, tiles, out_ranges|
@@ -184,7 +184,7 @@ class CABlockIterator < CAIterator
184
184
 
185
185
  # Fold every region with a single core reduction `op` (the tier-1 shape).
186
186
  def fold (op, **kw)
187
- assemble { |view, _tiles| view.send(op, axis: @tile_axes, **kw) }
187
+ assemble { |view, _tiles| view.public_send(op, axis: @tile_axes, **kw) }
188
188
  end
189
189
 
190
190
  public
@@ -194,14 +194,20 @@ class CABlockIterator < CAIterator
194
194
  # A per-tile fold to one value over the trailing tile axes is exactly a core
195
195
  # per-axis reduction over those axes, so every reduction delegates to
196
196
  # `block_view.<op>(axis: tile_axes, ...)` per region. This inherits the core
197
- # dtype, mask, empty / all-masked (identity vs UNDEF) and epsilon-close
197
+ # data type, mask, empty / all-masked (identity vs UNDEF) and epsilon-close
198
198
  # contracts unchanged. `min_count:` / `fill_value:` pass straight to the core.
199
199
 
200
200
  # @overload sum(min_count: nil, fill_value: nil)
201
201
  # Per-tile sum. @return [CArray] tile-grid shaped
202
+ # @overload accumulate(min_count: nil, fill_value: nil)
203
+ # Per-tile sum kept in the source's own data type, wrapping at its width,
204
+ # as the core `accumulate` does -- `sum` answers in the type the core
205
+ # promotes to (float64 for integers), so a tile count over `uint8` cells
206
+ # stays one byte wide instead of eight.
207
+ # @return [CArray] tile-grid shaped
202
208
  # The rest are analogous: prod / mean / min / max, sample and population
203
209
  # variance / stddev, all / any.
204
- [:sum, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
210
+ [:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
205
211
  :variancep, :stddevp].each do |op|
206
212
  define_method(op) do |min_count: nil, fill_value: nil|
207
213
  kw = {}
@@ -281,7 +287,11 @@ class CABlockIterator < CAIterator
281
287
  # Per-tile position of the maximum (tile-local flat index).
282
288
  # @return [CArray]
283
289
  [:min_index, :max_index].each do |op|
284
- define_method(op) { assemble { |view, _| view.send(op, axis: @tile_axes) } }
290
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
291
+ def #{op}
292
+ assemble { |view, _| view.#{op}(axis: @tile_axes) }
293
+ end
294
+ RUBY
285
295
  end
286
296
 
287
297
  # @overload min_addr
@@ -309,7 +319,7 @@ class CABlockIterator < CAIterator
309
319
  each_region do |strip_ranges, tiles, out_ranges|
310
320
  vview = @source[*strip_ranges].block_view(*tiles)
311
321
  sview = saddr[*strip_ranges].block_view(*tiles)
312
- mi = vview.send(idx_op, axis: @tile_axes) # tile-local flat index
322
+ mi = vview.public_send(idx_op, axis: @tile_axes) # tile-local flat index
313
323
  grid = (0...@sndim).map { |i| vview.shape[i] }
314
324
  cells = tiles.inject(1) { |p, t| p * t }
315
325
  safe = mi.copy
@@ -380,7 +390,7 @@ class CABlockIterator < CAIterator
380
390
  wt = tiles == @sizes ? weights : weights[*tiles.map { |t| 0...t }]
381
391
  wshape = ([1] * @sndim) + tiles
382
392
  # Pass the weights through unchanged, like the window iterator: core wsum /
383
- # wmean own weight/dtype coercion, so do not pre-coerce the weights here.
393
+ # wmean own weight / type coercion, so do not pre-coerce the weights here.
384
394
  wfull = wt.reshape(*wshape).broadcast_to(*view.shape)
385
395
  yield view, wfull
386
396
  end
@@ -516,8 +526,8 @@ class CABlockIterator < CAIterator
516
526
  # reusing the same padded (masked-margin) entity as the iterate escape
517
527
  # hatches, so this is the slow path (a per-tile materialize), and the
518
528
  # out-of-bounds cells of a partial edge tile are dropped from the result.
519
- # cumsum / cumprod -> float64, cummax / cummin preserve the value dtype,
520
- # cumcount -> int64 running count of present cells; the output dtype is seeded
529
+ # cumsum / cumprod -> float64, cummax / cummin preserve the value data type,
530
+ # cumcount -> int64 running count of present cells; the output data type is seeded
521
531
  # from the first tile's scan.
522
532
 
523
533
  # @overload cumsum
@@ -527,10 +537,10 @@ class CABlockIterator < CAIterator
527
537
  # Per-tile inclusive running product (float64), source-shaped.
528
538
  # @return [CArray]
529
539
  # @overload cummax
530
- # Per-tile inclusive running maximum (value dtype), source-shaped.
540
+ # Per-tile inclusive running maximum (value data type), source-shaped.
531
541
  # @return [CArray]
532
542
  # @overload cummin
533
- # Per-tile inclusive running minimum (value dtype), source-shaped.
543
+ # Per-tile inclusive running minimum (value data type), source-shaped.
534
544
  # @return [CArray]
535
545
  # @overload cumcount
536
546
  # Per-tile running count of present cells (int64), source-shaped.
@@ -542,7 +552,7 @@ class CABlockIterator < CAIterator
542
552
  private
543
553
 
544
554
  # Drive a within-tile segment scan: scan each tile flattened row-major and
545
- # scatter back to a source-shaped result. The output dtype is taken from the
555
+ # scatter back to a source-shaped result. The output data type is taken from the
546
556
  # first tile's scan (all tiles share it); the padded margin is UNDEF and the
547
557
  # OOB cells of a partial edge tile are cropped from the result.
548
558
  def block_scan (op)
@@ -552,7 +562,7 @@ class CABlockIterator < CAIterator
552
562
  pgv = nil
553
563
  CArray.each_index(*@shape) do |*g|
554
564
  tile = tgv[*g, *nils]
555
- scanned = tile.copy.reshape(tile.elements).send(op).reshape(*@sizes)
565
+ scanned = tile.copy.reshape(tile.elements).public_send(op).reshape(*@sizes)
556
566
  unless pout
557
567
  pout = CArray.new(scanned.data_type, padded_source.shape)
558
568
  pout[] = UNDEF
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # carray/categorical.rb
4
4
  #
5
- # CACategorical — a categorical dtype: dense integer codes (the storage) plus
5
+ # CACategorical — a categorical data type: dense integer codes (the storage) plus
6
6
  # a label vocabulary. Structurally the same as a pandas Categorical or an
7
7
  # Arrow dictionary: each element is an index into a small set of categories.
8
8
  #
@@ -66,7 +66,7 @@ class CACategorical < CAObject
66
66
  }.freeze
67
67
 
68
68
  # The exclusion sentinel per codes data_type: the all-ones bit pattern, read
69
- # as type-max for an unsigned dtype and as -1 for a signed one. Either way it
69
+ # as type-max for an unsigned data type and as -1 for a signed one. Either way it
70
70
  # is out of every valid [0, k) range and byte-identical to a pandas / Arrow
71
71
  # missing code.
72
72
  SENTINEL = {
@@ -521,7 +521,7 @@ class CArray
521
521
  # Integer / Float keys stay distinct (eql?, so 1 and 1.0 are separate
522
522
  # categories). The discovery path below is reserved for sort_labels (which
523
523
  # reorders the vocabulary, desyncing the appearance-order codes), an explicit
524
- # labels list, and the dtypes the factorize kernel does not take (complex).
524
+ # labels list, and the data types the factorize kernel does not take (complex).
525
525
  if labels.nil? && !sort_labels && (integer? || float? || object? || fixlen? || boolean?)
526
526
  codes, levels = __factorize_appearance__
527
527
  return CACategorical.from_codes(codes, levels.to_a)
@@ -541,7 +541,7 @@ class CArray
541
541
  end
542
542
  end
543
543
 
544
- # Choose a narrow unsigned code dtype, reserving its top value as the
544
+ # Choose a narrow unsigned code type, reserving its top value as the
545
545
  # exclusion sentinel so it never collides with a real code 0..k-1.
546
546
  k = labels_arr.size
547
547
  code_type, sentinel =