carray 3.0.1 → 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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  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 +90 -41
  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 +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  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 +194 -42
  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 +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -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 +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  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 +551 -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 +88 -75
  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 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  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 +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
data/ext/ca_axis_group.c CHANGED
@@ -33,6 +33,7 @@
33
33
  --------------------------------------------------------------------------- */
34
34
 
35
35
  #include "carray.h"
36
+ #include "carray_internal.h" /* CA_EXTREMUM_REPLACES, ca_obj_extremum_replaces */
36
37
  #include "ca_kernel_iterator.h"
37
38
  #include <math.h>
38
39
 
@@ -64,8 +65,8 @@ group_op_code (VALUE vop)
64
65
  else if ( id == rb_intern("max_addr") ) return GR_MAXADDR;
65
66
  else if ( id == rb_intern("all") ) return GR_ALL;
66
67
  else if ( id == rb_intern("any") ) return GR_ANY;
67
- rb_raise(rb_eArgError, "axis_group_reduce: unsupported op :%s",
68
- rb_id2name(id));
68
+ rb_raise(rb_eArgError, "axis_group_reduce: unsupported op :%"PRIsVALUE,
69
+ rb_id2str(id));
69
70
  }
70
71
 
71
72
  /* ---- per-slab-element composite-code walk, templated on the load type ----
@@ -110,16 +111,20 @@ group_op_code (VALUE vop)
110
111
  ca_size_t *gw_ocode = NULL, *gw_doff = NULL, *gw_moff = NULL, \
111
112
  *gw_gaddr = NULL; \
112
113
  int gw_ready = 0; \
114
+ /* freed by the unwind too: the walk can raise between here and the \
115
+ releases below (a gather that cannot convert a cell, and in the object \
116
+ lane a callback into Ruby). */ \
117
+ volatile VALUE gw_h1 = 0, gw_h2 = 0, gw_h3 = 0, gw_h4 = 0; \
113
118
  int gw_need_addr = ( op == GR_MINADDR || op == GR_MAXADDR ); \
114
119
  CA_FOR_EACH_SLAB(st, ca, axes, (int8_t) ngroup, CA_KERNEL_READ, p, m) { \
115
120
  int8_t sndim = st.slab_ndim; \
116
121
  ca_size_t SE = st.slab_elements; \
117
122
  if ( SE > 0 ) { \
118
123
  if ( ! gw_ready ) { \
119
- gw_ocode = ALLOC_N(ca_size_t, SE); \
120
- gw_doff = ALLOC_N(ca_size_t, SE); \
121
- gw_moff = ALLOC_N(ca_size_t, SE); \
122
- gw_gaddr = ALLOC_N(ca_size_t, SE); \
124
+ gw_ocode = ALLOCV_N(ca_size_t, gw_h1, SE); \
125
+ gw_doff = ALLOCV_N(ca_size_t, gw_h2, SE); \
126
+ gw_moff = ALLOCV_N(ca_size_t, gw_h3, SE); \
127
+ gw_gaddr = ALLOCV_N(ca_size_t, gw_h4, SE); \
123
128
  ca_size_t sidx[CA_RANK_MAX]; \
124
129
  for ( int8_t k = 0; k < sndim; k++ ) sidx[k] = 0; \
125
130
  for ( ca_size_t e = 0; e < SE; e++ ) { \
@@ -158,21 +163,95 @@ group_op_code (VALUE vop)
158
163
  ca_size_t oc = gw_ocode[e]; \
159
164
  if ( oc == GW_SKIP ) continue; \
160
165
  if ( m && m[ gw_moff[e] ] ) continue; \
161
- double v = (double) ( *(T *)(p + gw_doff[e]) ); \
166
+ T rv = *(T *)(p + gw_doff[e]); \
167
+ double v = (double) rv; \
162
168
  ca_size_t o = oc + b; \
163
169
  ca_size_t gaddr = gw_gaddr[e]; \
164
170
  ACCUM; \
165
- (void) v; (void) o; (void) gaddr; \
171
+ (void) rv; (void) v; (void) o; (void) gaddr; \
166
172
  } \
167
173
  } \
168
174
  b++; \
169
175
  } \
170
- if ( gw_ocode ) xfree(gw_ocode); \
171
- if ( gw_doff ) xfree(gw_doff); \
172
- if ( gw_moff ) xfree(gw_moff); \
173
- if ( gw_gaddr ) xfree(gw_gaddr); \
176
+ ALLOCV_END(gw_h1); ALLOCV_END(gw_h2); \
177
+ ALLOCV_END(gw_h3); ALLOCV_END(gw_h4); \
174
178
  } while (0)
175
179
 
