carray 3.0.2 → 3.0.3

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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +304 -0
  3. data/README.md +3 -4
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +225 -0
  6. data/ext/ca_axis_group.c +177 -99
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +66 -71
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +663 -214
  14. data/ext/ca_kernel_iterator.h +231 -167
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +132 -22
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +1 -0
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +96 -25
  25. data/ext/ca_obj_face.c +406 -64
  26. data/ext/ca_obj_face.h +92 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +21 -21
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +189 -107
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +543 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +47 -49
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -118
  46. data/ext/ca_obj_stride.c +195 -34
  47. data/ext/ca_obj_string.c +17 -18
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +149 -27
  54. data/ext/ca_op_ipower.c +24 -11
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_sort_kernels.h +70 -0
  58. data/ext/ca_sweep_engine.c +102 -79
  59. data/ext/ca_sweep_engine.h +7 -20
  60. data/ext/ca_triop_dispatch.c +4 -1
  61. data/ext/ca_triop_dispatch.h +5 -2
  62. data/ext/caframe_csv_reader.c +463 -0
  63. data/ext/carray.h +70 -27
  64. data/ext/carray_access.c +455 -211
  65. data/ext/carray_address_basis.c +177 -36
  66. data/ext/carray_attribute.c +45 -3
  67. data/ext/carray_bincount.c +32 -2
  68. data/ext/carray_broadcast.c +46 -14
  69. data/ext/carray_call_cfunc.c +0 -28
  70. data/ext/carray_cast.c +1444 -209
  71. data/ext/carray_cast_func.rb +34 -21
  72. data/ext/carray_conversion.c +197 -73
  73. data/ext/carray_copy.c +32 -11
  74. data/ext/carray_core.c +514 -122
  75. data/ext/carray_count.c +140 -87
  76. data/ext/carray_element.c +18 -27
  77. data/ext/carray_factorize.c +569 -277
  78. data/ext/carray_generate.c +76 -40
  79. data/ext/carray_histogram.c +152 -94
  80. data/ext/carray_hold.c +2 -1
  81. data/ext/carray_index_classifier.c +63 -96
  82. data/ext/carray_internal.h +385 -3
  83. data/ext/carray_lazy.c +834 -79
  84. data/ext/carray_loop.c +81 -65
  85. data/ext/carray_mask.c +682 -316
  86. data/ext/carray_math_kernel.h +1 -0
  87. data/ext/carray_median_percentile.c +221 -76
  88. data/ext/carray_memory_view.c +254 -248
  89. data/ext/carray_operator.c +1089 -359
  90. data/ext/carray_order.c +133 -69
  91. data/ext/carray_partition.c +102 -56
  92. data/ext/carray_random.c +273 -239
  93. data/ext/carray_scatter.c +308 -65
  94. data/ext/carray_segment.c +257 -0
  95. data/ext/carray_slab.c +170 -70
  96. data/ext/carray_sort.c +215 -218
  97. data/ext/carray_sort_kernel.c +8 -1
  98. data/ext/carray_struct.c +99 -21
  99. data/ext/carray_test.c +167 -64
  100. data/ext/carray_time_text.c +1182 -0
  101. data/ext/carray_utils.c +363 -98
  102. data/ext/extconf.rb +27 -2
  103. data/ext/mk_call_cfunc.rb +0 -2
  104. data/ext/mk_pow5_table.rb +35 -0
  105. data/ext/mkkernel.rb +1076 -846
  106. data/ext/ruby_carray.c +7 -1
  107. data/ext/version.h +4 -4
  108. data/ext/version.rb +4 -2
  109. data/lib/carray/attribute.rb +39 -0
  110. data/lib/carray/autoload_carray.rb +19 -0
  111. data/lib/carray/autoload_method_extension.rb +6 -0
  112. data/lib/carray/axis_group.rb +174 -26
  113. data/lib/carray/basics.rb +91 -25
  114. data/lib/carray/bincount_nd.rb +6 -0
  115. data/lib/carray/block_iterator.rb +118 -48
  116. data/lib/carray/boolean_reduce.rb +21 -29
  117. data/lib/carray/categorical.rb +94 -26
  118. data/lib/carray/categorical_iterator.rb +333 -826
  119. data/lib/carray/complex.rb +36 -29
  120. data/lib/carray/conditional.rb +94 -7
  121. data/lib/carray/const_string.rb +46 -0
  122. data/lib/carray/frame/concat.rb +157 -6
  123. data/lib/carray/frame/convert.rb +13 -7
  124. data/lib/carray/frame/csv_parser.rb +589 -82
  125. data/lib/carray/frame/describe.rb +79 -0
  126. data/lib/carray/frame/frame.rb +124 -25
  127. data/lib/carray/frame/group.rb +189 -63
  128. data/lib/carray/frame/io.rb +412 -72
  129. data/lib/carray/frame/join.rb +134 -46
  130. data/lib/carray/frame/records.rb +37 -16
  131. data/lib/carray/frame/reshape.rb +448 -0
  132. data/lib/carray/frame/sort.rb +26 -2
  133. data/lib/carray/frame/verbs.rb +715 -48
  134. data/lib/carray/frame.rb +2 -0
  135. data/lib/carray/fuse_source.rb +111 -2
  136. data/lib/carray/fusion.rb +229 -38
  137. data/lib/carray/inspect.rb +29 -11
  138. data/lib/carray/iterator.rb +14 -2
  139. data/lib/carray/lazy.rb +54 -30
  140. data/lib/carray/mask_gap_fill.rb +66 -52
  141. data/lib/carray/math.rb +32 -19
  142. data/lib/carray/meld_reduce.rb +40 -7
  143. data/lib/carray/methods/align_addr.rb +8 -4
  144. data/lib/carray/methods/bincount.rb +2 -2
  145. data/lib/carray/methods/bit_string.rb +14 -3
  146. data/lib/carray/methods/composition.rb +16 -10
  147. data/lib/carray/methods/factorize.rb +2 -2
  148. data/lib/carray/methods/gather_nd.rb +13 -2
  149. data/lib/carray/methods/insert_block.rb +9 -14
  150. data/lib/carray/methods/is_in.rb +117 -4
  151. data/lib/carray/methods/join.rb +1 -1
  152. data/lib/carray/methods/locate_addr.rb +177 -36
  153. data/lib/carray/methods/mode.rb +18 -1
  154. data/lib/carray/methods/repeat.rb +1 -20
  155. data/lib/carray/methods/resize.rb +21 -23
  156. data/lib/carray/methods/snap.rb +34 -12
  157. data/lib/carray/methods/unique.rb +11 -2
  158. data/lib/carray/record_order.rb +208 -0
  159. data/lib/carray/runtime.rb +14 -11
  160. data/lib/carray/segment_iterator.rb +990 -0
  161. data/lib/carray/serialize.rb +197 -27
  162. data/lib/carray/slab_iterator.rb +51 -27
  163. data/lib/carray/stack.rb +10 -4
  164. data/lib/carray/string_operation_extension.rb +4 -3
  165. data/lib/carray/struct.rb +142 -51
  166. data/lib/carray/struct_builder.rb +110 -14
  167. data/lib/carray/table.rb +2 -2
  168. data/lib/carray/time.rb +517 -103
  169. data/lib/carray/window_iterator.rb +127 -72
  170. data/lib/carray.rb +2 -2
  171. data/yard-stubs/ca_obj_array.rb +4 -4
  172. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  173. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  174. data/yard-stubs/ca_obj_fake.rb +1 -1
  175. data/yard-stubs/ca_obj_field.rb +1 -1
  176. data/yard-stubs/ca_obj_refer.rb +3 -3
  177. data/yard-stubs/ca_obj_roll.rb +0 -1
  178. data/yard-stubs/ca_obj_stride.rb +10 -7
  179. data/yard-stubs/ca_obj_transpose.rb +1 -1
  180. data/yard-stubs/ca_obj_window.rb +1 -2
  181. data/yard-stubs/carray_access.rb +5 -4
  182. data/yard-stubs/carray_broadcast.rb +1 -1
  183. data/yard-stubs/carray_cast.rb +78 -43
  184. data/yard-stubs/carray_conversion.rb +13 -1
  185. data/yard-stubs/carray_copy.rb +8 -3
  186. data/yard-stubs/carray_core.rb +3 -3
  187. data/yard-stubs/carray_count.rb +9 -0
  188. data/yard-stubs/carray_lazy.rb +5 -1
  189. data/yard-stubs/carray_loop.rb +4 -1
  190. data/yard-stubs/carray_mask.rb +34 -10
  191. data/yard-stubs/carray_math.rb +25 -13
  192. data/yard-stubs/carray_median_percentile.rb +10 -6
  193. data/yard-stubs/carray_order.rb +34 -13
  194. data/yard-stubs/carray_scatter.rb +10 -3
  195. data/yard-stubs/carray_segment.rb +72 -0
  196. data/yard-stubs/carray_slab.rb +2 -1
  197. data/yard-stubs/carray_sort.rb +43 -3
  198. data/yard-stubs/carray_test.rb +10 -0
  199. data/yard-stubs/carray_utils.rb +0 -26
  200. metadata +11 -19
  201. data/ext/ca_for_each_element.h +0 -239
  202. data/ext/carray_build_flags.h +0 -3
  203. data/ext/carray_config.h +0 -28
  204. data/ext/carray_kernels_bincmp.c +0 -4446
  205. data/ext/carray_kernels_binop.c +0 -11001
  206. data/ext/carray_kernels_init.c +0 -1131
  207. data/ext/carray_kernels_map.c +0 -3467
  208. data/ext/carray_kernels_moncmp.c +0 -2097
  209. data/ext/carray_kernels_monop.c +0 -18313
  210. data/ext/carray_kernels_reduce_aggregate.c +0 -26005
  211. data/ext/carray_kernels_reduce_boolean.c +0 -330
  212. data/ext/carray_kernels_reduce_cumulative.c +0 -14862
  213. data/ext/carray_kernels_reduce_extreme.c +0 -17494
  214. data/ext/carray_kernels_reduce_variance.c +0 -3910
  215. data/ext/carray_kernels_scan.c +0 -3693
  216. data/ext/carray_kernels_search.c +0 -32218
  217. data/ext/carray_kernels_sort.c +0 -10626
  218. data/ext/carray_kernels_triop.c +0 -1392
