carray 3.0.2 → 3.0.4

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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
@@ -1,120 +1,109 @@
1
1
  /* ---------------------------------------------------------------------------
2
2
 
3
- ca_categorical_iterator.c — counting-sort scatter for CACategoricalIterator.
4
-
5
- __categorical_scatter__ lays a flat payload out as a category-contiguous
6
- copy in a single pass, driven by the categorical codes and a per-category
7
- write cursor (a mutable copy of reduceat_index's segment starts). No
8
- permutation array is built; O(n), stable (ascending scan keeps each
9
- category's members in source order). It is the discrete, value-carrying
10
- sibling of histogram_scatter_ki (ext/carray_histogram.c): where the histogram
11
- scatters a +1 into counts, this scatters the payload cell into grouped.
12
-
13
- Two masks meet here and are kept distinct:
14
- - the CODES mask is authoritative for exclusion — a masked code cell does
15
- not join any group (the sentinel value never has to be read; `c < k` is a
16
- defensive assert only).
17
- - the VALUE mask propagates into grouped, so a group with a masked payload
18
- cell reduces as CArray does (the cell is skipped by mask-aware reductions).
19
-
20
- The output position is data-dependent (cursor[code]++), which the aligned
21
- kernel_iterator macros do not model, so the flat inputs are materialised here:
22
- ca_attach aliases a contiguous entity (codes / a contiguous value) and gathers
23
- a view. Codes dispatch on their native integer type (no coercion); the value
24
- move is a bytes-wide memcpy (grouped shares the value data type, so no value-type
25
- dispatch is needed).
26
-
27
- Surface (private): codes.__categorical_scatter__(value, cursor, grouped, k)
3
+ ca_categorical_iterator.c — kernels for CACategorical and CACategoricalIterator.
4
+
5
+ addresses_by_category orders the cells of a categorical by category, the
6
+ counting sort behind CACategorical#sort_addr: one pass over the codes
7
+ writes each cell's address at its category's cursor (a mutable copy of the
8
+ segment starts), so every cell of category 0 comes first, then 1, ..., in
9
+ source order within a category (stable). A cell in no category -- a masked
10
+ code, or a code outside 0...k -- is written in the same pass at a second
11
+ cursor that starts after the last category, so the excluded cells follow
12
+ in source order.
13
+
14
+ The output position depends on the code, which the aligned kernel_iterator
15
+ macros do not model, so the codes are materialised here: ca_attach aliases a
16
+ contiguous entity and gathers a view.
17
+
18
+ Surface (private): codes.addresses_by_category(cursor, k, nvalid)
28
19
  self = codes (integer, carries the exclusion mask), read flat
29
- value = payload (any data type, may carry a mask), read flat, same length
30
20
  cursor = int64 length-k segment starts (mutated in place, consumed)
31
- grouped = pre-allocated contiguous entity of the value data type, length nvalid
32
21
  k = number of categories
33
- Returns grouped.
22
+ nvalid = number of cells in a category (where the excluded cells start)
23
+ Returns a new int64 CArray of codes.elements addresses.
34
24
 
35
25
  --------------------------------------------------------------------------- */
36
26
 
37
27
  #include "carray.h"
28
+ #include "carray_internal.h" /* CA_EXTREMUM_REPLACES */
29
+ #include "ca_sort_kernels.h" /* ca_partition_nan_f64 */
38
30
  #include <stdlib.h> /* qsort */
39
31
  #include <math.h> /* floor */
40
32
 
41
- #define CATEGORICAL_SCATTER_BODY(CODE_T) \
33
+ #define ADDRESSES_BY_CATEGORY_BODY(CODE_T) \
42
34
  do { \
43
35
  const CODE_T *cp = (const CODE_T *) codes->ptr; \
44
36
  for ( j = 0; j < n; j++ ) { \
45
- if ( cmask && cmask[j] ) continue; /* excluded by codes mask */ \
46
37
  c = (int64_t) cp[j]; \
47
- if ( c < 0 || c >= k ) continue; /* defensive */ \
48
- pos = (ca_size_t) cur[c]; \
49
- cur[c] = (int64_t) (pos + 1); \
50
- memcpy(gp + pos * bytes, vp + j * bytes, (size_t) bytes); \
51
- if ( vmask && vmask[j] ) gmask[pos] = 1; /* propagate value mask */ \
38
+ if ( ( cmask && cmask[j] ) || c < 0 || c >= k ) { \
39
+ if ( tail >= (int64_t) n ) { overrun = 1; break; } \
40
+ out[tail++] = (int64_t) j; /* in no category */ \
41
+ continue; \
42
+ } \
43
+ if ( cur[c] < 0 || cur[c] >= nvalid ) { overrun = 1; break; } \
44
+ out[cur[c]++] = (int64_t) j; \
52
45
  } \
53
46
  } while (0)