180
+ /* GROUP_DISPATCH_T(ACCUM_T): the same walk, for an accumulator that needs to
181
+ name the source type -- one that keeps its answer in that type instead of
182
+ widening to double. ACCUM_T is invoked as ACCUM_T(T). */
183
+ #define GROUP_DISPATCH_T(ACCUM_T) \
184
+ switch ( ca->data_type ) { \
185
+ case CA_BOOLEAN: GROUP_WALK(boolean8_t, ACCUM_T(boolean8_t)); break; \
186
+ case CA_INT8: GROUP_WALK(int8_t, ACCUM_T(int8_t)); break; \
187
+ case CA_UINT8: GROUP_WALK(uint8_t, ACCUM_T(uint8_t)); break; \
188
+ case CA_INT16: GROUP_WALK(int16_t, ACCUM_T(int16_t)); break; \
189
+ case CA_UINT16: GROUP_WALK(uint16_t, ACCUM_T(uint16_t)); break; \
190
+ case CA_INT32: GROUP_WALK(int32_t, ACCUM_T(int32_t)); break; \
191
+ case CA_UINT32: GROUP_WALK(uint32_t, ACCUM_T(uint32_t)); break; \
192
+ case CA_INT64: GROUP_WALK(int64_t, ACCUM_T(int64_t)); break; \
193
+ case CA_UINT64: GROUP_WALK(uint64_t, ACCUM_T(uint64_t)); break; \
194
+ case CA_FLOAT32: GROUP_WALK(float, ACCUM_T(float)); break; \
195
+ case CA_FLOAT64: GROUP_WALK(double, ACCUM_T(double)); break; \
196
+ default: break; \
197
+ }
198
+
199
+ /* An extremum keeps the source data type: its magnitude never grows, so
200
+ widening to double buys nothing and costs exactness -- an int64 past 2^53
201
+ came back rounded, and a genuinely larger value could lose the comparison
202
+ to a smaller one that rounded to the same double. The scan siblings
203
+ (GROUP_SCAN_EXTREMUM_WALK) already held a native accumulator; these are the
204
+ reduce twins.
205
+
206
+ A NaN loses every contest, as it does in CArray's own min / max: it is held
207
+ only while nothing else has landed, the first number displaces it, and a
208
+ group of nothing but NaN answers NaN (for a position, UNDEF). `v` is the
209
+ widened load, so testing it for NaN is the same question for every source
210
+ type and costs an integer body nothing. The replacement test is the
211
+ shared CA_EXTREMUM_REPLACES; the first member is taken whatever it is.
212
+ seen_num[] says whether a number has landed (the positions read it);
213
+ cnt[] says whether anything has. */
214
+ #define GMINMAX(T, A, CMP) \
215
+ do { \
216
+ A *acc = (A *) co->ptr; \
217
+ A av = (A) rv; \
218
+ if ( cnt[o] == 0 || CA_EXTREMUM_REPLACES(av, acc[o], CMP) ) acc[o] = av; \
219
+ if ( v == v ) seen_num[o] = 1; \
220
+ cnt[o] += 1; \
221
+ } while (0)
222
+
223
+ /* Boolean answers as its 0/1 numeric storage, which is what CArray#min / #max
224
+ do with a boolean array -- the boolean-returning twins are all / any. Every
225
+ other type answers in its own. */
226
+ #define GROUP_DISPATCH_EXTREMUM(CMP) \
227
+ switch ( ca->data_type ) { \
228
+ case CA_BOOLEAN: GROUP_WALK(boolean8_t, GMINMAX(boolean8_t, uint64_t, CMP)); break; \
229
+ case CA_INT8: GROUP_WALK(int8_t, GMINMAX(int8_t, int8_t, CMP)); break; \
230
+ case CA_UINT8: GROUP_WALK(uint8_t, GMINMAX(uint8_t, uint8_t, CMP)); break; \
231
+ case CA_INT16: GROUP_WALK(int16_t, GMINMAX(int16_t, int16_t, CMP)); break; \
232
+ case CA_UINT16: GROUP_WALK(uint16_t, GMINMAX(uint16_t, uint16_t, CMP)); break; \
233
+ case CA_INT32: GROUP_WALK(int32_t, GMINMAX(int32_t, int32_t, CMP)); break; \
234
+ case CA_UINT32: GROUP_WALK(uint32_t, GMINMAX(uint32_t, uint32_t, CMP)); break; \
235
+ case CA_INT64: GROUP_WALK(int64_t, GMINMAX(int64_t, int64_t, CMP)); break; \
236
+ case CA_UINT64: GROUP_WALK(uint64_t, GMINMAX(uint64_t, uint64_t, CMP)); break; \
237
+ case CA_FLOAT32: GROUP_WALK(float, GMINMAX(float, float, CMP)); break; \
238
+ case CA_FLOAT64: GROUP_WALK(double, GMINMAX(double, double, CMP)); break; \
239
+ default: break; \
240
+ }
241
+
242
+ #define GMINMAXADDR(T, CMP, ADDR) \
243
+ do { \
244
+ T *acc = (T *) xbuf; \
245
+ if ( v == v && ( ! seen_num[o] || rv CMP acc[o] ) ) { \
246
+ acc[o] = rv; \
247
+ ADDR[o] = (int64_t) ( band_addr[b] + gaddr ); \
248
+ seen_num[o] = 1; \
249
+ } \
250
+ cnt[o] += 1; \
251
+ } while (0)
252
+ #define GMINADDR(T) GMINMAXADDR(T, <, mnaddr)
253
+ #define GMAXADDR(T) GMINMAXADDR(T, >, mxaddr)
254
+
176
255
  /* Run one walk over every supported native data type. Dispatched on the source
177
256
  data_type so the inner loop stays monomorphic (no forced float64 cast). */
178
257
  #define GROUP_DISPATCH(ACCUM) \
@@ -232,7 +311,7 @@ group_op_code (VALUE vop)
232
311
  Empty / all-masked groups follow the CArray zero-contribution contract: an
233
312
  identity-bearing op returns its identity (sum 0, prod 1, count 0, all true,
234
313
  any false), a ratio / extremum returns UNDEF. Sample variance / stddev:
235
- n == 0 UNDEF, n == 1 -> 0.0, n >= 2 the formula.
314
+ n < 2 UNDEF, n >= 2 the formula.
236
315
  */
237
316
  static VALUE
238
317
  rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
@@ -243,7 +322,7 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
243
322
  GetCArray(self, src);
244
323
 
245
324
  if ( src->ndim <= 0 ) {
246
- rb_raise(rb_eRuntimeError, "axis_group_reduce: scalar source");
325
+ rb_raise(rb_eArgError, "axis_group_reduce: scalar source");
247
326
  }
248
327
 
249
328
  /* --- group (slab) axes --- */
@@ -255,7 +334,6 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
255
334
  int8_t axes[CA_RANK_MAX];
256
335
  char is_group[CA_RANK_MAX];
257
336
  for ( int8_t i = 0; i < src->ndim; i++ ) is_group[i] = 0;
258
- ca_size_t group_prod = 1;
259
337
  for ( long i = 0; i < ngroup; i++ ) {
260
338
  int a = NUM2INT(RARRAY_AREF(vgaxes, i));
261
339
  if ( a < 0 || a >= src->ndim ) {
@@ -269,7 +347,6 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
269
347
  }
270
348
  is_group[a] = 1;
271
349
  axes[i] = (int8_t) a;
272
- group_prod *= src->dim[a];
273
350
  }
274
351
 
275
352
  /* --- bundles: small per-group code tables (metadata, kept alive) --- */
@@ -303,12 +380,23 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
303
380
  rb_raise(rb_eArgError, "axis_group_reduce: bundle k must be positive");
304
381
  }
305
382
 
306
- /* int32 view of the codes (metadata-sized, O(consumed-axis dims)) */
383
+ /* The code table is copied out and the source let go of at once, rather
384
+ than held attached for the length of the walk. Everything between here
385
+ and the end of the walk can raise -- a validation just below, a gather
386
+ that cannot convert a cell, an object-lane callback into Ruby -- and a
387
+ raise jumps over every detach, leaving the source materialised with
388
+ nothing left to release it. The copy rides a temporary buffer that the
389
+ unwind collects. */
307
390
  VALUE v32 = rb_ca_wrap_readonly(vcodes, INT2NUM(CA_INT32));
308
391
  rb_ary_push((VALUE) keep, v32);
309
392
  GetCArray(v32, bundle_ca[bi]);
