carray 3.0.1 → 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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -0
  3. data/README.md +2 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +33 -4
  6. data/ext/ca_axis_group.c +202 -96
  7. data/ext/ca_categorical_iterator.c +108 -54
  8. data/ext/ca_kernel_iterator.c +317 -51
  9. data/ext/ca_kernel_iterator.h +142 -35
  10. data/ext/ca_obj_array.c +62 -20
  11. data/ext/ca_obj_block.c +4 -4
  12. data/ext/ca_obj_const_string.c +85 -26
  13. data/ext/ca_obj_face.c +24 -0
  14. data/ext/ca_obj_face.h +15 -0
  15. data/ext/ca_obj_fixlen_string.c +18 -5
  16. data/ext/ca_obj_meld.c +123 -25
  17. data/ext/ca_obj_object.c +8 -0
  18. data/ext/ca_obj_select.c +49 -34
  19. data/ext/ca_obj_stack.c +3 -8
  20. data/ext/ca_obj_stride.c +72 -1
  21. data/ext/ca_obj_string.c +8 -4
  22. data/ext/ca_obj_window.c +8 -2
  23. data/ext/ca_op_ipower.c +1 -2
  24. data/ext/ca_rng_normal.h +42 -0
  25. data/ext/ca_rng_xoshiro256pp.h +105 -0
  26. data/ext/ca_sweep_engine.c +307 -143
  27. data/ext/ca_sweep_engine.h +26 -5
  28. data/ext/carray.h +21 -2
  29. data/ext/carray_access.c +32 -20
  30. data/ext/carray_address_basis.c +590 -0
  31. data/ext/carray_broadcast.c +3 -3
  32. data/ext/carray_call_cfunc.c +667 -483
  33. data/ext/carray_cast.c +115 -41
  34. data/ext/carray_copy.c +55 -30
  35. data/ext/carray_core.c +83 -3
  36. data/ext/carray_count.c +9 -10
  37. data/ext/carray_factorize.c +46 -25
  38. data/ext/carray_internal.h +17 -0
  39. data/ext/carray_kernels_reduce_aggregate.c +168 -0
  40. data/ext/carray_kernels_reduce_cumulative.c +270 -1
  41. data/ext/carray_kernels_reduce_extreme.c +554 -8
  42. data/ext/carray_kernels_scan.c +4 -4
  43. data/ext/carray_kernels_search.c +94 -14
  44. data/ext/carray_loop.c +7 -1
  45. data/ext/carray_mask.c +23 -8
  46. data/ext/carray_median_percentile.c +55 -0
  47. data/ext/carray_operator.c +4 -4
  48. data/ext/carray_order.c +1 -1
  49. data/ext/carray_random.c +384 -40
  50. data/ext/carray_slab.c +13 -0
  51. data/ext/carray_sort.c +20 -22
  52. data/ext/mk_call_cfunc.rb +103 -116
  53. data/ext/mkkernel.rb +297 -29
  54. data/ext/ruby_carray.c +10 -1
  55. data/ext/version.h +4 -4
  56. data/lib/carray/autoload_carray.rb +5 -3
  57. data/lib/carray/autoload_method_extension.rb +12 -0
  58. data/lib/carray/axis_group.rb +77 -0
  59. data/lib/carray/basics.rb +4 -0
  60. data/lib/carray/block_iterator.rb +92 -16
  61. data/lib/carray/categorical.rb +150 -33
  62. data/lib/carray/categorical_iterator.rb +207 -80
  63. data/lib/carray/const_string.rb +131 -27
  64. data/lib/carray/construct.rb +40 -0
  65. data/lib/carray/data_type_extension.rb +3 -0
  66. data/lib/carray/data_type_limits.rb +91 -0
  67. data/lib/carray/fixlen_string.rb +1 -1
  68. data/lib/carray/frame/csv_parser.rb +11 -4
  69. data/lib/carray/frame/frame.rb +81 -10
  70. data/lib/carray/frame/group.rb +36 -3
  71. data/lib/carray/frame/io.rb +67 -15
  72. data/lib/carray/frame/records.rb +18 -4
  73. data/lib/carray/frame/verbs.rb +14 -11
  74. data/lib/carray/inspect.rb +42 -9
  75. data/lib/carray/iterator.rb +143 -0
  76. data/lib/carray/lazy.rb +0 -37
  77. data/lib/carray/mask_gap_fill.rb +3 -1
  78. data/lib/carray/methods/discovery_along.rb +74 -0
  79. data/lib/carray/methods/factorize.rb +50 -0
  80. data/lib/carray/methods/is_in.rb +13 -2
  81. data/lib/carray/methods/locate_addr.rb +75 -2
  82. data/lib/carray/methods/mask_duplicates.rb +35 -1
  83. data/lib/carray/methods/nunique.rb +22 -1
  84. data/lib/carray/methods/repeat.rb +110 -0
  85. data/lib/carray/methods/unique.rb +41 -1
  86. data/lib/carray/rng.rb +86 -0
  87. data/lib/carray/slab_iterator.rb +58 -13
  88. data/lib/carray/string_operation_extension.rb +5 -1
  89. data/lib/carray/time.rb +18 -2
  90. data/lib/carray/window_iterator.rb +142 -20
  91. data/lib/carray.rb +2 -0
  92. data/yard-stubs/ca_obj_block.rb +2 -7
  93. data/yard-stubs/ca_obj_window.rb +10 -2
  94. data/yard-stubs/carray_access.rb +1 -1
  95. data/yard-stubs/carray_broadcast.rb +1 -1
  96. data/yard-stubs/carray_core.rb +0 -80
  97. data/yard-stubs/carray_count.rb +7 -2
  98. data/yard-stubs/carray_lazy.rb +205 -0
  99. data/yard-stubs/carray_math.rb +1486 -3
  100. data/yard-stubs/carray_median_percentile.rb +16 -2
  101. data/yard-stubs/carray_order.rb +9 -69
  102. data/yard-stubs/carray_slab.rb +9 -7
  103. data/yard-stubs/carray_sort.rb +7 -5
  104. metadata +9 -1
