carray 3.0.0 → 3.0.2

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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +845 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +33 -33
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_dispatch.c +33 -4
  8. data/ext/ca_axis_group.c +256 -114
  9. data/ext/ca_binop_dispatch.c +5 -6
  10. data/ext/ca_binop_dispatch.h +0 -7
  11. data/ext/ca_categorical_iterator.c +121 -67
  12. data/ext/ca_for_each_element.h +6 -8
  13. data/ext/ca_group_iter.c +1 -1
  14. data/ext/ca_kernel_iterator.c +386 -74
  15. data/ext/ca_kernel_iterator.h +145 -45
  16. data/ext/ca_obj_array.c +62 -20
  17. data/ext/ca_obj_bincmp.c +57 -27
  18. data/ext/ca_obj_binop.c +86 -30
  19. data/ext/ca_obj_block.c +4 -4
  20. data/ext/ca_obj_const_string.c +88 -27
  21. data/ext/ca_obj_face.c +104 -0
  22. data/ext/ca_obj_face.h +48 -1
  23. data/ext/ca_obj_fake.c +11 -0
  24. data/ext/ca_obj_fixlen_string.c +21 -6
  25. data/ext/ca_obj_grid.c +11 -3
  26. data/ext/ca_obj_meld.c +125 -22
  27. data/ext/ca_obj_moncmp.c +33 -14
  28. data/ext/ca_obj_monop.c +19 -7
  29. data/ext/ca_obj_object.c +107 -4
  30. data/ext/ca_obj_record.c +3 -1
  31. data/ext/ca_obj_refer.c +30 -6
  32. data/ext/ca_obj_roll.c +13 -5
  33. data/ext/ca_obj_select.c +49 -34
  34. data/ext/ca_obj_select_axis.c +16 -6
  35. data/ext/ca_obj_shift.c +3 -3
  36. data/ext/ca_obj_stack.c +3 -8
  37. data/ext/ca_obj_stride.c +178 -24
  38. data/ext/ca_obj_string.c +11 -5
  39. data/ext/ca_obj_tile.c +13 -5
  40. data/ext/ca_obj_time.c +3 -1
  41. data/ext/ca_obj_timedelta.c +3 -1
  42. data/ext/ca_obj_transpose.c +1 -1
  43. data/ext/ca_obj_triop.c +62 -23
  44. data/ext/ca_obj_window.c +94 -19
  45. data/ext/ca_op_cmplx64.h +123 -0
  46. data/ext/ca_op_ipower.c +1 -5
  47. data/ext/ca_rng_normal.h +42 -0
  48. data/ext/ca_rng_xoshiro256pp.h +105 -0
  49. data/ext/ca_sort_kernels.h +5 -5
  50. data/ext/ca_sweep_engine.c +362 -155
  51. data/ext/ca_sweep_engine.h +39 -9
  52. data/ext/ca_transform_common.c +7 -0
  53. data/ext/carray.h +148 -132
  54. data/ext/carray_access.c +83 -49
  55. data/ext/carray_address_basis.c +590 -0
  56. data/ext/carray_bincount.c +8 -8
  57. data/ext/carray_broadcast.c +102 -9
  58. data/ext/carray_build_flags.h +3 -0
  59. data/ext/carray_call_cfunc.c +2010 -335
  60. data/ext/carray_call_cfunc.h +153 -0
  61. data/ext/carray_cast.c +184 -72
  62. data/ext/carray_conversion.c +22 -22
  63. data/ext/carray_copy.c +67 -32
  64. data/ext/carray_core.c +140 -13
  65. data/ext/carray_count.c +10 -11
  66. data/ext/carray_element.c +7 -7
  67. data/ext/carray_factorize.c +72 -51
  68. data/ext/carray_hold.c +1 -1
  69. data/ext/carray_index_classifier.c +7 -21
  70. data/ext/carray_internal.h +36 -3
  71. data/ext/carray_kernels_bincmp.c +1 -0
  72. data/ext/carray_kernels_binop.c +320 -298
  73. data/ext/carray_kernels_init.c +1095 -0
  74. data/ext/carray_kernels_map.c +1 -0
  75. data/ext/carray_kernels_moncmp.c +1 -0
  76. data/ext/carray_kernels_monop.c +412 -411
  77. data/ext/carray_kernels_reduce_aggregate.c +256 -87
  78. data/ext/carray_kernels_reduce_boolean.c +1 -0
  79. data/ext/carray_kernels_reduce_cumulative.c +324 -54
  80. data/ext/carray_kernels_reduce_extreme.c +605 -58
  81. data/ext/carray_kernels_reduce_variance.c +1 -0
  82. data/ext/carray_kernels_scan.c +5 -4
  83. data/ext/carray_kernels_search.c +187 -106
  84. data/ext/carray_kernels_sort.c +1 -0
  85. data/ext/carray_kernels_triop.c +1 -0
  86. data/ext/carray_lazy.c +175 -5
  87. data/ext/carray_loop.c +7 -1
  88. data/ext/carray_mask.c +29 -13
  89. data/ext/carray_median_percentile.c +56 -1
  90. data/ext/carray_memory_view.c +46 -21
  91. data/ext/carray_operator.c +50 -53
  92. data/ext/carray_order.c +1 -1
  93. data/ext/carray_partition.c +4 -4
  94. data/ext/carray_random.c +391 -47
  95. data/ext/carray_scatter.c +1 -1
  96. data/ext/carray_slab.c +13 -0
  97. data/ext/carray_sort.c +22 -24
  98. data/ext/carray_sort_kernel.c +10 -10
  99. data/ext/carray_test.c +1 -1
  100. data/ext/extconf.rb +18 -0
  101. data/ext/mk_call_cfunc.rb +323 -145
  102. data/ext/mkkernel.rb +651 -129
  103. data/ext/ruby_carray.c +28 -17
  104. data/ext/version.h +4 -4
  105. data/lib/carray/autoload_carray.rb +24 -3
  106. data/lib/carray/autoload_method_extension.rb +14 -1
  107. data/lib/carray/axis_group.rb +84 -7
  108. data/lib/carray/basics.rb +63 -59
  109. data/lib/carray/bincount_nd.rb +12 -12
  110. data/lib/carray/block_iterator.rb +111 -25
  111. data/lib/carray/categorical.rb +154 -37
  112. data/lib/carray/categorical_iterator.rb +300 -151
  113. data/lib/carray/conditional.rb +14 -14
  114. data/lib/carray/const_string.rb +131 -27
  115. data/lib/carray/construct.rb +61 -0
  116. data/lib/carray/core_extensions.rb +62 -5
  117. data/lib/carray/data_type_extension.rb +21 -1
  118. data/lib/carray/data_type_limits.rb +91 -0
  119. data/lib/carray/fixlen_string.rb +1 -1
  120. data/lib/carray/frame/concat.rb +3 -3
  121. data/lib/carray/frame/convert.rb +1 -1
  122. data/lib/carray/frame/csv_parser.rb +11 -4
  123. data/lib/carray/frame/frame.rb +85 -21
  124. data/lib/carray/frame/group.rb +36 -3
  125. data/lib/carray/frame/io.rb +224 -15
  126. data/lib/carray/frame/records.rb +18 -4
  127. data/lib/carray/frame/sort.rb +1 -1
  128. data/lib/carray/frame/verbs.rb +32 -12
  129. data/lib/carray/fuse_source.rb +123 -0
  130. data/lib/carray/fusion.rb +218 -0
  131. data/lib/carray/histogram.rb +16 -16
  132. data/lib/carray/inspect.rb +43 -16
  133. data/lib/carray/iterator.rb +147 -3
  134. data/lib/carray/lazy.rb +88 -73
  135. data/lib/carray/mask_gap_fill.rb +3 -1
  136. data/lib/carray/meld_reduce.rb +2 -2
  137. data/lib/carray/methods/align_addr.rb +1 -1
  138. data/lib/carray/methods/composition.rb +1 -1
  139. data/lib/carray/methods/discovery_along.rb +74 -0
  140. data/lib/carray/methods/factorize.rb +50 -0
  141. data/lib/carray/methods/is_in.rb +25 -14
  142. data/lib/carray/methods/locate_addr.rb +81 -3
  143. data/lib/carray/methods/mask_duplicates.rb +36 -2
  144. data/lib/carray/methods/meshgrid.rb +4 -5
  145. data/lib/carray/methods/mode.rb +2 -2
  146. data/lib/carray/methods/nunique.rb +23 -2
  147. data/lib/carray/methods/repeat.rb +110 -0
  148. data/lib/carray/methods/snap.rb +7 -2
  149. data/lib/carray/methods/unique.rb +44 -4
  150. data/lib/carray/methods/value_counts.rb +2 -2
  151. data/lib/carray/rng.rb +86 -0
  152. data/lib/carray/runtime.rb +0 -19
  153. data/lib/carray/slab_iterator.rb +77 -19
  154. data/lib/carray/string_operation_extension.rb +10 -6
  155. data/lib/carray/time.rb +906 -491
  156. data/lib/carray/window_iterator.rb +421 -27
  157. data/lib/carray.rb +7 -5
  158. data/yard-stubs/ca_obj_array.rb +385 -0
  159. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  160. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  161. data/yard-stubs/ca_obj_block.rb +68 -0
  162. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  163. data/yard-stubs/ca_obj_fake.rb +31 -0
  164. data/yard-stubs/ca_obj_farray.rb +32 -0
  165. data/yard-stubs/ca_obj_field.rb +45 -0
  166. data/yard-stubs/ca_obj_grid.rb +35 -0
  167. data/yard-stubs/ca_obj_refer.rb +72 -0
  168. data/yard-stubs/ca_obj_roll.rb +45 -0
  169. data/yard-stubs/ca_obj_shift.rb +43 -0
  170. data/yard-stubs/ca_obj_stride.rb +181 -0
  171. data/yard-stubs/ca_obj_tile.rb +29 -0
  172. data/yard-stubs/ca_obj_transpose.rb +40 -0
  173. data/yard-stubs/ca_obj_window.rb +57 -0
  174. data/yard-stubs/carray_access.rb +131 -0
  175. data/yard-stubs/carray_attribute.rb +246 -0
  176. data/yard-stubs/carray_broadcast.rb +37 -0
  177. data/yard-stubs/carray_cast.rb +489 -0
  178. data/yard-stubs/carray_class.rb +65 -0
  179. data/yard-stubs/carray_conversion.rb +76 -0
  180. data/yard-stubs/carray_copy.rb +79 -0
  181. data/yard-stubs/carray_core.rb +34 -0
  182. data/yard-stubs/carray_count.rb +84 -0
  183. data/yard-stubs/carray_element.rb +108 -0
  184. data/yard-stubs/carray_generate.rb +66 -0
  185. data/yard-stubs/carray_lazy.rb +228 -0
  186. data/yard-stubs/carray_loop.rb +140 -0
  187. data/yard-stubs/carray_mask.rb +259 -0
  188. data/yard-stubs/carray_math.rb +1615 -0
  189. data/yard-stubs/carray_mathfunc.rb +45 -0
  190. data/yard-stubs/carray_median_percentile.rb +103 -0
  191. data/yard-stubs/carray_memory_view.rb +163 -0
  192. data/yard-stubs/carray_order.rb +252 -0
  193. data/yard-stubs/carray_random.rb +89 -0
  194. data/yard-stubs/carray_scatter.rb +106 -0
  195. data/yard-stubs/carray_slab.rb +59 -0
  196. data/yard-stubs/carray_sort.rb +165 -0
  197. data/yard-stubs/carray_test.rb +85 -0
  198. data/yard-stubs/carray_undef.rb +64 -0
  199. data/yard-stubs/carray_utils.rb +97 -0
  200. data/yard-stubs/ruby_carray.rb +193 -0
  201. metadata +67 -10
  202. 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
 