310
- ca_attach(bundle_ca[bi]);
311
- bundle_codes[bi] = (int32_t *) bundle_ca[bi]->ptr;
393
+ {
394
+ size_t nbytes = (size_t) bundle_ca[bi]->elements * sizeof(int32_t);
395
+ VALUE vbuf = rb_str_tmp_new((long) nbytes);
396
+ rb_ary_push((VALUE) keep, vbuf);
397
+ ca_copy_data(bundle_ca[bi], RSTRING_PTR(vbuf));
398
+ bundle_codes[bi] = (int32_t *) RSTRING_PTR(vbuf);
399
+ }
312
400
 
313
401
  int nb = (int) RARRAY_LEN(vbaxes);
314
402
  if ( nb <= 0 || nb > src->ndim ) {
@@ -363,13 +451,21 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
363
451
  }
364
452
  }
365
453
 
366
- /* --- band layout + output shape [K_total, *band_dims] --- */
367
- ca_size_t band = (group_prod > 0) ? (src->elements / group_prod) : 0;
454
+ /* --- band layout + output shape [K_total, *band_dims] ---
455
+ band is the product of the band dims, taken directly rather than as
456
+ src->elements / group_prod: a zero-length *group* axis makes group_prod
457
+ zero, and the division has to answer something. Answering 0 made nout 0
458
+ while the output really had K_total x band cells, so every finalisation
459
+ loop below ran zero times and the freshly zeroed buffer went back to the
460
+ caller unmasked -- a mean, a min and a variance all reported as 0.0.
461
+ Taken directly, nout == co->elements for every shape, and the group with
462
+ no cell in it takes the same empty-group path as any other. */
463
+ ca_size_t band = 1;
368
464
  ca_size_t odim[CA_RANK_MAX];
369
465
  int8_t ondim = 1;
370
466
  odim[0] = K_total;
371
467
  for ( int8_t i = 0; i < src->ndim; i++ ) {
372
- if ( ! is_group[i] ) odim[ondim++] = src->dim[i];
468
+ if ( ! is_group[i] ) { odim[ondim++] = src->dim[i]; band *= src->dim[i]; }
373
469
  }
374
470
  ca_size_t nout = K_total * band;
375
471
 
@@ -380,10 +476,9 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
380
476
  case CA_INT32: case CA_UINT32: case CA_INT64: case CA_UINT64:
381
477
  case CA_FLOAT32: case CA_FLOAT64: break;
382
478
  default:
383
- for ( int bi = 0; bi < n_bundles; bi++ ) ca_detach(bundle_ca[bi]);
384
- rb_raise(rb_eRuntimeError,
385
- "axis_group_reduce: unsupported source data_type %d",
386
- src->data_type);
479
+ rb_raise(rb_eCADataTypeError,
480
+ "axis_group: %"PRIsVALUE" over a group is not supported for data type %s",
481
+ rb_sym2str(vop), ca_type_name[src->data_type]);
387
482
  }
388
483
 
389
484
  /* output data type per op */
@@ -392,35 +487,52 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
392
487
  else if ( op == GR_MINADDR || op == GR_MAXADDR ) out_dt = CA_INT64;
393
488
  else if ( op == GR_ALL || op == GR_ANY ) out_dt = CA_BOOLEAN;
394
489
  else if ( op == GR_ACCUM ) out_dt = src->data_type;
490
+ else if ( op == GR_MIN || op == GR_MAX )
491
+ out_dt = ( src->data_type == CA_BOOLEAN ) ? CA_UINT64 : src->data_type;
395
492
  VALUE vout = rb_carray_new(out_dt, ondim, odim, 0, NULL);
396
493
  GetCArray(vout, co);
397
494
 
398
495
  /* --- accumulator buffers (only those the op needs; all O(nout)) --- */
399
- ca_size_t *cnt = ALLOC_N(ca_size_t, nout); MEMZERO(cnt, ca_size_t, nout);
400
- double *sum = NULL, *sumsq = NULL, *prod = NULL, *mn = NULL, *mx = NULL;
496
+ /* Scratch that the unwind collects. A raise anywhere in the walk -- an
497
+ object-lane callback into Ruby, a gather that cannot convert a cell --
498
+ jumps over every release below, so these ride ALLOCV rather than plain
499
+ malloc: the temporary buffer behind each holder is freed whether the
500
+ function returns or unwinds. */
501
+ volatile VALUE h_cnt = 0, h_sum = 0, h_sumsq = 0, h_prod = 0, h_seen = 0,
502
+ h_xbuf = 0, h_nz = 0, h_mnaddr = 0, h_mxaddr = 0, h_band = 0;
503
+ ca_size_t *cnt = ALLOCV_N(ca_size_t, h_cnt, nout);
504
+ MEMZERO(cnt, ca_size_t, nout);
505
+ double *sum = NULL, *sumsq = NULL, *prod = NULL;
506
+ boolean8_t *seen_num = NULL; /* has a number (not a NaN) landed here? */
507
+ char *xbuf = NULL; /* running extremum, in the source type */
401
508
  ca_size_t *nz = NULL;
402
509
  int64_t *mnaddr = NULL, *mxaddr = NULL; /* flat source addr of min / max */
403
510
  ca_size_t *band_addr = NULL; /* raveled addr of each band cell */
404
511
  if ( op == GR_SUM || op == GR_MEAN || op == GR_VARIANCE || op == GR_STDDEV ||
405
512
  op == GR_VARIANCEP || op == GR_STDDEVP ) {
406
- sum = ALLOC_N(double, nout); MEMZERO(sum, double, nout);
513
+ sum = ALLOCV_N(double, h_sum, nout); MEMZERO(sum, double, nout);
407
514
  }
408
515
  if ( op == GR_PROD ) {
409
- prod = ALLOC_N(double, nout);
516
+ prod = ALLOCV_N(double, h_prod, nout);
410
517
  for ( ca_size_t o = 0; o < nout; o++ ) prod[o] = 1.0;
411
518
  }