data/ext/ca_axis_group.c CHANGED
@@ -110,16 +110,20 @@ group_op_code (VALUE vop)
110
110
  ca_size_t *gw_ocode = NULL, *gw_doff = NULL, *gw_moff = NULL, \
111
111
  *gw_gaddr = NULL; \
112
112
  int gw_ready = 0; \
113
+ /* freed by the unwind too: the walk can raise between here and the \
114
+ releases below (a gather that cannot convert a cell, and in the object \
115
+ lane a callback into Ruby). */ \
116
+ volatile VALUE gw_h1 = 0, gw_h2 = 0, gw_h3 = 0, gw_h4 = 0; \
113
117
  int gw_need_addr = ( op == GR_MINADDR || op == GR_MAXADDR ); \
114
118
  CA_FOR_EACH_SLAB(st, ca, axes, (int8_t) ngroup, CA_KERNEL_READ, p, m) { \
115
119
  int8_t sndim = st.slab_ndim; \
116
120
  ca_size_t SE = st.slab_elements; \
117
121
  if ( SE > 0 ) { \
118
122
  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); \
123
+ gw_ocode = ALLOCV_N(ca_size_t, gw_h1, SE); \
124
+ gw_doff = ALLOCV_N(ca_size_t, gw_h2, SE); \
125
+ gw_moff = ALLOCV_N(ca_size_t, gw_h3, SE); \
126
+ gw_gaddr = ALLOCV_N(ca_size_t, gw_h4, SE); \
123
127
  ca_size_t sidx[CA_RANK_MAX]; \
124
128
  for ( int8_t k = 0; k < sndim; k++ ) sidx[k] = 0; \
125
129
  for ( ca_size_t e = 0; e < SE; e++ ) { \
@@ -158,21 +162,95 @@ group_op_code (VALUE vop)
158
162
  ca_size_t oc = gw_ocode[e]; \
159
163
  if ( oc == GW_SKIP ) continue; \
160
164
  if ( m && m[ gw_moff[e] ] ) continue; \
161
- double v = (double) ( *(T *)(p + gw_doff[e]) ); \
165
+ T rv = *(T *)(p + gw_doff[e]); \
166
+ double v = (double) rv; \
162
167
  ca_size_t o = oc + b; \
163
168
  ca_size_t gaddr = gw_gaddr[e]; \
164
169
  ACCUM; \
165
- (void) v; (void) o; (void) gaddr; \
170
+ (void) rv; (void) v; (void) o; (void) gaddr; \
166
171
  } \
167
172
  } \
168
173
  b++; \
169
174
  } \
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); \
175
+ ALLOCV_END(gw_h1); ALLOCV_END(gw_h2); \
176
+ ALLOCV_END(gw_h3); ALLOCV_END(gw_h4); \
174
177
  } while (0)