@@ -121,6 +121,13 @@ void Init_ca_kernel_iterator ();
121
121
 
122
122
  void Init_carray_slab ();
123
123
 
124
+ void Init_carray_address_basis (); /* CArray::AddressBasis: pointer + byte
125
+ strides lent for the length of a block,
126
+ for code that addresses cells itself.
127
+ A runtime facility at the ca_attach
128
+ layer, not a user surface; see
129
+ guides/devel/21_address_basis.md */
130
+
124
131
  void
125
132
  Init_carray_ext (void)
126
133
  {
@@ -142,7 +149,7 @@ Init_carray_ext (void)
142
149
  rb_cCScalar = rb_define_class("CScalar", rb_cCArray);
143
150
  rb_cCAView = rb_define_class("CAView", rb_cCArray);
144
151
  /* CAREFUL: CAStride must exist before its subclasses (CARefer /
145
- CABlock / CAField / CARepeat / CAUnboundRepeat / CATranspose /
152
+ CABlock / CAField / CARepeat / CATranspose /
146
153
  CAFarray) are defined or registered. See devel/CAStride.md. */
147
154
  rb_cCAStride = rb_define_class("CAStride", rb_cCAView);
148
155
  rb_cCARefer = rb_define_class("CARefer", rb_cCAStride);
@@ -151,10 +158,6 @@ Init_carray_ext (void)
151
158
  rb_cCASelect = rb_define_class("CASelect", rb_cCAView);
152
159
  rb_cCAObject = rb_define_class("CAObject", rb_cCAView);
153
160
  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
161
  rb_cCArrayMask = rb_define_class("CArrayMask", rb_cCArray);
159
162
  rb_cCAStrideMask = rb_define_class("CAStrideMask", rb_cCAStride);
160
163
  rb_cCAReferMask = rb_define_class("CAReferMask", rb_cCARefer);
@@ -162,7 +165,6 @@ Init_carray_ext (void)
162
165
  rb_cCAFieldMask = rb_define_class("CAFieldMask", rb_cCAField);
163
166
  rb_cCASelectMask = rb_define_class("CASelectMask", rb_cCASelect);
164
167
  rb_cCARepeatMask = rb_define_class("CARepeatMask", rb_cCARepeat);
165
- rb_cCAUnboundRepeatMask = rb_define_class("CAUnboundRepeatMask", rb_cCAUnboundRepeat);
166
168
 
167
169
  /* -- Exception class -- */
168
170
 
@@ -175,6 +177,10 @@ Init_carray_ext (void)
175
177
 
176
178
  /* -- version -- */
177
179
 
180
+ /* The flags this build's arithmetic was compiled with. Anything that
181
+ computes what these kernels compute, somewhere other than in them, has
182
+ to be built the same way to arrive at the same answer. */
183
+ rb_define_const(rb_cCArray, "BUILD_FLAGS", rb_str_new2(CA_BUILD_FLAGS));
178
184
  rb_define_const(rb_cCArray, "VERSION", rb_str_new2(CA_VERSION));
179
185
  rb_define_const(rb_cCArray, "VERSION_CODE", INT2NUM(CA_VERSION_CODE));
180
186
  rb_define_const(rb_cCArray, "VERSION_MAJOR", INT2NUM(CA_VERSION_MAJOR));
@@ -184,8 +190,14 @@ Init_carray_ext (void)
184
190
 
185
191
  /* -- system -- */
186
192
  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);