412
- if ( op == GR_MIN || op == GR_MINADDR ) {
413
- mn = ALLOC_N(double, nout);
414
- for ( ca_size_t o = 0; o < nout; o++ ) mn[o] = HUGE_VAL;
519
+ if ( op == GR_MIN || op == GR_MAX ||
520
+ op == GR_MINADDR || op == GR_MAXADDR ) {
521
+ seen_num = ALLOCV_N(boolean8_t, h_seen, nout);
522
+ MEMZERO(seen_num, boolean8_t, nout);
415
523
  }
416
- if ( op == GR_MAX || op == GR_MAXADDR ) {
417
- mx = ALLOC_N(double, nout);
418
- for ( ca_size_t o = 0; o < nout; o++ ) mx[o] = -HUGE_VAL;
524
+ if ( op == GR_MIN || op == GR_MAX ) {
525
+ /* the walk writes the extremum straight into the output, in its own type */
526
+ MEMZERO(co->ptr, char, (size_t) nout * co->bytes);
527
+ }
528
+ if ( op == GR_MINADDR || op == GR_MAXADDR ) {
529
+ xbuf = ALLOCV_N(char, h_xbuf, (size_t) nout * src->bytes);
530
+ MEMZERO(xbuf, char, (size_t) nout * src->bytes);
419
531
  }
420
- if ( op == GR_MINADDR ) { mnaddr = ALLOC_N(int64_t, nout); MEMZERO(mnaddr, int64_t, nout); }
421
- if ( op == GR_MAXADDR ) { mxaddr = ALLOC_N(int64_t, nout); MEMZERO(mxaddr, int64_t, nout); }
532
+ if ( op == GR_MINADDR ) { mnaddr = ALLOCV_N(int64_t, h_mnaddr, nout); MEMZERO(mnaddr, int64_t, nout); }
533
+ if ( op == GR_MAXADDR ) { mxaddr = ALLOCV_N(int64_t, h_mxaddr, nout); MEMZERO(mxaddr, int64_t, nout); }
422
534
  if ( op == GR_ALL || op == GR_ANY ) {
423
- nz = ALLOC_N(ca_size_t, nout); MEMZERO(nz, ca_size_t, nout);
535
+ nz = ALLOCV_N(ca_size_t, h_nz, nout); MEMZERO(nz, ca_size_t, nout);
424
536
  }
425
537
 
426
538
  /* min_addr / max_addr need the flat raveled source address of each cell.
@@ -436,7 +548,7 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
436
548
  int band_axis[CA_RANK_MAX]; int nband_axes = 0;
437
549
  for ( int8_t k = 0; k < src->ndim; k++ )
438
550
  if ( ! is_group[k] ) band_axis[nband_axes++] = k;
439
- band_addr = ALLOC_N(ca_size_t, band > 0 ? band : 1);
551
+ band_addr = ALLOCV_N(ca_size_t, h_band, band > 0 ? band : 1);
440
552
  for ( ca_size_t bb = 0; bb < band; bb++ ) {
441
553
  ca_size_t rem = bb, addr = 0;
442
554
  for ( int j = nband_axes - 1; j >= 0; j-- ) { /* last band axis fastest */
@@ -459,27 +571,13 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
459
571
  GROUP_DISPATCH( cnt[o] += 1; sum[o] += v; );
460
572
  for ( ca_size_t o = 0; o < nout; o++ )
461
573
  if ( cnt[o] > 0 ) sum[o] /= (double) cnt[o]; /* sum -> mean */
462
- sumsq = ALLOC_N(double, nout); MEMZERO(sumsq, double, nout);
574
+ sumsq = ALLOCV_N(double, h_sumsq, nout); MEMZERO(sumsq, double, nout);
463
575
  GROUP_DISPATCH( { double _d = v - sum[o]; sumsq[o] += _d * _d; } );
464
576
  }
465
- else if ( op == GR_MINADDR ) {
466
- GROUP_DISPATCH(
467
- cnt[o] += 1;
468
- if ( v < mn[o] ) {
469
- ca_size_t faddr = band_addr[b] + gaddr;
470
- mn[o] = v; mnaddr[o] = (int64_t) faddr;
471
- }
472
- );
473
- }
474
- else if ( op == GR_MAXADDR ) {
475
- GROUP_DISPATCH(
476
- cnt[o] += 1;
477
- if ( v > mx[o] ) {
478
- ca_size_t faddr = band_addr[b] + gaddr;
479
- mx[o] = v; mxaddr[o] = (int64_t) faddr;
480
- }
481
- );
482
- }
577
+ else if ( op == GR_MINADDR ) { GROUP_DISPATCH_T(GMINADDR); }
578
+ else if ( op == GR_MAXADDR ) { GROUP_DISPATCH_T(GMAXADDR); }
579
+ else if ( op == GR_MIN ) { GROUP_DISPATCH_EXTREMUM(<); }
580
+ else if ( op == GR_MAX ) { GROUP_DISPATCH_EXTREMUM(>); }
483
581
  else if ( op == GR_ACCUM ) {
484
582
  MEMZERO(co->ptr, char, (size_t) nout * co->bytes);
485
583
  switch ( ca->data_type ) {
@@ -502,8 +600,6 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
502
600
  cnt[o] += 1;
503
601
  if ( sum ) sum[o] += v;
504
602
  if ( prod ) prod[o] *= v;
505
- if ( mn ) { if ( v < mn[o] ) mn[o] = v; }
506
- if ( mx ) { if ( v > mx[o] ) mx[o] = v; }
507
603
  if ( nz ) { if ( v != 0.0 ) nz[o] += 1; }
508
604
  );
509
605
  }
@@ -521,8 +617,8 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
521
617
  /* Empty / all-masked groups follow the same zero-contribution contract as
522
618
  CArray reductions (ERI, matching the categorical sibling): an identity-
523
619
  bearing op returns its identity (sum 0, prod 1, count 0, all true, any
524
- false), a ratio / extremum returns UNDEF. Sample variance/stddev: n==0
525
- UNDEF, n==1 -> 0.0 (the n=1 contract), n>=2 the formula. */
620
+ false), a ratio / extremum returns UNDEF. Sample variance/stddev: n<2
621
+ UNDEF (n-1 is zero), n>=2 the formula. */
526
622
  if ( op == GR_COUNT ) {
527
623
  int64_t *out = (int64_t *) co->ptr;
528
624
  for ( ca_size_t o = 0; o < nout; o++ ) out[o] = (int64_t) cnt[o];
@@ -531,13 +627,20 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
531
627
  int64_t *out = (int64_t *) co->ptr;
532
628
  int64_t *addr = ( op == GR_MINADDR ) ? mnaddr : mxaddr;
533
629
  for ( ca_size_t o = 0; o < nout; o++ ) {
534
- if ( cnt[o] == 0 ) { out[o] = 0; MARK_UNDEF(o); } /* empty -> UNDEF */
630
+ /* Nothing present, or nothing but NaN: either way no cell here won,
631
+ and the core answers UNDEF for both. */
632
+ if ( cnt[o] == 0 || ! seen_num[o] ) { out[o] = 0; MARK_UNDEF(o); }
535
633
  else out[o] = addr[o];
536
634
  }
537
635
  }
538
636
  else if ( op == GR_ACCUM ) {
539
637
  /* already folded in place, in the source's own type; empty groups hold 0 */
540
638
  }