175
178
 
179
+ /* GROUP_DISPATCH_T(ACCUM_T): the same walk, for an accumulator that needs to
180
+ name the source type -- one that keeps its answer in that type instead of
181
+ widening to double. ACCUM_T is invoked as ACCUM_T(T). */
182
+ #define GROUP_DISPATCH_T(ACCUM_T) \
183
+ switch ( ca->data_type ) { \
184
+ case CA_BOOLEAN: GROUP_WALK(boolean8_t, ACCUM_T(boolean8_t)); break; \
185
+ case CA_INT8: GROUP_WALK(int8_t, ACCUM_T(int8_t)); break; \
186
+ case CA_UINT8: GROUP_WALK(uint8_t, ACCUM_T(uint8_t)); break; \
187
+ case CA_INT16: GROUP_WALK(int16_t, ACCUM_T(int16_t)); break; \
188
+ case CA_UINT16: GROUP_WALK(uint16_t, ACCUM_T(uint16_t)); break; \
189
+ case CA_INT32: GROUP_WALK(int32_t, ACCUM_T(int32_t)); break; \
190
+ case CA_UINT32: GROUP_WALK(uint32_t, ACCUM_T(uint32_t)); break; \
191
+ case CA_INT64: GROUP_WALK(int64_t, ACCUM_T(int64_t)); break; \
192
+ case CA_UINT64: GROUP_WALK(uint64_t, ACCUM_T(uint64_t)); break; \
193
+ case CA_FLOAT32: GROUP_WALK(float, ACCUM_T(float)); break; \
194
+ case CA_FLOAT64: GROUP_WALK(double, ACCUM_T(double)); break; \
195
+ default: break; \
196
+ }
197
+
198
+ /* An extremum keeps the source data type: its magnitude never grows, so
199
+ widening to double buys nothing and costs exactness -- an int64 past 2^53
200
+ came back rounded, and a genuinely larger value could lose the comparison
201
+ to a smaller one that rounded to the same double. The scan siblings
202
+ (GROUP_SCAN_EXTREMUM_WALK) already held a native accumulator; these are the
203
+ reduce twins.
204
+
205
+ A NaN loses every contest, as it does in CArray's own min / max: it is held
206
+ only while nothing else has landed, the first number displaces it, and a
207
+ group of nothing but NaN answers NaN (for a position, UNDEF). `v` is the
208
+ widened load, so testing it for NaN is the same question for every source
209
+ type and costs an integer body nothing. seen_num[] says whether a number
210
+ has landed; cnt[] still says whether anything has. */
211
+ #define GMINMAX(T, A, CMP) \
212
+ do { \
213
+ A *acc = (A *) co->ptr; \
214
+ A av = (A) rv; \
215
+ if ( v == v ) { \
216
+ if ( ! seen_num[o] ) { acc[o] = av; seen_num[o] = 1; } \
217
+ else if ( av CMP acc[o] ) acc[o] = av; \
218
+ } else if ( cnt[o] == 0 ) acc[o] = av; \
219
+ cnt[o] += 1; \
220
+ } while (0)
221
+
222
+ /* Boolean answers as its 0/1 numeric storage, which is what CArray#min / #max
223
+ do with a boolean array -- the boolean-returning twins are all / any. Every
224
+ other type answers in its own. */
225
+ #define GROUP_DISPATCH_EXTREMUM(CMP) \
226
+ switch ( ca->data_type ) { \
227
+ case CA_BOOLEAN: GROUP_WALK(boolean8_t, GMINMAX(boolean8_t, uint64_t, CMP)); break; \
228
+ case CA_INT8: GROUP_WALK(int8_t, GMINMAX(int8_t, int8_t, CMP)); break; \
229
+ case CA_UINT8: GROUP_WALK(uint8_t, GMINMAX(uint8_t, uint8_t, CMP)); break; \
230
+ case CA_INT16: GROUP_WALK(int16_t, GMINMAX(int16_t, int16_t, CMP)); break; \
231
+ case CA_UINT16: GROUP_WALK(uint16_t, GMINMAX(uint16_t, uint16_t, CMP)); break; \
232
+ case CA_INT32: GROUP_WALK(int32_t, GMINMAX(int32_t, int32_t, CMP)); break; \
233
+ case CA_UINT32: GROUP_WALK(uint32_t, GMINMAX(uint32_t, uint32_t, CMP)); break; \
234
+ case CA_INT64: GROUP_WALK(int64_t, GMINMAX(int64_t, int64_t, CMP)); break; \
235
+ case CA_UINT64: GROUP_WALK(uint64_t, GMINMAX(uint64_t, uint64_t, CMP)); break; \
236
+ case CA_FLOAT32: GROUP_WALK(float, GMINMAX(float, float, CMP)); break; \
237
+ case CA_FLOAT64: GROUP_WALK(double, GMINMAX(double, double, CMP)); break; \
238
+ default: break; \
239
+ }
240
+
241
+ #define GMINMAXADDR(T, CMP, ADDR) \
242
+ do { \
243
+ T *acc = (T *) xbuf; \
244
+ if ( v == v && ( ! seen_num[o] || rv CMP acc[o] ) ) { \
245
+ acc[o] = rv; \
246
+ ADDR[o] = (int64_t) ( band_addr[b] + gaddr ); \
247
+ seen_num[o] = 1; \
248
+ } \
249
+ cnt[o] += 1; \
250
+ } while (0)
251
+ #define GMINADDR(T) GMINMAXADDR(T, <, mnaddr)
252
+ #define GMAXADDR(T) GMINMAXADDR(T, >, mxaddr)
253
+
176
254
  /* Run one walk over every supported native data type. Dispatched on the source
177
255
  data_type so the inner loop stays monomorphic (no forced float64 cast). */