54
47
 
55
48
  static VALUE
56
- rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
57
- VALUE rgrouped, VALUE rk)
49
+ rb_ca_addresses_by_category (VALUE self, VALUE rcursor, VALUE rk, VALUE rnvalid)
58
50
  {
59
- CArray *codes, *value, *cursor, *grouped;
60
- int64_t k, c, *cur;
61
- ca_size_t n, j, pos, bytes;
62
- boolean8_t *cmask, *vmask, *gmask = NULL;
63
- char *gp, *vp;
64
-
65
- GetCArray(self, codes);
66
- GetCArray(rvalue, value);
67
- GetCArray(rcursor, cursor);
68
- GetCArray(rgrouped, grouped);
69
- k = (int64_t) NUM2LL(rk);
70
-
71
- n = codes->elements;
72
- bytes = value->bytes;
73
- if ( value->elements != n ) {
74
- rb_raise(rb_eArgError,
75
- "__categorical_scatter__: value length %lld != codes length %lld",
76
- (long long) value->elements, (long long) n);
77
- }
78
- if ( cursor->data_type != CA_INT64 || cursor->elements != k ) {
79
- rb_raise(rb_eArgError, "__categorical_scatter__: cursor must be int64[k]");
80
- }
81
- if ( grouped->bytes != bytes ) {
82
- rb_raise(rb_eArgError, "__categorical_scatter__: grouped/value data type mismatch");
51
+ CArray *codes, *cursor, *result;
52
+ VALUE rresult;
53
+ int64_t k, c, *cur, *out, tail, nvalid;
54
+ ca_size_t n, j, dim;
55
+ int overrun = 0;
56
+ boolean8_t *cmask;
57
+
58
+ GetCArray(self, codes);
59
+ GetCArray(rcursor, cursor);
60
+ k = (int64_t) NUM2LL(rk);
61
+ nvalid = (int64_t) NUM2LL(rnvalid);
62
+ n = codes->elements;
63
+
64
+ if ( cursor->data_type != CA_INT64 || cursor->elements != k ||
65
+ ! ca_is_entity(cursor) ) {
66
+ rb_raise(rb_eArgError, "addresses_by_category: cursor must be an int64[k] entity");
83
67
  }
84
-
85
- ca_attach(codes);
86
- ca_attach(value);
87
- cmask = ca_mask_ptr(codes);
88
- vmask = ca_mask_ptr(value);
89
- cur = (int64_t *) cursor->ptr;
90
- gp = grouped->ptr;
91
- vp = value->ptr;
92
-
93
- if ( vmask ) { /* grouped needs a mask to receive it */
94
- ca_create_mask(grouped);
95
- gmask = (boolean8_t *) grouped->mask->ptr;
68
+ if ( nvalid < 0 || nvalid > (int64_t) n ) {
69
+ rb_raise(rb_eArgError, "addresses_by_category: nvalid %lld out of 0..%lld",
70
+ (long long) nvalid, (long long) n);
96
71
  }
97
-
98
72
  switch ( codes->data_type ) {
99
- case CA_INT8: CATEGORICAL_SCATTER_BODY(int8_t); break;
100
- case CA_UINT8: CATEGORICAL_SCATTER_BODY(uint8_t); break;
101
- case CA_INT16: CATEGORICAL_SCATTER_BODY(int16_t); break;
102
- case CA_UINT16: CATEGORICAL_SCATTER_BODY(uint16_t); break;
103
- case CA_INT32: CATEGORICAL_SCATTER_BODY(int32_t); break;
104
- case CA_UINT32: CATEGORICAL_SCATTER_BODY(uint32_t); break;
105
- case CA_INT64: CATEGORICAL_SCATTER_BODY(int64_t); break;
106
- case CA_UINT64: CATEGORICAL_SCATTER_BODY(uint64_t); break;
73
+ case CA_INT8: case CA_UINT8: case CA_INT16: case CA_UINT16:
74
+ case CA_INT32: case CA_UINT32: case CA_INT64: case CA_UINT64:
75
+ break;
107
76
  default:
108
- ca_detach(codes);
109
- ca_detach(value);
110
77
  rb_raise(rb_eCADataTypeError,
111
- "__categorical_scatter__: integer codes required (got data_type %d)",
78
+ "addresses_by_category: integer codes required (got data_type %d)",
112
79
  codes->data_type);
113
80
  }
114
81
 
82
+ dim = n;
83
+ rresult = rb_carray_new(CA_INT64, 1, &dim, 0, NULL);
84
+ GetCArray(rresult, result);
85
+ out = (int64_t *) result->ptr;
86
+ cur = (int64_t *) cursor->ptr;
87
+ tail = nvalid;
88
+
89
+ ca_attach(codes); /* window: nothing below raises */
90
+ cmask = ca_mask_ptr(codes);
91
+ switch ( codes->data_type ) {
92
+ case CA_INT8: ADDRESSES_BY_CATEGORY_BODY(int8_t); break;
93
+ case CA_UINT8: ADDRESSES_BY_CATEGORY_BODY(uint8_t); break;
94
+ case CA_INT16: ADDRESSES_BY_CATEGORY_BODY(int16_t); break;
95
+ case CA_UINT16: ADDRESSES_BY_CATEGORY_BODY(uint16_t); break;
96
+ case CA_INT32: ADDRESSES_BY_CATEGORY_BODY(int32_t); break;
97
+ case CA_UINT32: ADDRESSES_BY_CATEGORY_BODY(uint32_t); break;
98
+ case CA_INT64: ADDRESSES_BY_CATEGORY_BODY(int64_t); break;
99
+ case CA_UINT64: ADDRESSES_BY_CATEGORY_BODY(uint64_t); break;
100
+ }
115
101
  ca_detach(codes);
116
- ca_detach(value);
117
- return rgrouped;
102
+ if ( overrun ) {
103
+ rb_raise(rb_eArgError,
104
+ "addresses_by_category: the cursor and nvalid do not match the codes");
105
+ }
106
+ return rresult;
118
107
  }
119
108
 
120
109
  /* ---------------------------------------------------------------------------
@@ -151,7 +140,22 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
151
140
  #define CAT_NAN_NEVER(v) (0)
152
141
  #define CAT_NAN_CHECK(v) ((v) != (v))
153
142
 
154
- #define REDUCEAT_MOMENTS_BODY(T, ISNAN) \
143
+ /* Whether v takes the place of the accumulator acc: never when v is NaN,
144
+ always when acc is NaN and v is not. */
145
+ #define CAT_REPLACES(v, acc, CMP, ISNAN) \
146
+ ( ! ISNAN(v) && ( ISNAN(acc) || (v) CMP (acc) ) )
147
+
148
+ /* A segment without masked cells of at least CAT_LANES_FROM cells is read
149
+ with eight independent sums and eight min / max lanes, so no add or
150
+ compare waits on the one before it; a shorter one would spend more on
151
+ setting up and merging the lanes than it saves.
152
+ Each lane starts from the segment's first value; a lane can end on NaN
153
+ only if every value it saw was NaN, so merging the lanes with the same
154
+ rule gives the answer one lane would. The sums are added in a different
155
+ order than the cells, which is the license every float reduction has. */
156
+ #define CAT_LANES_FROM 64
157
+
158
+ #define REDUCEAT_MOMENTS_BODY(T, ISNAN, LANES_FROM) \
155
159
  do { \
156
160
  const T *gp = (const T *) grouped->ptr; \
157
161
  T *minv = (T *) minp, *maxv = (T *) maxp; \
@@ -160,18 +164,96 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
160
164
  ca_size_t hi = (c + 1 < k) ? (ca_size_t) offs[c+1] : n; \
161
165
  ca_size_t j, cnt = 0; \
162
166
  double sacc = 0.0; \
163
- T mn = 0, mx = 0; int seen = 0, seen_num = 0; \
164
- for ( j = lo; j < hi; j++ ) { \
165
- if ( gm && gm[j] ) continue; /* masked value cell */ \
166
- { T v = gp[j]; \
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; } \
167
+ T mn = 0, mx = 0; int seen = 0; \
168
+ if ( ! gm && hi - lo < (ca_size_t) (LANES_FROM) ) { \
169
+ if ( hi > lo ) { \
170
+ mn = mx = gp[lo]; \
171
+ for ( j = lo; j < hi; j++ ) { \
172
+ T v = gp[j]; \
173
+ sacc += (double) v; \
174
+ if ( CAT_REPLACES(v, mn, <, ISNAN) ) mn = v; \
175
+ if ( CAT_REPLACES(v, mx, >, ISNAN) ) mx = v; \
176
+ } \
177
+ cnt = hi - lo; seen = 1; \
178
+ } \
179
+ } \
180
+ else if ( ! gm ) { \
181
+ { \
182
+ double s0 = 0, s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, s6 = 0, s7 = 0; \
183
+ T n0, n1, n2, n3, n4, n5, n6, n7, x0, x1, x2, x3, x4, x5, x6, x7; \
184
+ n0 = n1 = n2 = n3 = n4 = n5 = n6 = n7 = gp[lo]; \
185
+ x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = gp[lo]; \
186
+ for ( j = lo; j + 8 <= hi; j += 8 ) { \
187
+ T v0 = gp[j], v1 = gp[j+1], v2 = gp[j+2], v3 = gp[j+3]; \
188
+ T v4 = gp[j+4], v5 = gp[j+5], v6 = gp[j+6], v7 = gp[j+7]; \
189
+ s0 += (double) v0; s1 += (double) v1; \
190
+ s2 += (double) v2; s3 += (double) v3; \
191
+ s4 += (double) v4; s5 += (double) v5; \
192
+ s6 += (double) v6; s7 += (double) v7; \
193
+ if ( CAT_REPLACES(v0, n0, <, ISNAN) ) n0 = v0; \
194
+ if ( CAT_REPLACES(v1, n1, <, ISNAN) ) n1 = v1; \
195
+ if ( CAT_REPLACES(v2, n2, <, ISNAN) ) n2 = v2; \
196
+ if ( CAT_REPLACES(v3, n3, <, ISNAN) ) n3 = v3; \
197
+ if ( CAT_REPLACES(v4, n4, <, ISNAN) ) n4 = v4; \
198
+ if ( CAT_REPLACES(v5, n5, <, ISNAN) ) n5 = v5; \
199
+ if ( CAT_REPLACES(v6, n6, <, ISNAN) ) n6 = v6; \
200
+ if ( CAT_REPLACES(v7, n7, <, ISNAN) ) n7 = v7; \
201
+ if ( CAT_REPLACES(v0, x0, >, ISNAN) ) x0 = v0; \
202
+ if ( CAT_REPLACES(v1, x1, >, ISNAN) ) x1 = v1; \
203
+ if ( CAT_REPLACES(v2, x2, >, ISNAN) ) x2 = v2; \
204
+ if ( CAT_REPLACES(v3, x3, >, ISNAN) ) x3 = v3; \
205
+ if ( CAT_REPLACES(v4, x4, >, ISNAN) ) x4 = v4; \
206
+ if ( CAT_REPLACES(v5, x5, >, ISNAN) ) x5 = v5; \
207
+ if ( CAT_REPLACES(v6, x6, >, ISNAN) ) x6 = v6; \
208
+ if ( CAT_REPLACES(v7, x7, >, ISNAN) ) x7 = v7; \
209
+ } \
210
+ for ( ; j < hi; j++ ) { \
211
+ T v = gp[j]; \
212
+ s0 += (double) v; \
213
+ if ( CAT_REPLACES(v, n0, <, ISNAN) ) n0 = v; \
214
+ if ( CAT_REPLACES(v, x0, >, ISNAN) ) x0 = v; \
215
+ } \
216
+ sacc = ((s0 + s1) + (s2 + s3)) + ((s4 + s5) + (s6 + s7)); \
217
+ mn = n0; \
218
+ if ( CAT_REPLACES(n1, mn, <, ISNAN) ) mn = n1; \
219
+ if ( CAT_REPLACES(n2, mn, <, ISNAN) ) mn = n2; \
220
+ if ( CAT_REPLACES(n3, mn, <, ISNAN) ) mn = n3; \
221
+ if ( CAT_REPLACES(n4, mn, <, ISNAN) ) mn = n4; \
222
+ if ( CAT_REPLACES(n5, mn, <, ISNAN) ) mn = n5; \
223
+ if ( CAT_REPLACES(n6, mn, <, ISNAN) ) mn = n6; \
224
+ if ( CAT_REPLACES(n7, mn, <, ISNAN) ) mn = n7; \
225
+ mx = x0; \
226
+ if ( CAT_REPLACES(x1, mx, >, ISNAN) ) mx = x1; \
227
+ if ( CAT_REPLACES(x2, mx, >, ISNAN) ) mx = x2; \
228
+ if ( CAT_REPLACES(x3, mx, >, ISNAN) ) mx = x3; \
229
+ if ( CAT_REPLACES(x4, mx, >, ISNAN) ) mx = x4; \
230
+ if ( CAT_REPLACES(x5, mx, >, ISNAN) ) mx = x5; \
231
+ if ( CAT_REPLACES(x6, mx, >, ISNAN) ) mx = x6; \
232
+ if ( CAT_REPLACES(x7, mx, >, ISNAN) ) mx = x7; \
233
+ /* Of equal values a lane keeps its first, but the merge does not \
234
+ know which lane saw its value first in cell order. Only 0.0 and \
235
+ -0.0 are equal and tell apart: take the first zero, as one loop \
236
+ over the cells does. */ \
237
+ if ( mn == 0 ) { \
238
+ for ( j = lo; j < hi; j++ ) { if ( gp[j] == 0 ) { mn = gp[j]; break; } } \
239
+ } \
240
+ if ( mx == 0 ) { \
241
+ for ( j = lo; j < hi; j++ ) { if ( gp[j] == 0 ) { mx = gp[j]; break; } } \
171
242
  } \
172
- else if ( ! seen ) { mn = v; mx = v; } /* held until a number lands */\
173
- seen = 1; \
174
- cnt++; } \
243
+ cnt = hi - lo; seen = 1; \
244
+ } \
245
+ } \
246
+ else { \
247
+ for ( j = lo; j < hi && gm[j]; j++ ) ; /* skip to the first present */ \
248
+ if ( j < hi ) { mn = mx = gp[j]; seen = 1; } \
249
+ for ( ; j < hi; j++ ) { \
250
+ T v = gp[j]; \
251
+ if ( gm[j] ) continue; /* masked value cell */ \
252
+ sacc += (double) v; \
253
+ cnt++; \
254
+ if ( CAT_REPLACES(v, mn, <, ISNAN) ) mn = v; \
255
+ if ( CAT_REPLACES(v, mx, >, ISNAN) ) mx = v; \
256
+ } \
175
257
  } \
176
258
  countp[c] = (int64_t) cnt; \
177
259
  sump[c] = sacc; \
@@ -222,16 +304,16 @@ rb_ca_reduceat_moments (VALUE self, VALUE roffsets, VALUE rcounts,
222
304
  maxm = (boolean8_t *) maxs->mask->ptr;
223
305
 
224
306
  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;
307
+ case CA_INT8: REDUCEAT_MOMENTS_BODY(int8_t, CAT_NAN_NEVER, CA_LENGTH_MAX); break;
308
+ case CA_UINT8: REDUCEAT_MOMENTS_BODY(uint8_t, CAT_NAN_NEVER, CA_LENGTH_MAX); break;
309
+ case CA_INT16: REDUCEAT_MOMENTS_BODY(int16_t, CAT_NAN_NEVER, CA_LENGTH_MAX); break;
310
+ case CA_UINT16: REDUCEAT_MOMENTS_BODY(uint16_t, CAT_NAN_NEVER, CA_LENGTH_MAX); break;
311
+ case CA_INT32: REDUCEAT_MOMENTS_BODY(int32_t, CAT_NAN_NEVER, CA_LENGTH_MAX); break;
312
+ case CA_UINT32: REDUCEAT_MOMENTS_BODY(uint32_t, CAT_NAN_NEVER, CA_LENGTH_MAX); break;
313
+ case CA_INT64: REDUCEAT_MOMENTS_BODY(int64_t, CAT_NAN_NEVER, CA_LENGTH_MAX); break;
314
+ case CA_UINT64: REDUCEAT_MOMENTS_BODY(uint64_t, CAT_NAN_NEVER, CA_LENGTH_MAX); break;
315
+ case CA_FLOAT32: REDUCEAT_MOMENTS_BODY(float32_t, CAT_NAN_CHECK, CAT_LANES_FROM); break;
316
+ case CA_FLOAT64: REDUCEAT_MOMENTS_BODY(float64_t, CAT_NAN_CHECK, CAT_LANES_FROM); break;
235
317
  default:
236
318
  rb_raise(rb_eCADataTypeError,
237
319
  "__reduceat_moments__: numeric value required (got data_type %d)",
@@ -295,20 +377,12 @@ ca_nth_element_double (double *a, ca_size_t n, ca_size_t kth)
295
377
  { double f = (double) (m - 1) * p / 100.0; \
296
378
  ca_size_t ki = (ca_size_t) floor(f); \
297
379
  double vlo, vhi; \
298
- /* Push NaN to the tail, which is where CArray's sort puts it, so the \
299
- ki-th element here is the ki-th of the sorted segment. Selection \
300
- 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. */ \
305
- ca_size_t nnum = m, t0 = 0; \
306
- while ( t0 < nnum ) { \
307
- if ( scratch[t0] != scratch[t0] ) { \
308
- double tmp = scratch[--nnum]; \
309
- scratch[nnum] = scratch[t0]; scratch[t0] = tmp; \
310
- } else t0++; \
311
- } \
380
+ /* NaN to the tail, where CArray's sort puts it (ca_partition_nan), \
381
+ so the ki-th element here is the ki-th of the sorted segment. \
382
+ Selection runs on the numeric prefix; a position or an upper \
383
+ neighbour landing in the tail is NaN. The blend is \
384
+ CArray#percentile's, so both answer alike for NaN and infinities. */\
385
+ ca_size_t nnum = ca_partition_nan_f64(scratch, m); \
312
386
  if ( ki < nnum ) { \
313
387
  ca_nth_element_double(scratch, nnum, ki); /* scratch[ki] = ki-th */ \
314
388
  vlo = scratch[ki]; \
@@ -317,8 +391,8 @@ ca_nth_element_double (double *a, ca_size_t n, ca_size_t kth)
317
391
  double mn = scratch[ki+1]; ca_size_t t; \
318
392
  for ( t = ki + 2; t < nnum; t++ ) if ( scratch[t] < mn ) mn = scratch[t]; \
319
393
  vhi = mn; \
320
- } else vhi = vlo; \
321
- outp[c] = vlo + (f - (double) ki) * (vhi - vlo); } \
394
+ } else vhi = (double) NAN; \
395
+ outp[c] = ca_percentile_linear(vlo, vhi, f - (double) ki, ki, m); } \
322
396
  } \
323
397
  } while (0)
324
398
 
@@ -335,6 +409,11 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
335
409
  GetCArray(roffsets, offsets);
336
410
  GetCArray(rout, out);
337
411
  p = NUM2DBL(rp);
412
+ if ( !(p >= 0.0 && p <= 100.0) ) {
413
+ rb_raise(rb_eArgError,
414
+ "percentile: p must be Numeric in [0,100] (got %"PRIsVALUE")",
415
+ rb_inspect(rp));
416
+ }
338
417
  k = (int64_t) offsets->elements;
339
418
  n = grouped->elements;
340
419
  if ( offsets->data_type != CA_INT64 || out->data_type != CA_FLOAT64 ||
@@ -383,7 +462,7 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
383
462
  accumulates the per-segment centred sum of squares; variance = SS / (n-1).
384
463
  Centred (not the one-pass sum-of-squares) so the ε-close contract holds — a
385
464
  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 ->
465
+ count 0 or 1 -> masked (the sample variance is undefined), count >= 2 ->
387
466
  SS / (count-1).
388
467
 
389
468
  Surface (private): grouped.__reduceat_variance__(offsets, means, counts, out)
@@ -391,7 +470,7 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
391
470
  offsets = int64[k] segment STARTS
392
471
  means = float64[k] per-segment mean (ignored where count < 2)
393
472
  counts = int64[k] per-segment present count
394
- out = float64[k] output: variance; count 0 masked, count 1 -> 0.0
473
+ out = float64[k] output: variance; count 0 or 1 masked
395
474
 
396
475
  --------------------------------------------------------------------------- */
397
476
 
@@ -400,8 +479,7 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
400
479
  const T *gp = (const T *) grouped->ptr; \
401
480
  for ( c = 0; c < k; c++ ) { \
402
481
  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 */ \
482
+ if ( cnt < 2 ) { outp[c] = 0.0; outm[c] = 1; continue; } /* undefined */ \
405
483
  { double mean = meanp[c], ss = 0.0; \
406
484
  ca_size_t lo = (ca_size_t) offs[c]; \
407
485
  ca_size_t hi = (c + 1 < k) ? (ca_size_t) offs[c+1] : n, j; \
@@ -689,19 +767,16 @@ cmp_double (const void *a, const void *b)
689
767
  scratch[m++] = (double) gp[jj]; \
690
768
  } \
691
769
  if ( m == 0 ) { for ( t = 0; t < 5; t++ ) { outp[t][c] = 0.0; outm[t][c] = 1; } continue; } \
770
+ /* NaN sorts last, as in the core's sort, so a position or an upper \
771
+ neighbour in the tail is NaN, as the core answers. */ \
692
772
  qsort(scratch, (size_t) m, sizeof(double), cmp_double); \
693
- { ca_size_t nnum = m; \
694
- while ( nnum > 0 && scratch[nnum-1] != scratch[nnum-1] ) nnum--; \
695
773
  for ( t = 0; t < 5; t++ ) { \
696
774
  double f = (double) (m - 1) * P[t] / 100.0; \
697
775
  ca_size_t ki = (ca_size_t) floor(f); \
698
- double vlo = scratch[ki]; \
699
- /* Clamp the upper neighbour into the numeric prefix: with NaN sorted \
700
- last, a position whose neighbour is a NaN interpolates against \
701
- itself, which is what the core answers. */ \
702
- double vhi = (ki + 1 < nnum) ? scratch[ki+1] : vlo; \
703
- outp[t][c] = vlo + (f - (double) ki) * (vhi - vlo); \
704
- } } \
776
+ double vhi = (ki + 1 < m) ? scratch[ki+1] : scratch[ki]; \
777
+ outp[t][c] = ca_percentile_linear(scratch[ki], vhi, f - (double) ki, \
778
+ ki, m); \
779
+ } \
705
780
  } \
706
781
  } while (0)