639
+ else if ( op == GR_MIN || op == GR_MAX ) {
640
+ /* the walk wrote the extremum in place, in the source's own type; a group
641
+ with no cell in it has nothing to report */
642
+ for ( ca_size_t o = 0; o < nout; o++ ) if ( cnt[o] == 0 ) MARK_UNDEF(o);
643
+ }
541
644
  else if ( op == GR_ALL ) {
542
645
  boolean8_t *out = (boolean8_t *) co->ptr; /* empty -> true (vacuous) */
543
646
  for ( ca_size_t o = 0; o < nout; o++ )
@@ -558,16 +661,9 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
558
661
  if ( cnt[o] == 0 ) { out[o] = 0.0; MARK_UNDEF(o); }
559
662
  else out[o] = sum[o] / (double) cnt[o];
560
663
  break;
561
- case GR_MIN:
562
- if ( cnt[o] == 0 ) { out[o] = 0.0; MARK_UNDEF(o); } else out[o] = mn[o];
563
- break;
564
- case GR_MAX:
565
- if ( cnt[o] == 0 ) { out[o] = 0.0; MARK_UNDEF(o); } else out[o] = mx[o];
566
- break;
567
664
  case GR_VARIANCE:
568
665
  case GR_STDDEV:
569
- if ( cnt[o] == 0 ) { out[o] = 0.0; MARK_UNDEF(o); }
570
- else if ( cnt[o] == 1 ) { out[o] = 0.0; } /* n=1 contract */
666
+ if ( cnt[o] < 2 ) { out[o] = 0.0; MARK_UNDEF(o); }
571
667
  else { double var = sumsq[o] / ( (double) cnt[o] - 1.0 );
572
668
  out[o] = ( op == GR_STDDEV ) ? sqrt(var) : var; }
573
669
  break;
@@ -582,17 +678,10 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
582
678
  }
583
679
  #undef MARK_UNDEF
584
680
 
585
- for ( int bi = 0; bi < n_bundles; bi++ ) ca_detach(bundle_ca[bi]);
586
- xfree(cnt);
587
- if ( sum ) xfree(sum);
588
- if ( sumsq ) xfree(sumsq);
589
- if ( prod ) xfree(prod);
590
- if ( mn ) xfree(mn);
591
- if ( mx ) xfree(mx);
592
- if ( nz ) xfree(nz);
593
- if ( mnaddr ) xfree(mnaddr);
594
- if ( mxaddr ) xfree(mxaddr);
595
- if ( band_addr ) xfree(band_addr);
681
+ ALLOCV_END(h_cnt); ALLOCV_END(h_sum); ALLOCV_END(h_sumsq);
682
+ ALLOCV_END(h_prod); ALLOCV_END(h_seen); ALLOCV_END(h_xbuf);
683
+ ALLOCV_END(h_nz); ALLOCV_END(h_mnaddr); ALLOCV_END(h_mxaddr);
684
+ ALLOCV_END(h_band);
596
685
 
597
686
  RB_GC_GUARD(keep);
598
687
  return vout;
@@ -763,7 +852,9 @@ group_scan_build_plan (ca_iter_state *st, boolean8_t *m,
763
852
  emits its own value: a per-group `seen` byte initialises the accumulator
764
853
  lazily on first hit — no sentinel like HUGE_VAL, which an integer data type could
765
854
  not represent. CMP is > for max, < for min: a later member replaces the
766
- running extremum when `rv CMP acc`. A cell masked within its group holds the
855
+ running extremum when CA_EXTREMUM_REPLACES (a NaN never does, and a
856
+ number displaces a leading NaN, as the core cummax / cummin and the
857
+ reduce twins above do). A cell masked within its group holds the
767
858
  current extremum once a member has been seen (output NOT masked, like sum);
768
859
  before any member the extremum is undefined so the cell stays UNDEF (the
769
860
  CArray reduction contract: empty max/min has no value — deliberately NOT the
@@ -776,7 +867,8 @@ group_scan_build_plan (ca_iter_state *st, boolean8_t *m,
776
867
  boolean8_t *m; \
777
868
  ca_size_t b = 0; \
778
869
  int ready = 0; \
779
- T *acce = ALLOC_N(T, K_total); \
870
+ volatile VALUE h_acce = 0; /* freed by the unwind if the walk raises */ \
871
+ T *acce = ALLOCV_N(T, h_acce, K_total); \
780
872
  T *outp = (T *) co->ptr; \
781
873
  CA_FOR_EACH_SLAB(st, ca, axes, (int8_t) ngroup, CA_KERNEL_READ, p, m) { \
782
874
  ca_size_t SE = st.slab_elements; \
@@ -803,13 +895,15 @@ group_scan_build_plan (ca_iter_state *st, boolean8_t *m,
803
895
  } \
804
896
  T rv = *(T *)(p + sw_doff[e]); \
805
897
  if ( ! seen[code] ) { acce[code] = rv; seen[code] = 1; } \
806
- else if ( rv CMP acce[code] ) { acce[code] = rv; } \
898
+ else if ( CA_EXTREMUM_REPLACES(rv, acce[code], CMP) ) { \
899
+ acce[code] = rv; \
900
+ } \
807
901
  outp[addr] = acce[code]; \
808
902
  } \
809
903
  } \
810
904
  b++; \
811
905
  } \
812
- xfree(acce); \
906
+ ALLOCV_END(h_acce); \
813
907
  } while (0)
814
908
 
815
909
  #define GROUP_SCAN_EXTREMUM_DISPATCH(CMP) \