178
256
  #define GROUP_DISPATCH(ACCUM) \
@@ -255,7 +333,6 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
255
333
  int8_t axes[CA_RANK_MAX];
256
334
  char is_group[CA_RANK_MAX];
257
335
  for ( int8_t i = 0; i < src->ndim; i++ ) is_group[i] = 0;
258
- ca_size_t group_prod = 1;
259
336
  for ( long i = 0; i < ngroup; i++ ) {
260
337
  int a = NUM2INT(RARRAY_AREF(vgaxes, i));
261
338
  if ( a < 0 || a >= src->ndim ) {
@@ -269,7 +346,6 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
269
346
  }
270
347
  is_group[a] = 1;
271
348
  axes[i] = (int8_t) a;
272
- group_prod *= src->dim[a];
273
349
  }
274
350
 
275
351
  /* --- bundles: small per-group code tables (metadata, kept alive) --- */
@@ -303,12 +379,25 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
303
379
  rb_raise(rb_eArgError, "axis_group_reduce: bundle k must be positive");
304
380
  }
305
381
 
306
- /* int32 view of the codes (metadata-sized, O(consumed-axis dims)) */
382
+ /* The code table is copied out and the source let go of at once, rather
383
+ than held attached for the length of the walk. Everything between here
384
+ and the end of the walk can raise -- a validation just below, a gather
385
+ that cannot convert a cell, an object-lane callback into Ruby -- and a
386
+ raise jumps over every detach, leaving the source materialised with
387
+ nothing left to release it. The copy rides a temporary buffer that the
388
+ unwind collects. */
307
389
  VALUE v32 = rb_ca_wrap_readonly(vcodes, INT2NUM(CA_INT32));
308
390
  rb_ary_push((VALUE) keep, v32);
309
391
  GetCArray(v32, bundle_ca[bi]);
310
- ca_attach(bundle_ca[bi]);
311
- bundle_codes[bi] = (int32_t *) bundle_ca[bi]->ptr;
392
+ {
393
+ size_t nbytes = (size_t) bundle_ca[bi]->elements * sizeof(int32_t);
394
+ VALUE vbuf = rb_str_tmp_new((long) nbytes);
395
+ rb_ary_push((VALUE) keep, vbuf);
396
+ ca_attach(bundle_ca[bi]);
397
+ memcpy(RSTRING_PTR(vbuf), bundle_ca[bi]->ptr, nbytes);
398
+ ca_detach(bundle_ca[bi]);
399
+ bundle_codes[bi] = (int32_t *) RSTRING_PTR(vbuf);
400
+ }
312
401
 
313
402
  int nb = (int) RARRAY_LEN(vbaxes);
314
403
  if ( nb <= 0 || nb > src->ndim ) {
@@ -363,13 +452,21 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
363
452
  }
364
453
  }
365
454
 