707
782
 
@@ -853,7 +928,7 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
853
928
 
854
929
  Not aligned kernel_iterator: output position depends on the codes value
855
930
  (data-dependent scatter), so ca_attach materialises the inputs into
856
- contiguous flat buffers — same pattern as sibling __categorical_scatter__.
931
+ contiguous flat buffers — same pattern as sibling addresses_by_category.
857
932
 
858
933
  Surface (private):
859
934
  h.__fiber_scatter_moments__(codes, axis, K, counts_out, sums_out)
@@ -869,7 +944,7 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
869
944
  (empty group cell → 0 + masked, matching __reduceat_moments__).
870
945
  --------------------------------------------------------------------------- */
871
946
 
872
- #define FIBER_SCATTER_BODY(H_T, C_T, ISNAN) \
947
+ #define FIBER_SCATTER_BODY(H_T, C_T) \
873
948
  do { \
874
949
  const H_T *hp = (const H_T *) h->ptr; \
875
950
  const C_T *cp = (const C_T *) codes->ptr; \
@@ -891,18 +966,11 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
891
966
  if ( c < 0 || c >= K ) continue; /* out-of-vocabulary */ \
892
967
  out_off = c * band_size + out_outer + inn; \
893
968
  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
969
  if ( countp[out_off] == 0 ) { \
898
970
  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
- } \
971
+ } else { \
972
+ if ( CA_EXTREMUM_REPLACES(v, minv[out_off], <) ) minv[out_off] = v;\
973
+ if ( CA_EXTREMUM_REPLACES(v, maxv[out_off], >) ) maxv[out_off] = v;\
906
974
  } \