@@ -841,19 +935,19 @@ group_scan_build_plan (ca_iter_state *st, boolean8_t *m,
841
935
  both unmasked (matching core object cumsum / cumprod); cummax / cummin hold
842
936
  the running VALUE only once a member has been seen and stay UNDEF before that
843
937
  (no identity for arbitrary objects). GW_SKIP (excluded) stays UNDEF. accn /
844
- seen are reset at each band; acco carries the running VALUE. */
938
+ seen are reset at each band; acco carries the running VALUE.
939
+ Expanded only in group_scan_object_body, whose walk state is c->st. */
845
940
  #define GROUP_SCAN_OBJECT_WALK(EMIT, HOLD) \
846
941
  do { \
847
- ca_iter_state st; \
848
942
  char *p; \
849
943
  boolean8_t *m; \
850
944
  ca_size_t b = 0; \
851
945
  int ready = 0; \
852
- CA_FOR_EACH_SLAB(st, ca, axes, (int8_t) ngroup, CA_KERNEL_READ, p, m) { \
853
- ca_size_t SE = st.slab_elements; \
946
+ CA_FOR_EACH_SLAB(c->st, ca, axes, (int8_t) ngroup, CA_KERNEL_READ, p, m) {\
947
+ ca_size_t SE = c->st.slab_elements; \
854
948
  if ( SE > 0 ) { \
855
949
  if ( ! ready ) { \
856
- group_scan_build_plan(&st, m, n_bundles, bundle_codes, bundle_k, \
950
+ group_scan_build_plan(&c->st, m, n_bundles, bundle_codes, bundle_k,\
857
951
  bundle_placeval, bundle_nconsumed, \
858
952
  bundle_slot, bundle_cstride, ngroup, \
859
953
  grstride, sw_code, sw_doff, sw_moff, sw_addr);\
@@ -878,6 +972,137 @@ group_scan_build_plan (ca_iter_state *st, boolean8_t *m,
878
972
  } \
879
973
  } while (0)
880
974
 
975
+ /* Marks output cell o UNDEF, creating the output mask on first use. Used
976
+ by the scan walks, in functions that hold the output as `co` and the mask
977
+ pointer as `omask`. */
978
+ #define MARK_OUT_UNDEF(o) do { \
979
+ if ( ! omask ) { \
980
+ ca_create_mask(co); \
981
+ omask = (boolean8_t *) co->mask->ptr; \
982
+ } \
983
+ omask[o] = 1; \
984
+ } while (0)
985
+
986
+ /* The CA_OBJECT lane of __axis_group_scan__. Every cell calls into Ruby,
987
+ so the walk can raise part way; its state lives in the caller's frame, in
988
+ c->st, and ca_iter_ensure finishes it however this body leaves. The
989
+ buffers are ALLOCV-backed, so the unwind collects them. */
990
+ typedef struct {
991
+ ca_iter_state st;
992
+ CArray *ca;
993
+ CArray *co;
994
+ int op;
995
+ int8_t *axes;
996
+ long ngroup;
997
+ int n_bundles;
998
+ int32_t **bundle_codes;
999
+ ca_size_t *bundle_k;
1000
+ ca_size_t *bundle_placeval;
1001
+ int *bundle_nconsumed;
1002
+ int (*bundle_slot)[CA_RANK_MAX];
1003
+ ca_size_t (*bundle_cstride)[CA_RANK_MAX];
1004
+ ca_size_t *grstride;
1005
+ ca_size_t *band_addr;
1006
+ ca_size_t K_total;
1007
+ ca_size_t *sw_code;
1008
+ ca_size_t *sw_doff;
1009
+ ca_size_t *sw_moff;
1010
+ ca_size_t *sw_addr;
1011
+ ca_size_t *accn;
1012
+ char *seen;
1013
+ VALUE *acco;
1014
+ } group_scan_object_t;
1015
+
1016
+ static VALUE
1017
+ group_scan_object_body (VALUE arg)
1018
+ {
1019
+ group_scan_object_t *c = (group_scan_object_t *) arg;
1020
+ CArray *ca = c->ca;
1021
+ CArray *co = c->co;
1022
+ int op = c->op;
1023
+ int8_t *axes = c->axes;
1024
+ long ngroup = c->ngroup;
1025
+ int n_bundles = c->n_bundles;
1026
+ int32_t **bundle_codes = c->bundle_codes;
1027
+ ca_size_t *bundle_k = c->bundle_k;
1028
+ ca_size_t *bundle_placeval = c->bundle_placeval;
1029
+ int *bundle_nconsumed = c->bundle_nconsumed;
1030
+ int (*bundle_slot)[CA_RANK_MAX] = c->bundle_slot;
1031
+ ca_size_t (*bundle_cstride)[CA_RANK_MAX] = c->bundle_cstride;
1032
+ ca_size_t *grstride = c->grstride;
1033
+ ca_size_t *band_addr = c->band_addr;
1034
+ ca_size_t K_total = c->K_total;
1035
+ ca_size_t *sw_code = c->sw_code;
1036
+ ca_size_t *sw_doff = c->sw_doff;
1037
+ ca_size_t *sw_moff = c->sw_moff;
1038
+ ca_size_t *sw_addr = c->sw_addr;
1039
+ ca_size_t *accn = c->accn;
1040
+ char *seen = c->seen;
1041
+ VALUE *acco = c->acco;
1042
+ boolean8_t *omask = NULL;
1043
+
1044
+ if ( op == GS_CUMCOUNT ) { /* int64 1-based running count */
1045
+ int64_t *outi = (int64_t *) co->ptr;
1046
+ GROUP_SCAN_OBJECT_WALK(
1047
+ accn[code] += 1; outi[addr] = (int64_t) accn[code];,
1048
+ outi[addr] = (int64_t) accn[code];
1049
+ );
1050
+ }
1051
+ else { /* object running VALUE */
1052
+ VALUE *outo = (VALUE *) co->ptr;
1053
+ switch ( op ) {
1054
+ case GS_CUMSUM:
1055
+ /* Identity 0 (matching the core object cumsum): a group's acc is lazily
1056
+ seeded to 0 (Fixnum, so 0 + ev promotes to ev's class), so a cell
1057
+ masked before any present member holds 0 unmasked and an all-masked
1058
+ group yields 0 everywhere. seen doubles as the per-band init flag. */
1059
+ GROUP_SCAN_OBJECT_WALK(
1060
+ if ( ! seen[code] ) { acco[code] = INT2FIX(0); seen[code] = 1; }
1061
+ acco[code] = rb_funcall(acco[code], rb_intern("+"), 1, ev);
1062
+ outo[addr] = acco[code];,
1063
+ if ( ! seen[code] ) { acco[code] = INT2FIX(0); seen[code] = 1; }
1064
+ outo[addr] = acco[code];
1065
+ );
1066
+ break;
1067
+ case GS_CUMPROD:
1068
+ /* Identity 1, same lazy-init as cumsum. */
1069
+ GROUP_SCAN_OBJECT_WALK(
1070
+ if ( ! seen[code] ) { acco[code] = INT2FIX(1); seen[code] = 1; }
1071
+ acco[code] = rb_funcall(acco[code], rb_intern("*"), 1, ev);
1072
+ outo[addr] = acco[code];,
1073
+ if ( ! seen[code] ) { acco[code] = INT2FIX(1); seen[code] = 1; }
1074
+ outo[addr] = acco[code];
1075
+ );
1076
+ break;
1077
+ case GS_CUMMAX:
1078
+ /* No identity: seed from the first member, hold only once seen; a cell
1079
+ masked before any member stays UNDEF (empty-max contract). */
1080
+ GROUP_SCAN_OBJECT_WALK(
1081
+ if ( ! seen[code] ) { acco[code] = ev; seen[code] = 1; }
1082
+ else if ( ca_obj_extremum_replaces(ev, acco[code], rb_intern(">")) ) {
1083
+ acco[code] = ev;
1084
+ }
1085
+ outo[addr] = acco[code];,
1086
+ if ( seen[code] ) { outo[addr] = acco[code]; }
1087
+ else { MARK_OUT_UNDEF(addr); }
1088
+ );
1089
+ break;
1090
+ case GS_CUMMIN:
1091
+ GROUP_SCAN_OBJECT_WALK(
1092
+ if ( ! seen[code] ) { acco[code] = ev; seen[code] = 1; }
1093
+ else if ( ca_obj_extremum_replaces(ev, acco[code], rb_intern("<")) ) {
1094
+ acco[code] = ev;
1095
+ }
1096
+ outo[addr] = acco[code];,
1097
+ if ( seen[code] ) { outo[addr] = acco[code]; }
1098
+ else { MARK_OUT_UNDEF(addr); }
1099
+ );
1100
+ break;
1101
+ }
1102
+ }
1103
+ return Qnil;
1104
+ }
1105
+
881
1106
  static int
882
1107
  group_scan_op_code (VALUE vop)
883
1108
  {
@@ -887,7 +1112,7 @@ group_scan_op_code (VALUE vop)
887
1112
  else if ( id == rb_intern("cummax") ) return GS_CUMMAX;
888
1113
  else if ( id == rb_intern("cummin") ) return GS_CUMMIN;
889
1114
  else if ( id == rb_intern("cumprod") ) return GS_CUMPROD;
890
- rb_raise(rb_eArgError, "axis_group_scan: unsupported op :%s", rb_id2name(id));
1115
+ rb_raise(rb_eArgError, "axis_group_scan: unsupported op :%"PRIsVALUE, rb_id2str(id));
891
1116
  }
892
1117
 
893
1118
  /* __axis_group_scan__(group_axes, bundles, op) — group-keyed segment scan of
@@ -930,7 +1155,7 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
930
1155
  GetCArray(self, src);
931
1156
 
932
1157
  if ( src->ndim <= 0 ) {
933
- rb_raise(rb_eRuntimeError, "axis_group_scan: scalar source");
1158
+ rb_raise(rb_eArgError, "axis_group_scan: scalar source");
934
1159
  }
935
1160
 
936
1161
  /* --- group (slab) axes (same validation as reduce) --- */
@@ -990,11 +1215,23 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
990
1215
  rb_raise(rb_eArgError, "axis_group_scan: bundle k must be positive");
991
1216
  }
992
1217
 
1218
+ /* The code table is copied out and the source let go of at once, rather
1219
+ than held attached for the length of the walk. Everything between here
1220
+ and the end of the walk can raise -- a validation just below, a gather
1221
+ that cannot convert a cell, an object-lane callback into Ruby -- and a
1222
+ raise jumps over every detach, leaving the source materialised with
1223
+ nothing left to release it. The copy rides a temporary buffer that the
1224
+ unwind collects. */
993
1225
  VALUE v32 = rb_ca_wrap_readonly(vcodes, INT2NUM(CA_INT32));
994
1226
  rb_ary_push((VALUE) keep, v32);
995
1227
  GetCArray(v32, bundle_ca[bi]);
996
- ca_attach(bundle_ca[bi]);
997
- bundle_codes[bi] = (int32_t *) bundle_ca[bi]->ptr;
1228
+ {
1229
+ size_t nbytes = (size_t) bundle_ca[bi]->elements * sizeof(int32_t);
1230
+ VALUE vbuf = rb_str_tmp_new((long) nbytes);
1231
+ rb_ary_push((VALUE) keep, vbuf);
1232
+ ca_copy_data(bundle_ca[bi], RSTRING_PTR(vbuf));
1233
+ bundle_codes[bi] = (int32_t *) RSTRING_PTR(vbuf);
1234
+ }
998
1235
 
999
1236
  int nb = (int) RARRAY_LEN(vbaxes);
1000
1237
  if ( nb <= 0 || nb > src->ndim ) {
@@ -1054,8 +1291,7 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
1054
1291
  case CA_INT32: case CA_UINT32: case CA_INT64: case CA_UINT64:
1055
1292
  case CA_FLOAT32: case CA_FLOAT64: case CA_OBJECT: break;
1056
1293
  default:
1057
- for ( int bi = 0; bi < n_bundles; bi++ ) ca_detach(bundle_ca[bi]);
1058
- rb_raise(rb_eRuntimeError,
1294
+ rb_raise(rb_eCADataTypeError,
1059
1295
  "axis_group_scan: unsupported source data_type %d",
1060
1296
  src->data_type);
1061
1297
  }
@@ -1091,7 +1327,8 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
1091
1327
  int band_axis[CA_RANK_MAX]; int nband_axes = 0;
1092
1328
  for ( int8_t k = 0; k < src->ndim; k++ )
1093
1329
  if ( ! is_group[k] ) band_axis[nband_axes++] = k;
1094
- ca_size_t *band_addr = ALLOC_N(ca_size_t, band > 0 ? band : 1);
1330
+ volatile VALUE h_band2 = 0;
1331
+ ca_size_t *band_addr = ALLOCV_N(ca_size_t, h_band2, band > 0 ? band : 1);
1095
1332
  for ( ca_size_t bb = 0; bb < band; bb++ ) {
1096
1333
  ca_size_t rem = bb, addr = 0;
1097
1334
  for ( int j = nband_axes - 1; j >= 0; j-- ) {
@@ -1108,86 +1345,41 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
1108
1345
  member / object identity init) are all tiny; acco (object running VALUE) is
1109
1346
  allocated only for the object arithmetic / extremum ops. */
1110
1347
  ca_size_t plan_n = (group_prod > 0) ? group_prod : 1;
1111
- ca_size_t *sw_code = ALLOC_N(ca_size_t, plan_n);
1112
- ca_size_t *sw_doff = ALLOC_N(ca_size_t, plan_n);
1113
- ca_size_t *sw_moff = ALLOC_N(ca_size_t, plan_n);
1114
- ca_size_t *sw_addr = ALLOC_N(ca_size_t, plan_n);
1115
- double *accd = ALLOC_N(double, K_total);
1116
- ca_size_t *accn = ALLOC_N(ca_size_t, K_total);
1117
- char *seen = ALLOC_N(char, K_total);
1348
+ /* Scratch that the unwind collects. The object lane calls back into Ruby
1349
+ for every cell, so a raise in the middle of the walk is ordinary here --
1350
+ an operand that will not coerce, a <=> that answers nil -- and it jumps
1351
+ over every release below. ALLOCV frees the buffer behind each holder
1352
+ whether the function returns or unwinds. */
1353
+ volatile VALUE h_code = 0, h_doff = 0, h_moff = 0, h_addr = 0,
1354
+ h_accd = 0, h_accn = 0, h_seen = 0, h_acco = 0;
1355
+ ca_size_t *sw_code = ALLOCV_N(ca_size_t, h_code, plan_n);
1356
+ ca_size_t *sw_doff = ALLOCV_N(ca_size_t, h_doff, plan_n);
1357
+ ca_size_t *sw_moff = ALLOCV_N(ca_size_t, h_moff, plan_n);
1358
+ ca_size_t *sw_addr = ALLOCV_N(ca_size_t, h_addr, plan_n);
1359
+ double *accd = ALLOCV_N(double, h_accd, K_total);
1360
+ ca_size_t *accn = ALLOCV_N(ca_size_t, h_accn, K_total);
1361
+ char *seen = ALLOCV_N(char, h_seen, K_total);
1118
1362
  VALUE *acco = NULL;
1119
1363
  double acc_init = ( op == GS_CUMPROD ) ? 1.0 : 0.0;
1120
1364
 
1121
1365
  boolean8_t *omask = NULL;
1122
- #define MARK_OUT_UNDEF(o) do { \
1123
- if ( ! omask ) { \
1124
- ca_create_mask(co); \
1125
- omask = (boolean8_t *) co->mask->ptr; \
1126
- } \
1127
- omask[o] = 1; \
1128
- } while (0)
1129
1366
 
1130
1367
  if ( src->data_type == CA_OBJECT ) {
1131
- if ( op == GS_CUMCOUNT ) { /* int64 1-based running count */
1132
- int64_t *outi = (int64_t *) co->ptr;
1133
- GROUP_SCAN_OBJECT_WALK(
1134
- accn[code] += 1; outi[addr] = (int64_t) accn[code];,
1135
- outi[addr] = (int64_t) accn[code];
1136
- );
1137
- }
1138
- else { /* object running VALUE */
1139
- VALUE *outo = (VALUE *) co->ptr;
1140
- acco = ALLOC_N(VALUE, K_total);
1141
- switch ( op ) {
1142
- case GS_CUMSUM:
1143
- /* Identity 0 (matching the core object cumsum): a group's acc is lazily
1144
- seeded to 0 (Fixnum, so 0 + ev promotes to ev's class), so a cell
1145
- masked before any present member holds 0 unmasked and an all-masked
1146
- group yields 0 everywhere. seen doubles as the per-band init flag. */
1147
- GROUP_SCAN_OBJECT_WALK(
1148
- if ( ! seen[code] ) { acco[code] = INT2FIX(0); seen[code] = 1; }
1149
- acco[code] = rb_funcall(acco[code], rb_intern("+"), 1, ev);
1150
- outo[addr] = acco[code];,
1151
- if ( ! seen[code] ) { acco[code] = INT2FIX(0); seen[code] = 1; }
1152
- outo[addr] = acco[code];
1153
- );
1154
- break;
1155
- case GS_CUMPROD:
1156
- /* Identity 1, same lazy-init as cumsum. */
1157
- GROUP_SCAN_OBJECT_WALK(
1158
- if ( ! seen[code] ) { acco[code] = INT2FIX(1); seen[code] = 1; }
1159
- acco[code] = rb_funcall(acco[code], rb_intern("*"), 1, ev);
1160
- outo[addr] = acco[code];,
1161
- if ( ! seen[code] ) { acco[code] = INT2FIX(1); seen[code] = 1; }
1162
- outo[addr] = acco[code];
1163
- );
1164
- break;
1165
- case GS_CUMMAX:
1166
- /* No identity: seed from the first member, hold only once seen; a cell
1167
- masked before any member stays UNDEF (empty-max contract). */
1168
- GROUP_SCAN_OBJECT_WALK(
1169
- if ( ! seen[code] ) { acco[code] = ev; seen[code] = 1; }
1170
- else if ( NUM2INT(rb_funcall(ev, rb_intern("<=>"), 1, acco[code])) > 0 ) {
1171
- acco[code] = ev;
1172
- }
1173
- outo[addr] = acco[code];,
1174
- if ( seen[code] ) { outo[addr] = acco[code]; }
1175
- else { MARK_OUT_UNDEF(addr); }
1176
- );
1177
- break;
1178
- case GS_CUMMIN:
1179
- GROUP_SCAN_OBJECT_WALK(
1180
- if ( ! seen[code] ) { acco[code] = ev; seen[code] = 1; }
1181
- else if ( NUM2INT(rb_funcall(ev, rb_intern("<=>"), 1, acco[code])) < 0 ) {
1182
- acco[code] = ev;
1183
- }
1184
- outo[addr] = acco[code];,
1185
- if ( seen[code] ) { outo[addr] = acco[code]; }
1186
- else { MARK_OUT_UNDEF(addr); }
1187
- );
1188
- break;
1189
- }
1368
+ group_scan_object_t c;
1369
+ if ( op != GS_CUMCOUNT ) {
1370
+ acco = ALLOCV_N(VALUE, h_acco, K_total);
1190
1371
  }
1372
+ c.ca = ca; c.co = co; c.op = op;
1373
+ c.axes = axes; c.ngroup = ngroup;
1374
+ c.n_bundles = n_bundles; c.bundle_codes = bundle_codes;
1375
+ c.bundle_k = bundle_k; c.bundle_placeval = bundle_placeval;
1376
+ c.bundle_nconsumed = bundle_nconsumed;
1377
+ c.bundle_slot = bundle_slot; c.bundle_cstride = bundle_cstride;
1378
+ c.grstride = grstride; c.band_addr = band_addr; c.K_total = K_total;
1379
+ c.sw_code = sw_code; c.sw_doff = sw_doff;
1380
+ c.sw_moff = sw_moff; c.sw_addr = sw_addr;
1381
+ c.accn = accn; c.seen = seen; c.acco = acco;
1382
+ ca_iter_ensure(1, &c.st, group_scan_object_body, (VALUE) &c);
1191
1383
  }
1192
1384
  else { /* native data type dispatch */
1193
1385
  switch ( op ) {
@@ -1217,18 +1409,10 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
1217
1409
  break;
1218
1410
  }
1219
1411
  }
1220
- #undef MARK_OUT_UNDEF
1221
-
1222
- for ( int bi = 0; bi < n_bundles; bi++ ) ca_detach(bundle_ca[bi]);
1223
- xfree(band_addr);
1224
- xfree(sw_code);
1225
- xfree(sw_doff);
1226
- xfree(sw_moff);
1227
- xfree(sw_addr);
1228
- xfree(accd);
1229
- xfree(accn);
1230
- xfree(seen);
1231
- if ( acco ) xfree(acco);
1412
+
1413
+ ALLOCV_END(h_band2); ALLOCV_END(h_code); ALLOCV_END(h_doff);
1414
+ ALLOCV_END(h_moff); ALLOCV_END(h_addr); ALLOCV_END(h_accd);
1415
+ ALLOCV_END(h_accn); ALLOCV_END(h_seen); ALLOCV_END(h_acco);
1232
1416
 
1233
1417
  RB_GC_GUARD(keep);
1234
1418
  return vout;