366
- /* --- band layout + output shape [K_total, *band_dims] --- */
367
- ca_size_t band = (group_prod > 0) ? (src->elements / group_prod) : 0;
455
+ /* --- band layout + output shape [K_total, *band_dims] ---
456
+ band is the product of the band dims, taken directly rather than as
457
+ src->elements / group_prod: a zero-length *group* axis makes group_prod
458
+ zero, and the division has to answer something. Answering 0 made nout 0
459
+ while the output really had K_total x band cells, so every finalisation
460
+ loop below ran zero times and the freshly zeroed buffer went back to the
461
+ caller unmasked -- a mean, a min and a variance all reported as 0.0.
462
+ Taken directly, nout == co->elements for every shape, and the group with
463
+ no cell in it takes the same empty-group path as any other. */
464
+ ca_size_t band = 1;
368
465
  ca_size_t odim[CA_RANK_MAX];
369
466
  int8_t ondim = 1;
370
467
  odim[0] = K_total;
371
468
  for ( int8_t i = 0; i < src->ndim; i++ ) {
372
- if ( ! is_group[i] ) odim[ondim++] = src->dim[i];
469
+ if ( ! is_group[i] ) { odim[ondim++] = src->dim[i]; band *= src->dim[i]; }
373
470
  }
374
471
  ca_size_t nout = K_total * band;
375
472
 
@@ -380,7 +477,6 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
380
477
  case CA_INT32: case CA_UINT32: case CA_INT64: case CA_UINT64:
381
478
  case CA_FLOAT32: case CA_FLOAT64: break;
382
479
  default:
383
- for ( int bi = 0; bi < n_bundles; bi++ ) ca_detach(bundle_ca[bi]);
384
480
  rb_raise(rb_eRuntimeError,
385
481
  "axis_group_reduce: unsupported source data_type %d",
386
482
  src->data_type);
@@ -392,35 +488,52 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
392
488
  else if ( op == GR_MINADDR || op == GR_MAXADDR ) out_dt = CA_INT64;
393
489
  else if ( op == GR_ALL || op == GR_ANY ) out_dt = CA_BOOLEAN;
394
490
  else if ( op == GR_ACCUM ) out_dt = src->data_type;
491
+ else if ( op == GR_MIN || op == GR_MAX )
492
+ out_dt = ( src->data_type == CA_BOOLEAN ) ? CA_UINT64 : src->data_type;
395
493
  VALUE vout = rb_carray_new(out_dt, ondim, odim, 0, NULL);
396
494
  GetCArray(vout, co);
397
495
 
398
496
  /* --- 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;
497
+ /* Scratch that the unwind collects. A raise anywhere in the walk -- an
498
+ object-lane callback into Ruby, a gather that cannot convert a cell --
499
+ jumps over every release below, so these ride ALLOCV rather than plain
500
+ malloc: the temporary buffer behind each holder is freed whether the
501
+ function returns or unwinds. */
502
+ volatile VALUE h_cnt = 0, h_sum = 0, h_sumsq = 0, h_prod = 0, h_seen = 0,
503
+ h_xbuf = 0, h_nz = 0, h_mnaddr = 0, h_mxaddr = 0, h_band = 0;
504
+ ca_size_t *cnt = ALLOCV_N(ca_size_t, h_cnt, nout);
505
+ MEMZERO(cnt, ca_size_t, nout);
506
+ double *sum = NULL, *sumsq = NULL, *prod = NULL;
507
+ boolean8_t *seen_num = NULL; /* has a number (not a NaN) landed here? */
508
+ char *xbuf = NULL; /* running extremum, in the source type */
401
509
  ca_size_t *nz = NULL;
402
510
  int64_t *mnaddr = NULL, *mxaddr = NULL; /* flat source addr of min / max */
403
511
  ca_size_t *band_addr = NULL; /* raveled addr of each band cell */
404
512
  if ( op == GR_SUM || op == GR_MEAN || op == GR_VARIANCE || op == GR_STDDEV ||
405
513
  op == GR_VARIANCEP || op == GR_STDDEVP ) {
406
- sum = ALLOC_N(double, nout); MEMZERO(sum, double, nout);
514
+ sum = ALLOCV_N(double, h_sum, nout); MEMZERO(sum, double, nout);
407
515
  }