907
975
  countp[out_off]++; \
908
976
  sump[out_off] += (double) v; \
@@ -911,16 +979,16 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
911
979
  } \
912
980
  } while (0)
913
981
 
914
- #define FIBER_SCATTER_DISPATCH_C(H_T, ISNAN) \
982
+ #define FIBER_SCATTER_DISPATCH_C(H_T) \
915
983
  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; \
984
+ case CA_INT8: FIBER_SCATTER_BODY(H_T, int8_t); break; \
985
+ case CA_UINT8: FIBER_SCATTER_BODY(H_T, uint8_t); break; \
986
+ case CA_INT16: FIBER_SCATTER_BODY(H_T, int16_t); break; \
987
+ case CA_UINT16: FIBER_SCATTER_BODY(H_T, uint16_t); break; \
988
+ case CA_INT32: FIBER_SCATTER_BODY(H_T, int32_t); break; \
989
+ case CA_UINT32: FIBER_SCATTER_BODY(H_T, uint32_t); break; \
990
+ case CA_INT64: FIBER_SCATTER_BODY(H_T, int64_t); break; \
991
+ case CA_UINT64: FIBER_SCATTER_BODY(H_T, uint64_t); break; \
924
992
  default: \
925
993
  ca_detach(h); ca_detach(codes); \