@@ -35,6 +35,7 @@
35
35
  --------------------------------------------------------------------------- */
36
36
 
37
37
  #include "carray.h"
38
+ #include "carray_internal.h" /* CA_EXTREMUM_REPLACES */
38
39
  #include <stdlib.h> /* qsort */
39
40
  #include <math.h> /* floor */
40
41
 
@@ -82,8 +83,7 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
82
83
  rb_raise(rb_eArgError, "__categorical_scatter__: grouped/value data type mismatch");
83
84
  }
84
85
 
85
- ca_attach(codes);
86
- ca_attach(value);
86
+ ca_attach_n(2, codes, value); /* window: detached before each raise inside */
87
87
  cmask = ca_mask_ptr(codes);
88
88
  vmask = ca_mask_ptr(value);
89
89
  cur = (int64_t *) cursor->ptr;
@@ -151,7 +151,7 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
151
151
  #define CAT_NAN_NEVER(v) (0)
152
152
  #define CAT_NAN_CHECK(v) ((v) != (v))
153
153
 
154
- #define REDUCEAT_MOMENTS_BODY(T, ISNAN) \
154
+ #define REDUCEAT_MOMENTS_BODY(T) \
155
155
  do { \
156
156
  const T *gp = (const T *) grouped->ptr; \
157
157
  T *minv = (T *) minp, *maxv = (T *) maxp; \
@@ -160,17 +160,16 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
160
160
  ca_size_t hi = (c + 1 < k) ? (ca_size_t) offs[c+1] : n; \
161
161
  ca_size_t j, cnt = 0; \
162
162
  double sacc = 0.0; \
163
- T mn = 0, mx = 0; int seen = 0, seen_num = 0; \
163
+ T mn = 0, mx = 0; int seen = 0; \
164
164
  for ( j = lo; j < hi; j++ ) { \
165
165
  if ( gm && gm[j] ) continue; /* masked value cell */ \
166
166
  { T v = gp[j]; \
167
167
  sacc += (double) v; \
168
- if ( ! ISNAN(v) ) { \
169
- if ( ! seen_num ) { mn = v; mx = v; seen_num = 1; } \
170
- else { if ( v < mn ) mn = v; if ( v > mx ) mx = v; } \
168
+ if ( ! seen ) { mn = v; mx = v; seen = 1; } \
169
+ else { \
170
+ if ( CA_EXTREMUM_REPLACES(v, mn, <) ) mn = v; \
171
+ if ( CA_EXTREMUM_REPLACES(v, mx, >) ) mx = v; \
171
172
  } \
172
- else if ( ! seen ) { mn = v; mx = v; } /* held until a number lands */\
173
- seen = 1; \
174
173
  cnt++; } \
175
174
  } \
176
175
  countp[c] = (int64_t) cnt; \
@@ -222,16 +221,16 @@ rb_ca_reduceat_moments (VALUE self, VALUE roffsets, VALUE rcounts,
222
221
  maxm = (boolean8_t *) maxs->mask->ptr;
223
222
 
224
223
  switch ( grouped->data_type ) {
225
- case CA_INT8: REDUCEAT_MOMENTS_BODY(int8_t, CAT_NAN_NEVER); break;
226
- case CA_UINT8: REDUCEAT_MOMENTS_BODY(uint8_t, CAT_NAN_NEVER); break;
227
- case CA_INT16: REDUCEAT_MOMENTS_BODY(int16_t, CAT_NAN_NEVER); break;
228
- case CA_UINT16: REDUCEAT_MOMENTS_BODY(uint16_t, CAT_NAN_NEVER); break;
229
- case CA_INT32: REDUCEAT_MOMENTS_BODY(int32_t, CAT_NAN_NEVER); break;
230
- case CA_UINT32: REDUCEAT_MOMENTS_BODY(uint32_t, CAT_NAN_NEVER); break;
231
- case CA_INT64: REDUCEAT_MOMENTS_BODY(int64_t, CAT_NAN_NEVER); break;
232
- case CA_UINT64: REDUCEAT_MOMENTS_BODY(uint64_t, CAT_NAN_NEVER); break;
233
- case CA_FLOAT32: REDUCEAT_MOMENTS_BODY(float32_t, CAT_NAN_CHECK); break;
234
- case CA_FLOAT64: REDUCEAT_MOMENTS_BODY(float64_t, CAT_NAN_CHECK); break;
224
+ case CA_INT8: REDUCEAT_MOMENTS_BODY(int8_t); break;
225
+ case CA_UINT8: REDUCEAT_MOMENTS_BODY(uint8_t); break;
226
+ case CA_INT16: REDUCEAT_MOMENTS_BODY(int16_t); break;
227
+ case CA_UINT16: REDUCEAT_MOMENTS_BODY(uint16_t); break;
228
+ case CA_INT32: REDUCEAT_MOMENTS_BODY(int32_t); break;
229
+ case CA_UINT32: REDUCEAT_MOMENTS_BODY(uint32_t); break;
230
+ case CA_INT64: REDUCEAT_MOMENTS_BODY(int64_t); break;
231
+ case CA_UINT64: REDUCEAT_MOMENTS_BODY(uint64_t); break;
232
+ case CA_FLOAT32: REDUCEAT_MOMENTS_BODY(float32_t); break;
233
+ case CA_FLOAT64: REDUCEAT_MOMENTS_BODY(float64_t); break;
235
234
  default:
236
235
  rb_raise(rb_eCADataTypeError,
237
236
  "__reduceat_moments__: numeric value required (got data_type %d)",
@@ -298,10 +297,9 @@ ca_nth_element_double (double *a, ca_size_t n, ca_size_t kth)
298
297
  /* Push NaN to the tail, which is where CArray's sort puts it, so the \
299
298
  ki-th element here is the ki-th of the sorted segment. Selection \
300
299
  runs on the numeric prefix, since a comparison-based quickselect \
301
- has nothing to say about a NaN, and a position landing in the tail \
302
- answers NaN. The upper neighbour is clamped into the prefix too: a \
303
- position whose neighbour would be a NaN interpolates against itself \
304
- and answers vlo, which is why the median of [NaN, -9.0] is -9.0. */ \
300
+ has nothing to say about a NaN; a position or an upper neighbour \
301
+ landing in the tail is NaN. The blend is CArray#percentile's, so \
302
+ both answer alike for NaN and infinities. */ \
305
303
  ca_size_t nnum = m, t0 = 0; \
306
304
  while ( t0 < nnum ) { \
307
305
  if ( scratch[t0] != scratch[t0] ) { \
@@ -317,8 +315,10 @@ ca_nth_element_double (double *a, ca_size_t n, ca_size_t kth)
317
315
  double mn = scratch[ki+1]; ca_size_t t; \
318
316
  for ( t = ki + 2; t < nnum; t++ ) if ( scratch[t] < mn ) mn = scratch[t]; \
319
317
  vhi = mn; \
320
- } else vhi = vlo; \
321
- outp[c] = vlo + (f - (double) ki) * (vhi - vlo); } \
318
+ } else vhi = (double) NAN; \
319
+ { double r = f - (double) ki; \
320
+ outp[c] = ( r == 0.0 || ki + 1 >= m ) ? vlo \
321
+ : vlo * (1.0 - r) + vhi * r; } } \
322
322
  } \
323
323
  } while (0)
324
324
 
@@ -335,6 +335,11 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
335
335
  GetCArray(roffsets, offsets);
336
336
  GetCArray(rout, out);
337
337
  p = NUM2DBL(rp);
338
+ if ( !(p >= 0.0 && p <= 100.0) ) {
339
+ rb_raise(rb_eArgError,
340
+ "percentile: p must be Numeric in [0,100] (got %"PRIsVALUE")",
341
+ rb_inspect(rp));
342
+ }
338
343
  k = (int64_t) offsets->elements;
339
344
  n = grouped->elements;
340
345
  if ( offsets->data_type != CA_INT64 || out->data_type != CA_FLOAT64 ||
@@ -383,7 +388,7 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
383
388
  accumulates the per-segment centred sum of squares; variance = SS / (n-1).
384
389
  Centred (not the one-pass sum-of-squares) so the ε-close contract holds — a
385
390
  one-pass formula cancels catastrophically. Matches CArray#variance per group:
386
- count 0 -> masked (undefined), count 1 -> 0.0 (the n=1 contract), count >= 2 ->
391
+ count 0 or 1 -> masked (the sample variance is undefined), count >= 2 ->
387
392
  SS / (count-1).
388
393
 
389
394
  Surface (private): grouped.__reduceat_variance__(offsets, means, counts, out)
@@ -391,7 +396,7 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
391
396
  offsets = int64[k] segment STARTS
392
397
  means = float64[k] per-segment mean (ignored where count < 2)
393
398
  counts = int64[k] per-segment present count
394
- out = float64[k] output: variance; count 0 masked, count 1 -> 0.0
399
+ out = float64[k] output: variance; count 0 or 1 masked
395
400
 
396
401
  --------------------------------------------------------------------------- */
397
402
 
@@ -400,8 +405,7 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
400
405
  const T *gp = (const T *) grouped->ptr; \
401
406
  for ( c = 0; c < k; c++ ) { \
402
407
  int64_t cnt = countp[c]; \
403
- if ( cnt == 0 ) { outp[c] = 0.0; outm[c] = 1; continue; } /* -> masked */ \
404
- if ( cnt == 1 ) { outp[c] = 0.0; continue; } /* n=1 contract: 0.0 */ \
408
+ if ( cnt < 2 ) { outp[c] = 0.0; outm[c] = 1; continue; } /* undefined */ \
405
409
  { double mean = meanp[c], ss = 0.0; \
406
410
  ca_size_t lo = (ca_size_t) offs[c]; \
407
411
  ca_size_t hi = (c + 1 < k) ? (ca_size_t) offs[c+1] : n, j; \
@@ -869,7 +873,7 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
869
873
  (empty group cell → 0 + masked, matching __reduceat_moments__).
870
874
  --------------------------------------------------------------------------- */
871
875
 
872
- #define FIBER_SCATTER_BODY(H_T, C_T, ISNAN) \
876
+ #define FIBER_SCATTER_BODY(H_T, C_T) \
873
877
  do { \
874
878
  const H_T *hp = (const H_T *) h->ptr; \
875
879
  const C_T *cp = (const C_T *) codes->ptr; \
@@ -891,18 +895,11 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
891
895
  if ( c < 0 || c >= K ) continue; /* out-of-vocabulary */ \
892
896
  out_off = c * band_size + out_outer + inn; \
893
897
  v = hp[off]; \
894
- /* NaN loses every contest, as in the flat twin and the core. min \
895
- and max are seeded together and only ever move on a number, so a \
896
- NaN sitting in min means this cell has met nothing but NaN yet. */\
897
898
  if ( countp[out_off] == 0 ) { \
898
899
  minv[out_off] = v; maxv[out_off] = v; \
899
- } else if ( ! ISNAN(v) ) { \
900
- if ( ISNAN(minv[out_off]) ) { \
901
- minv[out_off] = v; maxv[out_off] = v; \
902
- } else { \
903
- if ( v < minv[out_off] ) minv[out_off] = v; \
904
- if ( v > maxv[out_off] ) maxv[out_off] = v; \
905
- } \
900
+ } else { \
901
+ if ( CA_EXTREMUM_REPLACES(v, minv[out_off], <) ) minv[out_off] = v;\
902
+ if ( CA_EXTREMUM_REPLACES(v, maxv[out_off], >) ) maxv[out_off] = v;\
906
903
  } \
907
904
  countp[out_off]++; \
908
905
  sump[out_off] += (double) v; \
@@ -911,16 +908,16 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
911
908
  } \
912
909
  } while (0)
913
910
 
914
- #define FIBER_SCATTER_DISPATCH_C(H_T, ISNAN) \
911
+ #define FIBER_SCATTER_DISPATCH_C(H_T) \
915
912
  switch ( codes->data_type ) { \
916
- case CA_INT8: FIBER_SCATTER_BODY(H_T, int8_t, ISNAN); break; \
917
- case CA_UINT8: FIBER_SCATTER_BODY(H_T, uint8_t, ISNAN); break; \
918
- case CA_INT16: FIBER_SCATTER_BODY(H_T, int16_t, ISNAN); break; \
919
- case CA_UINT16: FIBER_SCATTER_BODY(H_T, uint16_t, ISNAN); break; \
920
- case CA_INT32: FIBER_SCATTER_BODY(H_T, int32_t, ISNAN); break; \
921
- case CA_UINT32: FIBER_SCATTER_BODY(H_T, uint32_t, ISNAN); break; \
922
- case CA_INT64: FIBER_SCATTER_BODY(H_T, int64_t, ISNAN); break; \
923
- case CA_UINT64: FIBER_SCATTER_BODY(H_T, uint64_t, ISNAN); break; \
913
+ case CA_INT8: FIBER_SCATTER_BODY(H_T, int8_t); break; \
914
+ case CA_UINT8: FIBER_SCATTER_BODY(H_T, uint8_t); break; \
915
+ case CA_INT16: FIBER_SCATTER_BODY(H_T, int16_t); break; \
916
+ case CA_UINT16: FIBER_SCATTER_BODY(H_T, uint16_t); break; \
917
+ case CA_INT32: FIBER_SCATTER_BODY(H_T, int32_t); break; \
918
+ case CA_UINT32: FIBER_SCATTER_BODY(H_T, uint32_t); break; \
919
+ case CA_INT64: FIBER_SCATTER_BODY(H_T, int64_t); break; \
920
+ case CA_UINT64: FIBER_SCATTER_BODY(H_T, uint64_t); break; \
924
921
  default: \
925
922
  ca_detach(h); ca_detach(codes); \
926
923
  rb_raise(rb_eCADataTypeError, \
@@ -1005,8 +1002,7 @@ rb_ca_fiber_scatter_moments (VALUE self, VALUE rcodes, VALUE raxis, VALUE rk,
1005
1002
  band_size = outer_prod * inner_prod;
1006
1003
  total = (ca_size_t)(K * band_size);
1007
1004
 
1008
- ca_attach(h);
1009
- ca_attach(codes);
1005
+ ca_attach_n(2, h, codes); /* window: detached before each raise inside */
1010
1006
  hm = ca_mask_ptr(h);
1011
1007
  cm = ca_mask_ptr(codes);
1012
1008
  countp = (int64_t *) counts->ptr;
@@ -1020,16 +1016,16 @@ rb_ca_fiber_scatter_moments (VALUE self, VALUE rcodes, VALUE raxis, VALUE rk,
1020
1016
  memset(maxp, 0, (size_t) total * (size_t) h->bytes);
1021
1017
 
1022
1018
  switch ( h->data_type ) {
1023
- case CA_INT8: FIBER_SCATTER_DISPATCH_C(int8_t, CAT_NAN_NEVER); break;
1024
- case CA_UINT8: FIBER_SCATTER_DISPATCH_C(uint8_t, CAT_NAN_NEVER); break;
1025
- case CA_INT16: FIBER_SCATTER_DISPATCH_C(int16_t, CAT_NAN_NEVER); break;
1026
- case CA_UINT16: FIBER_SCATTER_DISPATCH_C(uint16_t, CAT_NAN_NEVER); break;
1027
- case CA_INT32: FIBER_SCATTER_DISPATCH_C(int32_t, CAT_NAN_NEVER); break;
1028
- case CA_UINT32: FIBER_SCATTER_DISPATCH_C(uint32_t, CAT_NAN_NEVER); break;
1029
- case CA_INT64: FIBER_SCATTER_DISPATCH_C(int64_t, CAT_NAN_NEVER); break;
1030
- case CA_UINT64: FIBER_SCATTER_DISPATCH_C(uint64_t, CAT_NAN_NEVER); break;
1031
- case CA_FLOAT32: FIBER_SCATTER_DISPATCH_C(float32_t, CAT_NAN_CHECK); break;
1032
- case CA_FLOAT64: FIBER_SCATTER_DISPATCH_C(float64_t, CAT_NAN_CHECK); break;
1019
+ case CA_INT8: FIBER_SCATTER_DISPATCH_C(int8_t); break;
1020
+ case CA_UINT8: FIBER_SCATTER_DISPATCH_C(uint8_t); break;
1021
+ case CA_INT16: FIBER_SCATTER_DISPATCH_C(int16_t); break;
1022
+ case CA_UINT16: FIBER_SCATTER_DISPATCH_C(uint16_t); break;
1023
+ case CA_INT32: FIBER_SCATTER_DISPATCH_C(int32_t); break;
1024
+ case CA_UINT32: FIBER_SCATTER_DISPATCH_C(uint32_t); break;
1025
+ case CA_INT64: FIBER_SCATTER_DISPATCH_C(int64_t); break;
1026
+ case CA_UINT64: FIBER_SCATTER_DISPATCH_C(uint64_t); break;
1027
+ case CA_FLOAT32: FIBER_SCATTER_DISPATCH_C(float32_t); break;
1028
+ case CA_FLOAT64: FIBER_SCATTER_DISPATCH_C(float64_t); break;
1033
1029
  default:
1034
1030
  ca_detach(h); ca_detach(codes);
1035
1031
  rb_raise(rb_eCADataTypeError,
@@ -1172,8 +1168,7 @@ rb_ca_fiber_scatter_prod (VALUE self, VALUE rcodes, VALUE raxis,
1172
1168
  band_size = outer_prod * inner_prod;
1173
1169
  total = (ca_size_t)(K * band_size);
1174
1170
 
1175
- ca_attach(h);
1176
- ca_attach(codes);
1171
+ ca_attach_n(2, h, codes); /* window: detached before each raise inside */
1177
1172
  hm = ca_mask_ptr(h);
1178
1173
  cm = ca_mask_ptr(codes);
1179
1174
  outp = (double *) out->ptr;
@@ -1281,6 +1276,7 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1281
1276
  ca_size_t ax, inn, outer, cell;
1282
1277
  ca_size_t axis_size, inner_prod, outer_prod, band_size, total;
1283
1278
  double *wp, *wsp, *wsw, *wmp;
1279
+ volatile VALUE hwsw = 0, hcnt = 0;
1284
1280
  boolean8_t *hm, *cm, *wm, *wmm;
1285
1281
  int8_t i, j;
1286
1282
  int64_t *cnt_scratch = NULL;
@@ -1341,9 +1337,7 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1341
1337
  band_size = outer_prod * inner_prod;
1342
1338
  total = (ca_size_t)(K * band_size);
1343
1339
 
1344
- ca_attach(h);
1345
- ca_attach(codes);
1346
- ca_attach(weights);
1340
+ ca_attach_n(3, h, codes, weights); /* window: detached before each raise inside */
1347
1341
  hm = ca_mask_ptr(h);
1348
1342
  cm = ca_mask_ptr(codes);
1349
1343
  wm = ca_mask_ptr(weights);
@@ -1352,8 +1346,9 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1352
1346
  wmp = (double *) wmean->ptr; /* wmean output (temp = sum-of-weights, then divide) */
1353
1347
 
1354
1348
  /* Two auxiliary scratches: sum-of-weights (per cell) and present-pair count. */
1355
- wsw = (double *) xmalloc((size_t) total * sizeof(double));
1356
- cnt_scratch = (int64_t *) xmalloc((size_t) total * sizeof(int64_t));
1349
+ /* ALLOCV, so a raise below (codes of the wrong type) leaves nothing. */
1350
+ wsw = ALLOCV_N(double, hwsw, total);
1351
+ cnt_scratch = ALLOCV_N(int64_t, hcnt, total);
1357
1352
 
1358
1353
  memset(wsp, 0, (size_t) total * sizeof(double));
1359
1354
  memset(wsw, 0, (size_t) total * sizeof(double));
@@ -1372,7 +1367,7 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1372
1367
  case CA_FLOAT32: FIBER_SCATTER_WSUM_DISPATCH_C(float32_t); break;
1373
1368
  case CA_FLOAT64: FIBER_SCATTER_WSUM_DISPATCH_C(float64_t); break;
1374
1369
  default:
1375
- xfree(wsw); xfree(cnt_scratch);
1370
+ ALLOCV_END(hwsw); ALLOCV_END(hcnt);
1376
1371
  ca_detach(h); ca_detach(codes); ca_detach(weights);
1377
1372
  rb_raise(rb_eCADataTypeError,
1378
1373
  "__fiber_scatter_wsum_wmean__: numeric value required (got %d)",
@@ -1391,8 +1386,8 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1391
1386
  }
1392
1387
  }
1393
1388
 
1394
- xfree(wsw);
1395
- xfree(cnt_scratch);
1389
+ ALLOCV_END(hwsw);
1390
+ ALLOCV_END(hcnt);
1396
1391
  ca_detach(h);
1397
1392
  ca_detach(codes);
1398
1393
  ca_detach(weights);
data/ext/ca_compare.c CHANGED
@@ -14,6 +14,7 @@
14
14
  #include "ruby.h"
15
15
  #include "carray.h"
16
16
  #include "ca_compare.h"
17
+ #include "carray_internal.h" /* ca_obj_cmp, CA_OBJ_ISNAN */
17
18
  #include <math.h>
18
19
 
19
20
  #define DEFINE_ELEM_CMP(type) \
@@ -53,13 +54,18 @@ DEFINE_ELEM_CMP(uint64_t)
53
54
  DEFINE_ELEM_CMP_FLOAT(float32_t)
54
55
  DEFINE_ELEM_CMP_FLOAT(float64_t)
55
56
 
56
- /* CA_OBJECT: compare via Ruby Comparable (`<=>`). */
57
+ /* CA_OBJECT: compare by `<=>`, as sort orders object cells: a Float NaN
58
+ after every other value, and a pair `<=>` cannot order raises
59
+ "comparison of A with B failed". */
57
60
  static int
58
61
  elem_cmp_VALUE (const void *va, const void *vb)
59
62
  {
60
63
  const VALUE *a = (const VALUE *) va;
61
64
  const VALUE *b = (const VALUE *) vb;
62
- return NUM2INT(rb_funcall(*a, rb_intern("<=>"), 1, *b));
65
+ int nan_a = CA_OBJ_ISNAN(*a);
66
+ int nan_b = CA_OBJ_ISNAN(*b);
67
+ if ( nan_a || nan_b ) return nan_a - nan_b;
68
+ return ca_obj_cmp(*a, *b);
63
69
  }
64
70
 
65
71
  /* FIXLEN / retired / otherwise unorderable data_types: callers must handle
data/ext/ca_for_buffer.h CHANGED
@@ -1,43 +1,33 @@
1
1
  /* ---------------------------------------------------------------------------
2
2
  *
3
- * ca_for_buffer.h -- sweep WHOLE_BUFFER macro family (whole contig buffer
4
- * handed to author / third-party library)
3
+ * ca_for_buffer.h -- whole contig buffer handed to author / third-party
4
+ * library
5
5
  *
6
- * PROPOSAL_L0_AUTHOR_SURFACE L0.2c (2026-06-11). C-side counterpart of
7
- * Ruby's `ca.attach! { |a| ... }` block: scope the ca_attach + ca_sync +
8
- * ca_detach lifecycle to a block, hand the author a contig buffer ptr
9
- * and element count, let the author do whatever (typically pass the
10
- * ptr to a third-party library: FFTW, akima init, fitpack surf1, ...).
6
+ * C-side counterpart of Ruby's `ca.attach! { |a| ... }` block: the
7
+ * attach / sync / detach lifecycle is scoped to a body function, which
8
+ * receives a contig buffer ptr and element count and may do whatever
9
+ * (typically pass the ptr to a third-party library: FFTW, akima init,
10
+ * fitpack surf1, ...).
11
11
  *
12
- * Two forms (Q8 = M1 alias-when-possible; non-contig source materialises
13
- * into a transient buffer; writable form syncs back on exit):
12
+ * The buffer aliases ca->ptr when the array is a contig entity and is a
13
+ * materialised scratch otherwise. A writable call syncs the buffer back
14
+ * to the array's storage when the body returns.
14
15
  *
15
- * CA_WITH_BUFFER(ca, T, ptr, n)
16
- * Read-only access. ptr : T const * pointing at native contig
17
- * layout of `ca` (= ca->ptr alias when contig entity, scratch
18
- * otherwise). n : ca->elements. No write-back on exit.
19
- *
20
- * CA_WITH_BUFFER_WRITABLE(ca, T, ptr, n)
21
- * Writable; same ptr/n semantics, plus ca_sync on block exit so
22
- * author writes propagate back to the view's storage.
16
+ * The body may raise. The view is closed however the body is left: a
17
+ * writable view is synced first, so what the body wrote before raising
18
+ * reaches the view's storage, and the view is detached even if that sync
19
+ * raises. The body's exception is the one that propagates.
23
20
  *
24
21
  * Author pattern:
25
22
  *
26
- * double *ptr;
27
- * ca_size_t n;
28
- * CA_WITH_BUFFER_WRITABLE(ca, double, ptr, n) {
23
+ * static void
24
+ * fft_body (void *user_data, void *ptr, ca_size_t n)
25
+ * {
26
+ * fftw_plan plan = (fftw_plan) user_data;
29
27
  * fftw_execute_dft(plan, ptr, ptr);
30
28
  * }
31
29
  *
32
- * Constraints (same as CA_FOR_EACH_FIBER family):
33
- * - `break;` from body exits cleanly (= outer for's advance clause
34
- * runs ca_sync / ca_detach).
35
- * - `return;` from body LEAKS the attach; restructure to break.
36
- * - Macros are NOT statement-equivalent (= nested for); no trailing
37
- * `else`.
38
- * - For Ruby-exception-safe lifecycle (e.g. when calling Ruby code or
39
- * anything that may raise from inside the body), use the function
40
- * form `rb_ca_call_with_buffer` instead (= rb_ensure-protected).
30
+ * rb_ca_call_with_buffer(rca, 1, fft_body, plan);
41
31
  *
42
32
  * --------------------------------------------------------------------------- */
43
33
 
@@ -47,46 +37,9 @@
47
37
  #include "carray.h"
48
38
  #include "ca_sweep_engine.h"
49
39
 
50
- /* ---------- macro forms ---------- */
51
-
52
- /* Read-only: ca_attach (alias-when-possible) + author body + ca_detach. */
53
- #define CA_WITH_BUFFER(_ca, T, _ptr, _n) \
54
- for ( CArray *__cwv_ca = (CArray *)(_ca); \
55
- __cwv_ca; \
56
- ca_detach(__cwv_ca), __cwv_ca = NULL ) \
57
- for ( int __cwv_once = (ca_attach(__cwv_ca), \
58
- (_ptr) = (T *)__cwv_ca->ptr, \
59
- (_n) = __cwv_ca->elements, \
60
- 1); \
61
- __cwv_once; \
62
- __cwv_once = 0 )
63
-
64
- /* Writable: same as above + ca_sync on block exit. */
65
- #define CA_WITH_BUFFER_WRITABLE(_ca, T, _ptr, _n) \
66
- for ( CArray *__cwvw_ca = (CArray *)(_ca); \
67
- __cwvw_ca; \
68
- ca_sync(__cwvw_ca), ca_detach(__cwvw_ca), __cwvw_ca = NULL ) \
69
- for ( int __cwvw_once = (ca_attach(__cwvw_ca), \
70
- (_ptr) = (T *)__cwvw_ca->ptr, \
71
- (_n) = __cwvw_ca->elements, \
72
- 1); \
73
- __cwvw_once; \
74
- __cwvw_once = 0 )
75
-
76
- /* ---------- function form (rb_ensure-protected, AC8) ----------
77
- *
78
- * Use this when the body may raise a Ruby exception (= calling rb_funcall,
79
- * type-checking with rb_check_type, indirect Ruby code, etc.). The
80
- * engine ca_attach's the view, runs body via rb_ensure, then guarantees
81
- * ca_sync (if writable) and ca_detach run before the exception
82
- * propagates.
83
- *
84
- * body_fn(user_data, ptr, n_elements) -> may raise
40
+ /* body_fn(user_data, ptr, n_elements) -> may raise
85
41
  *
86
- * Returns whatever body_fn returns via its own propagation (= the
87
- * function itself returns Qnil since rb_ensure body must be VALUE).
88
- * For richer return semantics, box your result in user_data.
89
- */
42
+ * Returns nothing; box a result in user_data. */
90
43
  typedef void (*ca_with_buffer_body_fn) (void *user_data, void *ptr,
91
44
  ca_size_t n_elements);
92
45
 
data/ext/ca_group_iter.c CHANGED
@@ -40,10 +40,14 @@ static ID id_axis_group_reduce, id_reshape, id_transpose, id_labels;
40
40
  static VALUE
41
41
  rb_ca_register_axis_group_classes (VALUE klass, VALUE cat, VALUE ag)
42
42
  {
43
+ static int registered = 0;
43
44
  rb_cCategorical = cat;
44
45
  rb_cAxisGroup = ag;
45
- rb_gc_register_address(&rb_cCategorical);
46
- rb_gc_register_address(&rb_cAxisGroup);
46
+ if ( ! registered ) { /* one GC root per address, not per call */
47
+ rb_gc_register_address(&rb_cCategorical);
48
+ rb_gc_register_address(&rb_cAxisGroup);
49
+ registered = 1;
50
+ }
47
51
  return Qnil;
48
52
  }
49
53
 
@@ -172,7 +176,7 @@ group_iter_shape_output (VALUE result, VALUE group_dims, VALUE perm, VALUE squee
172
176
  nband = cr->ndim - 1;
173
177
  nd = ng + nband;
174
178
  if ( nd > CA_RANK_MAX ) {
175
- rb_raise(rb_eRuntimeError, "axis_group: output ndim %ld too large", nd);
179
+ rb_raise(rb_eArgError, "axis_group: output ndim %ld too large", nd);
176
180
  }
177
181
  for ( i = 0; i < ng; i++ ) {
178
182
  dims[i] = RARRAY_AREF(group_dims, i);
@@ -233,17 +237,19 @@ group_iter_reduce (int argc, VALUE *argv, VALUE self)
233
237
  parsed = rb_funcall(rb_cAxisGroup, id_parse_axis,
234
238
  1, (vaxis == Qundef) ? Qnil : vaxis);
235
239
  if ( RTEST(RARRAY_AREF(parsed, 0)) == 0 ) {
236
- /* no :group -> plain reduction on the value (grouping not engaged) */
237
- if ( vaxis == Qundef ) {
240
+ /* no :group -> plain reduction on the value (grouping not engaged),
241
+ with every keyword the caller gave */
242
+ if ( NIL_P(kw) ) {
238
243
  return rb_funcall(value, op_id, 0);
239
244
  }
240
- else {
241
- VALUE h = rb_hash_new();
242
- rb_hash_aset(h, ID2SYM(rb_intern("axis")), vaxis);
243
- return rb_funcallv_kw(value, op_id, 1, &h, RB_PASS_KEYWORDS);
244
- }
245
+ return rb_funcallv_kw(value, op_id, 1, &kw, RB_PASS_KEYWORDS);
245
246
  }
246
247
 
248
+ /* min_count: / fill_value: are applied to the grouped result; any other
249
+ keyword is refused before the work is done. */
250
+ rb_funcall(rb_cAxisGroup, rb_intern("__check_reduce_keywords__"), 2,
251
+ op_sym, NIL_P(kw) ? rb_hash_new() : kw);
252
+
247
253
  /* group reduction */
248
254
  fused = RARRAY_AREF(parsed, 1);
249
255
  plan = rb_funcall(spec, id_reduce_plan, 1, fused);
@@ -253,9 +259,19 @@ group_iter_reduce (int argc, VALUE *argv, VALUE self)
253
259
  perm = RARRAY_AREF(plan, 3);
254
260
  squeeze = RARRAY_AREF(plan, 4);
255
261
 
262
+ /* a count reads only the mask, so a payload the kernel cannot read
263
+ (object, fixlen, ...) is counted through a boolean stand-in */
264
+ if ( op_id == rb_intern("count") || op_id == rb_intern("count_not_masked") ) {
265
+ value = rb_funcall(rb_cAxisGroup, rb_intern("__count_stand_in__"), 1, value);
266
+ }
256
267
  result = rb_funcall(value, id_axis_group_reduce, 3,
257
268
  group_axes, bundles, op_sym);
258
- return group_iter_shape_output(result, group_dims, perm, squeeze);
269
+ result = group_iter_shape_output(result, group_dims, perm, squeeze);
270
+ if ( ! NIL_P(kw) ) {
271
+ result = rb_funcall(rb_cAxisGroup, rb_intern("__finish_reduction__"), 4,
272
+ self, op_sym, kw, result);
273
+ }
274
+ return result;
259
275
  }
260
276
 
261
277
  void