408
516
  if ( op == GR_PROD ) {
409
- prod = ALLOC_N(double, nout);
517
+ prod = ALLOCV_N(double, h_prod, nout);
410
518
  for ( ca_size_t o = 0; o < nout; o++ ) prod[o] = 1.0;
411
519
  }
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;
520
+ if ( op == GR_MIN || op == GR_MAX ||
521
+ op == GR_MINADDR || op == GR_MAXADDR ) {
522
+ seen_num = ALLOCV_N(boolean8_t, h_seen, nout);
523
+ MEMZERO(seen_num, boolean8_t, nout);
415
524
  }
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;
525
+ if ( op == GR_MIN || op == GR_MAX ) {
526
+ /* the walk writes the extremum straight into the output, in its own type */
527
+ MEMZERO(co->ptr, char, (size_t) nout * co->bytes);
528
+ }
529
+ if ( op == GR_MINADDR || op == GR_MAXADDR ) {
530
+ xbuf = ALLOCV_N(char, h_xbuf, (size_t) nout * src->bytes);
531
+ MEMZERO(xbuf, char, (size_t) nout * src->bytes);
419
532
  }
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); }
533
+ if ( op == GR_MINADDR ) { mnaddr = ALLOCV_N(int64_t, h_mnaddr, nout); MEMZERO(mnaddr, int64_t, nout); }
534
+ if ( op == GR_MAXADDR ) { mxaddr = ALLOCV_N(int64_t, h_mxaddr, nout); MEMZERO(mxaddr, int64_t, nout); }
422
535
  if ( op == GR_ALL || op == GR_ANY ) {
423
- nz = ALLOC_N(ca_size_t, nout); MEMZERO(nz, ca_size_t, nout);
536
+ nz = ALLOCV_N(ca_size_t, h_nz, nout); MEMZERO(nz, ca_size_t, nout);
424
537
  }
425
538
 
426
539
  /* min_addr / max_addr need the flat raveled source address of each cell.
@@ -436,7 +549,7 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
436
549
  int band_axis[CA_RANK_MAX]; int nband_axes = 0;
437
550
  for ( int8_t k = 0; k < src->ndim; k++ )
438
551
  if ( ! is_group[k] ) band_axis[nband_axes++] = k;
439
- band_addr = ALLOC_N(ca_size_t, band > 0 ? band : 1);
552
+ band_addr = ALLOCV_N(ca_size_t, h_band, band > 0 ? band : 1);
440
553
  for ( ca_size_t bb = 0; bb < band; bb++ ) {
441
554
  ca_size_t rem = bb, addr = 0;
442
555
  for ( int j = nband_axes - 1; j >= 0; j-- ) { /* last band axis fastest */
@@ -459,27 +572,13 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
459
572
  GROUP_DISPATCH( cnt[o] += 1; sum[o] += v; );
460
573
  for ( ca_size_t o = 0; o < nout; o++ )
461
574
  if ( cnt[o] > 0 ) sum[o] /= (double) cnt[o]; /* sum -> mean */
462
- sumsq = ALLOC_N(double, nout); MEMZERO(sumsq, double, nout);
575
+ sumsq = ALLOCV_N(double, h_sumsq, nout); MEMZERO(sumsq, double, nout);
463
576
  GROUP_DISPATCH( { double _d = v - sum[o]; sumsq[o] += _d * _d; } );
464
577
  }
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
- }
578
+ else if ( op == GR_MINADDR ) { GROUP_DISPATCH_T(GMINADDR); }
579
+ else if ( op == GR_MAXADDR ) { GROUP_DISPATCH_T(GMAXADDR); }
580
+ else if ( op == GR_MIN ) { GROUP_DISPATCH_EXTREMUM(<); }
581
+ else if ( op == GR_MAX ) { GROUP_DISPATCH_EXTREMUM(>); }
483
582
  else if ( op == GR_ACCUM ) {
484
583
  MEMZERO(co->ptr, char, (size_t) nout * co->bytes);
485
584
  switch ( ca->data_type ) {
@@ -502,8 +601,6 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
502
601
  cnt[o] += 1;
503
602
  if ( sum ) sum[o] += v;
504
603
  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
604
  if ( nz ) { if ( v != 0.0 ) nz[o] += 1; }
508
605
  );
509
606
  }
@@ -531,13 +628,20 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
531
628
  int64_t *out = (int64_t *) co->ptr;
532
629
  int64_t *addr = ( op == GR_MINADDR ) ? mnaddr : mxaddr;