926
994
  rb_raise(rb_eCADataTypeError, \
@@ -1005,8 +1073,7 @@ rb_ca_fiber_scatter_moments (VALUE self, VALUE rcodes, VALUE raxis, VALUE rk,
1005
1073
  band_size = outer_prod * inner_prod;
1006
1074
  total = (ca_size_t)(K * band_size);
1007
1075
 
1008
- ca_attach(h);
1009
- ca_attach(codes);
1076
+ ca_attach_n(2, h, codes); /* window: detached before each raise inside */
1010
1077
  hm = ca_mask_ptr(h);
1011
1078
  cm = ca_mask_ptr(codes);
1012
1079
  countp = (int64_t *) counts->ptr;
@@ -1020,16 +1087,16 @@ rb_ca_fiber_scatter_moments (VALUE self, VALUE rcodes, VALUE raxis, VALUE rk,
1020
1087
  memset(maxp, 0, (size_t) total * (size_t) h->bytes);
1021
1088
 
1022
1089
  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;
1090
+ case CA_INT8: FIBER_SCATTER_DISPATCH_C(int8_t); break;
1091
+ case CA_UINT8: FIBER_SCATTER_DISPATCH_C(uint8_t); break;
1092
+ case CA_INT16: FIBER_SCATTER_DISPATCH_C(int16_t); break;
1093
+ case CA_UINT16: FIBER_SCATTER_DISPATCH_C(uint16_t); break;
1094
+ case CA_INT32: FIBER_SCATTER_DISPATCH_C(int32_t); break;
1095
+ case CA_UINT32: FIBER_SCATTER_DISPATCH_C(uint32_t); break;
1096
+ case CA_INT64: FIBER_SCATTER_DISPATCH_C(int64_t); break;
1097
+ case CA_UINT64: FIBER_SCATTER_DISPATCH_C(uint64_t); break;
1098
+ case CA_FLOAT32: FIBER_SCATTER_DISPATCH_C(float32_t); break;
1099
+ case CA_FLOAT64: FIBER_SCATTER_DISPATCH_C(float64_t); break;
1033
1100
  default:
1034
1101
  ca_detach(h); ca_detach(codes);
1035
1102
  rb_raise(rb_eCADataTypeError,
@@ -1172,8 +1239,7 @@ rb_ca_fiber_scatter_prod (VALUE self, VALUE rcodes, VALUE raxis,
1172
1239
  band_size = outer_prod * inner_prod;
1173
1240
  total = (ca_size_t)(K * band_size);
1174
1241
 
1175
- ca_attach(h);
1176
- ca_attach(codes);
1242
+ ca_attach_n(2, h, codes); /* window: detached before each raise inside */
1177
1243
  hm = ca_mask_ptr(h);
1178
1244
  cm = ca_mask_ptr(codes);
1179
1245
  outp = (double *) out->ptr;
@@ -1281,6 +1347,7 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1281
1347
  ca_size_t ax, inn, outer, cell;
1282
1348
  ca_size_t axis_size, inner_prod, outer_prod, band_size, total;
1283
1349
  double *wp, *wsp, *wsw, *wmp;
1350
+ volatile VALUE hwsw = 0, hcnt = 0;
1284
1351
  boolean8_t *hm, *cm, *wm, *wmm;
1285
1352
  int8_t i, j;
1286
1353
  int64_t *cnt_scratch = NULL;
@@ -1341,9 +1408,7 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1341
1408
  band_size = outer_prod * inner_prod;
1342
1409
  total = (ca_size_t)(K * band_size);
1343
1410
 
1344
- ca_attach(h);
1345
- ca_attach(codes);
1346
- ca_attach(weights);
1411
+ ca_attach_n(3, h, codes, weights); /* window: detached before each raise inside */
1347
1412
  hm = ca_mask_ptr(h);
1348
1413
  cm = ca_mask_ptr(codes);
1349
1414
  wm = ca_mask_ptr(weights);
@@ -1352,8 +1417,9 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1352
1417
  wmp = (double *) wmean->ptr; /* wmean output (temp = sum-of-weights, then divide) */
1353
1418
 
1354
1419
  /* 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));
1420
+ /* ALLOCV, so a raise below (codes of the wrong type) leaves nothing. */
1421
+ wsw = ALLOCV_N(double, hwsw, total);
1422
+ cnt_scratch = ALLOCV_N(int64_t, hcnt, total);
1357
1423
 
1358
1424
  memset(wsp, 0, (size_t) total * sizeof(double));
1359
1425
  memset(wsw, 0, (size_t) total * sizeof(double));
@@ -1372,7 +1438,7 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1372
1438
  case CA_FLOAT32: FIBER_SCATTER_WSUM_DISPATCH_C(float32_t); break;
1373
1439
  case CA_FLOAT64: FIBER_SCATTER_WSUM_DISPATCH_C(float64_t); break;
1374
1440
  default:
1375
- xfree(wsw); xfree(cnt_scratch);
1441
+ ALLOCV_END(hwsw); ALLOCV_END(hcnt);
1376
1442
  ca_detach(h); ca_detach(codes); ca_detach(weights);
1377
1443
  rb_raise(rb_eCADataTypeError,
1378
1444
  "__fiber_scatter_wsum_wmean__: numeric value required (got %d)",
@@ -1391,8 +1457,8 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1391
1457
  }
1392
1458
  }
1393
1459
 
1394
- xfree(wsw);
1395
- xfree(cnt_scratch);
1460
+ ALLOCV_END(hwsw);
1461
+ ALLOCV_END(hcnt);
1396
1462
  ca_detach(h);
1397
1463
  ca_detach(codes);
1398
1464
  ca_detach(weights);
@@ -1402,8 +1468,8 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1402
1468
  void
1403
1469
  Init_ca_categorical_iterator (void)
1404
1470
  {
1405
- rb_define_private_method(rb_cCArray, "__categorical_scatter__",
1406
- rb_ca_categorical_scatter, 4);
1471
+ rb_define_private_method(rb_cCArray, "addresses_by_category",
1472
+ rb_ca_addresses_by_category, 3);
1407
1473
  rb_define_private_method(rb_cCArray, "__fiber_scatter_moments__",
1408
1474
  rb_ca_fiber_scatter_moments, 7);
1409
1475
  rb_define_private_method(rb_cCArray, "__fiber_scatter_prod__",