193
+ /* Sentinel meaning "the caller did not give this argument". It is
194
+ distinct from nil because nil is itself a legal fill value (a
195
+ CA_OBJECT array can be filled with nil), so nil cannot mark
196
+ absence. C code only ever compares against it; the value is never
197
+ read and must never be passed in from Ruby. The constant exists to
198
+ anchor the object against the GC. */
199
+ CA_UNSPECIFIED = rb_funcall(rb_cObject, rb_intern("new"), 0);
200
+ rb_define_const(rb_cCArray, "UNSPECIFIED", CA_UNSPECIFIED);
189
201
 
190
202
  #ifdef HAVE_COMPLEX_H
191
203
  /* @private */
@@ -305,7 +317,7 @@ Init_carray_ext (void)
305
317
  Init_ca_obj_refer();
306
318
  Init_ca_obj_stride(); /* CAREFUL: must precede CAStride subclasses
307
319
  (farray / block / transpose / repeat /
308
- unbound_repeat / field). */
320
+ field). */
309
321
  Init_ca_obj_farray();
310
322
  Init_ca_obj_block();
311
323
  Init_ca_obj_select();
@@ -320,7 +332,6 @@ Init_carray_ext (void)
320
332
  Init_ca_obj_meld();
321
333
  Init_ca_obj_transpose();