533
630
  for ( ca_size_t o = 0; o < nout; o++ ) {
534
- if ( cnt[o] == 0 ) { out[o] = 0; MARK_UNDEF(o); } /* empty -> UNDEF */
631
+ /* Nothing present, or nothing but NaN: either way no cell here won,
632
+ and the core answers UNDEF for both. */
633
+ if ( cnt[o] == 0 || ! seen_num[o] ) { out[o] = 0; MARK_UNDEF(o); }
535
634
  else out[o] = addr[o];
536
635
  }
537
636
  }
538
637
  else if ( op == GR_ACCUM ) {
539
638
  /* already folded in place, in the source's own type; empty groups hold 0 */
540
639
  }
640
+ else if ( op == GR_MIN || op == GR_MAX ) {
641
+ /* the walk wrote the extremum in place, in the source's own type; a group
642
+ with no cell in it has nothing to report */
643
+ for ( ca_size_t o = 0; o < nout; o++ ) if ( cnt[o] == 0 ) MARK_UNDEF(o);
644
+ }
541
645
  else if ( op == GR_ALL ) {
542
646
  boolean8_t *out = (boolean8_t *) co->ptr; /* empty -> true (vacuous) */
543
647
  for ( ca_size_t o = 0; o < nout; o++ )
@@ -558,12 +662,6 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
558
662
  if ( cnt[o] == 0 ) { out[o] = 0.0; MARK_UNDEF(o); }
559
663
  else out[o] = sum[o] / (double) cnt[o];
560
664
  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
665
  case GR_VARIANCE:
568
666
  case GR_STDDEV:
569
667
  if ( cnt[o] == 0 ) { out[o] = 0.0; MARK_UNDEF(o); }
@@ -582,17 +680,10 @@ rb_ca_axis_group_reduce (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
582
680
  }
583
681
  #undef MARK_UNDEF
584
682
 
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);
683
+ ALLOCV_END(h_cnt); ALLOCV_END(h_sum); ALLOCV_END(h_sumsq);
684
+ ALLOCV_END(h_prod); ALLOCV_END(h_seen); ALLOCV_END(h_xbuf);
685
+ ALLOCV_END(h_nz); ALLOCV_END(h_mnaddr); ALLOCV_END(h_mxaddr);
686
+ ALLOCV_END(h_band);
596
687
 
597
688
  RB_GC_GUARD(keep);
598
689
  return vout;
@@ -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; \
@@ -809,7 +901,7 @@ group_scan_build_plan (ca_iter_state *st, boolean8_t *m,
809
901
  } \
810
902
  b++; \
811
903
  } \
812
- xfree(acce); \
904
+ ALLOCV_END(h_acce); \
813
905
  } while (0)
814
906
 
815
907
  #define GROUP_SCAN_EXTREMUM_DISPATCH(CMP) \
@@ -990,11 +1082,25 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
990
1082
  rb_raise(rb_eArgError, "axis_group_scan: bundle k must be positive");
991
1083
  }
992
1084
 
1085
+ /* The code table is copied out and the source let go of at once, rather
1086
+ than held attached for the length of the walk. Everything between here
1087
+ and the end of the walk can raise -- a validation just below, a gather
1088
+ that cannot convert a cell, an object-lane callback into Ruby -- and a
1089
+ raise jumps over every detach, leaving the source materialised with
1090
+ nothing left to release it. The copy rides a temporary buffer that the
1091
+ unwind collects. */
993
1092
  VALUE v32 = rb_ca_wrap_readonly(vcodes, INT2NUM(CA_INT32));
994
1093
  rb_ary_push((VALUE) keep, v32);
995
1094
  GetCArray(v32, bundle_ca[bi]);
996
- ca_attach(bundle_ca[bi]);
997
- bundle_codes[bi] = (int32_t *) bundle_ca[bi]->ptr;
1095
+ {
1096
+ size_t nbytes = (size_t) bundle_ca[bi]->elements * sizeof(int32_t);
1097
+ VALUE vbuf = rb_str_tmp_new((long) nbytes);
1098
+ rb_ary_push((VALUE) keep, vbuf);
1099
+ ca_attach(bundle_ca[bi]);
1100
+ memcpy(RSTRING_PTR(vbuf), bundle_ca[bi]->ptr, nbytes);
1101
+ ca_detach(bundle_ca[bi]);
1102
+ bundle_codes[bi] = (int32_t *) RSTRING_PTR(vbuf);
1103
+ }
998
1104
 
999
1105
  int nb = (int) RARRAY_LEN(vbaxes);