322
334
  Init_ca_obj_repeat();
323
- Init_ca_obj_unbound_repeat();
324
335
  Init_ca_obj_reduce();
325
336
  Init_ca_obj_field();
326
337
  Init_ca_obj_fake();
@@ -351,9 +362,7 @@ Init_carray_ext (void)
351
362
  /* CAIterator is the form-only base of the 3.0 iterator family; its shared
352
363
  accessors are plain Ruby attr_readers (lib/carray/iterator.rb). The C
353
364
  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/. */
365
+ CASlabIterator by name, so the class object must exist here. */
357
366
  rb_cCAIterator = rb_define_class("CAIterator", rb_cObject);
358
367
 
359
368
  Init_ca_group_iter(); /* devel/MEMO_AXIS_GROUP.md — axis-group [] surface
@@ -371,5 +380,7 @@ Init_carray_ext (void)
371
380
  Init_ca_kernel_iterator();
372
381
 
373
382
  Init_carray_slab();
383
+
384
+ Init_carray_address_basis();
374
385
  }
375
386
 
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.2"
2
+ #define CA_VERSION_CODE 302
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 2
6
+ #define CA_VERSION_DATE "2026/09/24"
@@ -30,8 +30,9 @@
30
30
 
31
31
  class CArray
32
32
  inspect_rb = "carray/inspect"
33
- autoload_method "inspect", inspect_rb
34
- autoload_method "source_code", inspect_rb
33
+ autoload_method "inspect", inspect_rb
34
+ autoload_method "inspect_full", inspect_rb
35
+ autoload_method "source_code", inspect_rb
35
36
  end
36
37
 
37
38
  # ---- Serializer / Marshal / dump / load ------------------------------------
@@ -42,7 +43,6 @@ class CArray
42
43
  autoload_method "self.save", serialize_rb
43
44
  autoload_method "self.load", serialize_rb
44
45
  autoload_method "self.dump", serialize_rb
45
- autoload_method "self.load_from_file", serialize_rb
46
46
  autoload_method "marshal_dump", serialize_rb
47
47
  autoload_method "marshal_load", serialize_rb
48
48
  end
@@ -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"
@@ -182,11 +201,13 @@ class CArray
182
201
  autoload_method "self.meshgrid", "carray/methods/meshgrid"
183
202
 
184
203
  autoload_method "bincount", "carray/methods/bincount"
204
+ autoload_method "repeat", "carray/methods/repeat"
185
205
  autoload_method "self.broadcast", "carray/methods/broadcast"
186
206
  autoload_method "gather_nd", "carray/methods/gather_nd"
187
207
  autoload_method "put_nd", "carray/methods/gather_nd"
188
208
  autoload_method "mask_duplicates", "carray/methods/mask_duplicates"
189
209
  autoload_method "unique", "carray/methods/unique"
210
+ autoload_method "factorize", "carray/methods/factorize"
190
211
  autoload_method "is_in", "carray/methods/is_in"
191
212
  autoload_method "intersection", "carray/methods/is_in"
192
213
  autoload_method "difference", "carray/methods/is_in"
@@ -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,14 +32,26 @@ 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
+ stub = nil
36
+ target.define_method(name) do |*args, **kwargs, &block|
35
37
  begin
36
38
  require library
37
39
  rescue LoadError
38
40
  raise "error in autoloading '#{library}' hooked by method " \
39
41
  "'#{original_spec}', check gem installation."
40
42
  end
43
+ # The require is supposed to have replaced this stub with the real
44
+ # definition. If it has not, the method does not exist anywhere, and
45
+ # forwarding would land straight back here and keep doing so until the
46
+ # stack gave out -- naming neither the method nor the library. Say
47
+ # which, once.
48
+ if target.instance_method(name) == stub
49
+ raise NoMethodError,
50
+ "'#{original_spec}' is registered for autoload from " \
51
+ "'#{library}', but that library defines no such method"
52
+ end
41
53
  send(name, *args, **kwargs, &block)
42
54
  end
55
+ stub = target.instance_method(name)
43
56
  end
44
57
  end
@@ -308,7 +308,72 @@ end
308
308
  # / min / max / variance / stddev / variancep / stddevp / count /
309
309
  # count_not_masked / all / any) bind in C to one driver; the rest of the common
310
310
  # iterator surface that composes cheaply from those is added here.
311
+ # CAGroupIterator subclasses CAIterator, but the class itself is created in
312
+ # C, so the reopening below carries no superclass for YARD to read.
313
+ # @!parse class CAGroupIterator < CAIterator; end
311
314
  class CAGroupIterator
315
+ # The shape one value per piece comes back in: a group slot contributes its
316
+ # category count, a band slot its length, in slot order. The base declares
317
+ # these and every other member answers them; this one inherited the readers
318
+ # without anything ever setting the ivars, so it answered nil -- which the
319
+ # base's own rule calls out as the one thing a member must not do ("a clean
320
+ # NotImplementedError, never a wrong answer").
321
+ def shape
322
+ spec.slot_meta.map { |m| m[:kind] == :group ? m[:k] : m[:len] }
323
+ end
324
+ alias dim shape
325
+
326
+ def ndim
327
+ spec.nslots
328
+ end
329
+
330
+ # The C dispatcher every reduction shares takes keywords only, so the
331
+ # value-equality and masked forms the base declares -- count(v), count(UNDEF)
332
+ # -- arrived as "wrong number of arguments", which reads as a method that
333
+ # does not take one rather than one whose engine has no such fold. Both are
334
+ # composed here instead, out of folds the engine does have.
335
+ alias __count_present__ count
336
+
337
+ # @overload count
338
+ # Per-group count of present (non-masked) cells.
339
+ # @overload count(UNDEF)
340
+ # Per-group count of masked cells, as {#count_masked}.
341
+ # @overload count(v)
342
+ # Per-group count of cells equal to `v`.
343
+ # @return [CArray]
344
+ def count (*args, **kw)
345
+ return __count_present__(**kw) if args.empty?
346
+ if args.size > 1
347
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..1)"
348
+ end
349
+ v = args.first
350
+ return count_masked(**kw) if v.equal?(UNDEF)
351
+ # a masked cell equals nothing, and eq marks it UNDEF; drop that to false
352
+ # so it is simply not counted
353
+ hit = value.eq(v)
354
+ hit = hit.strip_mask(false) if hit.has_mask?
355
+ self.class.__build__(hit.int64, spec).sum(**kw).int64
356
+ end
357
+
358
+ alias __all_folded__ all
359
+ alias __any_folded__ any
360
+
361
+ # @overload all
362
+ # Per-group `all` over a boolean payload.
363
+ # @return [CArray]
364
+ def all (**kw)
365
+ boolean_payload!(:all)
366
+ __all_folded__(**kw)
367
+ end
368
+
369
+ # @overload any
370
+ # Per-group `any` over a boolean payload.
371
+ # @return [CArray]
372
+ def any (**kw)
373
+ boolean_payload!(:any)
374
+ __any_folded__(**kw)
375
+ end
376
+
312
377
  # Per-group classified cell count (mask-independent) = count on the
313
378
  # mask-stripped value, so every classified cell is counted regardless of the
314
379
  # value mask (unlike count / count_not_masked, which count present cells).
@@ -401,8 +466,8 @@ class CAGroupIterator
401
466
  end
402
467
 
403
468
  # @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
469
+ # Per-group inclusive running maximum, source-shaped, in the source data type
470
+ # (extrema do not grow magnitude, so the data type is preserved). The first
406
471
  # member of a group emits its own value. Without :group delegates to value
407
472
  # cummax.
408
473
  # @return [CArray]
@@ -411,7 +476,7 @@ class CAGroupIterator
411
476
  end
412
477
 
413
478
  # @overload cummin(axis: :group)
414
- # Per-group inclusive running minimum, source-shaped, in the source dtype.
479
+ # Per-group inclusive running minimum, source-shaped, in the source data type.
415
480
  # Without :group delegates to value cummin.
416
481
  # @return [CArray]
417
482
  def cummin (**kw)
@@ -549,6 +614,18 @@ class CAGroupIterator
549
614
  # (the shape a length-K result reshapes to). This is the classification the C
550
615
  # scatter kernel computes on the fly, materialised once so the order
551
616
  # statistics / iterate / sort_addr can hold each group's members together.
617
+ # Whether a payload folds with all / any is the core's call, not this
618
+ # engine's. The engine counts any non-zero numeric cell as true, which made
619
+ # the group the one member of the family that answered where CArray#all and
620
+ # every sibling refuse -- so `data.all` and `data[g].all(axis: :group)` on
621
+ # the same float array disagreed about whether the question was even
622
+ # askable. Asking a one-cell array of the same data type lets the core's own
623
+ # refusal through, unworded by us.
624
+ def boolean_payload! (op)
625
+ CArray.new(value.data_type, [1]).public_send(op)
626
+ nil
627
+ end
628
+
552
629
  def composite_layout
553
630
  gslots = spec.slot_meta.select { |m| m[:kind] == :group }
554
631
  bslots = spec.slot_meta.select { |m| m[:kind] == :band }
@@ -612,7 +689,7 @@ class CAGroupIterator
612
689
  unless value.respond_to?(op)
613
690
  raise NotImplementedError, "axis_group scan: value has no ##{op}"
614
691
  end
615
- return value.send(op)
692
+ return value.public_send(op)
616
693
  end
617
694
  unless fused.empty?
618
695
  raise ArgumentError,
@@ -630,7 +707,7 @@ class CAGroupIterator
630
707
  # operation, and remains a follow-up.
631
708
  def order_stat (op, args, kw)
632
709
  has_group, fused = AxisGroup.parse_axis(kw[:axis])
633
- return value.send(op, *args) unless has_group
710
+ return value.public_send(op, *args) unless has_group
634
711
  unless fused.empty?
635
712
  raise NotImplementedError,
636
713
  "axis_group: folding a band into an order statistic (axis: [:group, k]) " \
@@ -651,13 +728,13 @@ class CAGroupIterator
651
728
  ccat, kdims, gslots, bslots, gaxes = composite_layout
652
729
  nout = (op == :quantile) ? 5 : 1
653
730
  if bslots.empty?
654
- res = value.group_by_category(ccat).send(op, *args)
731
+ res = value.group_by_category(ccat).public_send(op, *args)
655
732
  return nout == 1 ? res.reshape(*kdims) : res.map { |r| r.reshape(*kdims) }
656
733
  end
657
734
  out_shape = spec.slot_meta.map { |m| m[:kind] == :group ? m[:k] : m[:len] }
658
735
  outs = Array.new(nout) { CArray.float64(*out_shape) }
659
736
  each_band_block(ccat, gslots, bslots, gaxes) do |_vi, out_idx, _co, gi|
660
- result = gi.send(op, *args)
737
+ result = gi.public_send(op, *args)
661
738
  if nout == 1
662
739
  outs[0][*out_idx] = result.reshape(*kdims)
663
740
  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.
@@ -186,6 +159,10 @@ class CArray
186
159
  # @raise [ArgumentError] when `offset.length != self.ndim`.
187
160
  def paste (offset, src)
188
161
  raise ArgumentError, "offset length must equal ndim" if offset.length != ndim
162
+ # A zero-length source covers no cell, so there is nothing to write --
163
+ # and the empty range it would ask for is not a window CAWindow can
164
+ # build. This is what lets concatenate / mosaic take an empty piece.
165
+ return self if src.elements.zero?
189
166
  ranges = offset.each_with_index.map { |o, i| o...(o + src.shape[i]) }
190
167
  self.window(*ranges)[] = src
191
168
  self
@@ -246,6 +223,33 @@ class CArray
246
223
 
247
224
  alias cmp <=>
248
225
 
226
+ # @!group Elementwise math
227
+
228
+ # Returns `[quotient, remainder]` element-wise.
229
+ #
230
+ # The quotient is floored toward -inf, so `q * other + r == self` holds
231
+ # for every sign combination -- the pair Ruby's `Integer#divmod` and
232
+ # `Float#divmod` return. For integers that is `self / other` unchanged;
233
+ # for floats `/` is true division, so the quotient is floored here.
234
+ #
235
+ # @param other [CArray, Numeric] divisor.
236
+ # @return [Array<CArray>] the floored quotient and the remainder.
237
+ # @raise [ArgumentError] for complex arrays, which have no ordering to
238
+ # floor toward.
239
+ # @example
240
+ # CA_INT32([-7]).divmod(3) # => [-3, 2]
241
+ # CA_DOUBLE([-7.0]).divmod(3.0) # => [-3.0, 2.0]
242
+ def divmod(other)
243
+ if complex?
244
+ raise ArgumentError, "divmod is not defined for complex arrays"
245
+ end
246
+ q = self / other
247
+ q = q.floor unless q.integer?
248
+ [q, self % other]
249
+ end
250
+
251
+ # @!endgroup
252
+
249
253
  # @overload clip(min, max = nil, fill_value = nil, lfill: nil, ufill: nil)
250
254
  # Returns `self` with every element clamped to `[min, max]`.
251
255
  #
@@ -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