1000
1106
  if ( nb <= 0 || nb > src->ndim ) {
@@ -1054,7 +1160,6 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
1054
1160
  case CA_INT32: case CA_UINT32: case CA_INT64: case CA_UINT64:
1055
1161
  case CA_FLOAT32: case CA_FLOAT64: case CA_OBJECT: break;
1056
1162
  default:
1057
- for ( int bi = 0; bi < n_bundles; bi++ ) ca_detach(bundle_ca[bi]);
1058
1163
  rb_raise(rb_eRuntimeError,
1059
1164
  "axis_group_scan: unsupported source data_type %d",
1060
1165
  src->data_type);
@@ -1091,7 +1196,8 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
1091
1196
  int band_axis[CA_RANK_MAX]; int nband_axes = 0;
1092
1197
  for ( int8_t k = 0; k < src->ndim; k++ )
1093
1198
  if ( ! is_group[k] ) band_axis[nband_axes++] = k;
1094
- ca_size_t *band_addr = ALLOC_N(ca_size_t, band > 0 ? band : 1);
1199
+ volatile VALUE h_band2 = 0;
1200
+ ca_size_t *band_addr = ALLOCV_N(ca_size_t, h_band2, band > 0 ? band : 1);
1095
1201
  for ( ca_size_t bb = 0; bb < band; bb++ ) {
1096
1202
  ca_size_t rem = bb, addr = 0;
1097
1203
  for ( int j = nband_axes - 1; j >= 0; j-- ) {
@@ -1108,13 +1214,20 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
1108
1214
  member / object identity init) are all tiny; acco (object running VALUE) is
1109
1215
  allocated only for the object arithmetic / extremum ops. */
1110
1216
  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);
1217
+ /* Scratch that the unwind collects. The object lane calls back into Ruby
1218
+ for every cell, so a raise in the middle of the walk is ordinary here --
1219
+ an operand that will not coerce, a <=> that answers nil -- and it jumps
1220
+ over every release below. ALLOCV frees the buffer behind each holder
1221
+ whether the function returns or unwinds. */
1222
+ volatile VALUE h_code = 0, h_doff = 0, h_moff = 0, h_addr = 0,
1223
+ h_accd = 0, h_accn = 0, h_seen = 0, h_acco = 0;
1224
+ ca_size_t *sw_code = ALLOCV_N(ca_size_t, h_code, plan_n);
1225
+ ca_size_t *sw_doff = ALLOCV_N(ca_size_t, h_doff, plan_n);
1226
+ ca_size_t *sw_moff = ALLOCV_N(ca_size_t, h_moff, plan_n);
1227
+ ca_size_t *sw_addr = ALLOCV_N(ca_size_t, h_addr, plan_n);
1228
+ double *accd = ALLOCV_N(double, h_accd, K_total);
1229
+ ca_size_t *accn = ALLOCV_N(ca_size_t, h_accn, K_total);
1230
+ char *seen = ALLOCV_N(char, h_seen, K_total);
1118
1231
  VALUE *acco = NULL;
1119
1232
  double acc_init = ( op == GS_CUMPROD ) ? 1.0 : 0.0;
1120
1233
 
@@ -1137,7 +1250,7 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
1137
1250
  }
1138
1251
  else { /* object running VALUE */
1139
1252
  VALUE *outo = (VALUE *) co->ptr;
1140
- acco = ALLOC_N(VALUE, K_total);
1253
+ acco = ALLOCV_N(VALUE, h_acco, K_total);
1141
1254
  switch ( op ) {
1142
1255
  case GS_CUMSUM:
1143
1256
  /* Identity 0 (matching the core object cumsum): a group's acc is lazily
@@ -1219,16 +1332,9 @@ rb_ca_axis_group_scan (VALUE self, VALUE vgaxes, VALUE vbundles, VALUE vop)
1219
1332
  }
1220
1333
  #undef MARK_OUT_UNDEF
1221
1334
 
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);
1335
+ ALLOCV_END(h_band2); ALLOCV_END(h_code); ALLOCV_END(h_doff);
1336
+ ALLOCV_END(h_moff); ALLOCV_END(h_addr); ALLOCV_END(h_accd);
1337
+ ALLOCV_END(h_accn); ALLOCV_END(h_seen); ALLOCV_END(h_acco);
1232
1338
 
1233
1339
  RB_GC_GUARD(keep);
1234
1340
  return vout;